【Python】Basics - 类

Posted by 西维蜀黍 on 2019-09-23, Last Modified on 2021-09-21

类定义

>>> class Complex(object):
...     def __init__(self, realpart, imagpart):
...         self.r = realpart
...         self.i = imagpart
...
>>> x = Complex(3.0, -4.5)
>>> x.r, x.i
(3.0, -4.5)

class后面紧接着是类名,即Complex,类名通常是大写开头的单词,紧接着是(object),表示该类是从哪个类继承下来的。

通常,如果没有合适的继承类,就使用object类,这是所有类最终都会继承的类。

__init__其实就是构造函数(constrctor)。注意到__init__方法的第一个参数永远是self,表示创建的实例本身,因此,在__init__方法内部,就可以把各种属性绑定到self,因为self就指向创建的实例本身。

Python2 中定义类时要不要继承 object

class Person:
    """
    不带object
    """
    name = "zhengtong"
 
 
class Animal(object):
    """
    带有object
    """
    name = "chonghong"
 
if __name__ == "__main__":
    x = Person()
    print "Person", dir(x)
 
    y = Animal()
    print "Animal", dir(y)

运行结果:

Person ['__doc__', '__module__', 'name']

Animal ['__class__', '__delattr__', '__dict__', '__doc__', '__format__', '__getattribute__', '__hash__', '__init__', '__module__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'name']

Person类很明显能够看出区别,不继承object对象,只拥有了doc , module 和 自己定义的name变量,也就是说这个类的命名空间只有三个对象可以操作。

Animal类继承了object对象,拥有了好多可操作对象,这些都是类中的高级特性。

实际上在python 3 中已经默认就帮你加载了object了(即便你没有写上object)。

类中的变量

实例变量

class Kls(object):
  	def __inti__(self):
      	self.data = 1
        
    def printd(self):
        print(self.data)

通过self 来声明并为一个类对象的实例变量赋值。

类变量

可以直接在class中定义属性,这种属性是类属性,归Student类所有:

class Student(object):
    name = 'Student'
    
    def get_class_name(self):
      return Student.name

当我们定义了一个类属性后,这个属性虽然归类所有,但类的所有实例都可以访问到。来测试一下:

>>> Student.name # 打印类的name属性
Student
>>> s = Student() # 创建实例s
>>> print(s.name) # 打印name属性,因为实例并没有name属性,所以会继续查找class的name属性
Student
>>> s.name = 'Michael' # 给实例绑定name属性
>>> print(s.name) # 由于实例属性优先级比类属性高,因此,它会屏蔽掉类的name属性
Michael
>>> print(Student.name) # 但是类属性并未消失,用Student.name仍然可以访问
Student
>>> del s.name # 如果删除实例的name属性
>>> print(s.name) # 再次调用s.name,由于实例的name属性没有找到,类的name属性就显示出来了
Student

结论

  • 类变量可以用 类名.类变量 或者 self.类变量 两种中任何一种方式访问,但不建议使用后者这种方式。
  • 类变量是所有对象所共享的,无论任何时候都建议用类名的方式访问类变量。
  • 实例变量在类内部用 self 访问(self.name),在类外部通过类实例对象来访问(s.name)。
  • 类变量在类内部或者类外部都用类名来访问(Student.name)。

全局变量

在Python的变量使用中,经常会遇到这样的错误:

local variable 'a' referenced before assignment

它的意思是:局部变量“a”在赋值前就被引用了。这其实是因为,在默认情况下,任何被赋值(a = …)的变量都是一个局部变量(local variable),

比如运行下面的代码就会出现这样的问题:

a = 3
def Fuc():
    print (a)
    a = a + 1
Fuc()

但是如果把 a = a + 1 这一句删除又不会出现上述问题了

a = 3
def Fuc():
    print (a)
Fuc()

原来,在Python中,a = 3 定义了一个变量a,其作用域从定义处到代码结束。换句话说,在 a=3 以下的代码中均可以访问到该变量a,但如果要在子函数中修改该变量,则需要在子函数中将该变量声明为全局变量(通过 global 关键字)。

a = 3
def Fuc():
    global a
    print (a)
    a=a+1
Fuc()

总结,要在哪个函数中修改一个变量,则需在该函数中将该变量声明为全局变量。

但是有一个函数特殊,那就是主函数:

a = 3
def Fuc():
    global a
    print (a)  # 1
    a = a + 1
if name == "main":
    print (a)  # 2
    a = a + 1
    Fuc()
    print (a)  # 3

输出如下(Python3环境下):

3
4
5

三个print执行的顺序为:2, 1, 3 。可以看到主函数中并没有通过 global 关键字重新声明变量a,而仍然可以修改变量a。而在普通函数中,需要通过 global 关键字以将变量a 声明为全局变量,才可以修改它。

静态变量

class Foo(object):
    _count = 0 # 不要直接操作这个变量,也尽量避免访问它

    def add(self):
        Foo._count += 1

f1 = Foo()
f2 = Foo()
print(f1._count)
print(f2._count)
print(Foo._count)

f1.add()

print(f1._count)
print(f2._count)
print(Foo._count)

# 结果:
# 0 0 0
# 1 1 1

可以借助@property装饰器实现通过使用getter和setter进行包装:

class Foo(object):
    _count = 0 # 不要直接操作这个变量,也尽量避免访问它

    @property
    def count(self):
        return Foo._count

    @count.setter
    def count(self, num):
        Foo._count = num


f1 = Foo()
f2 = Foo()
print f1.count, f1._count, f2.count, f2._count

f1.count = 1
print f1.count, f1._count, f2.count, f2._count
# 结果:
# 0 0 0 0
# 1 1 1 1

私有变量

  • 类似__xx,以双下划线开头的实例变量名,就变成了一个私有变量(private),只有在类的内部通过 self__xx 可以访问,在类外部通过类的实例(对象)不能访问;
  • 类似__xx__,以双下划线开头,并且以双下划线结尾的是特殊变量,特殊变量是可以直接访问的,它不是private变量;
  • 类似_x,以单下划线开头的实例变量名,这样的变量在类外部通过类的实例(对象)是可以访问的。但是,按照约定俗成的规定,当你看到这样的变量时,意思就是,“虽然我可以被访问,但是请把我视为私有变量,不要随意访问”。

实验:

class A():
	def __init__(self):
		self._a = 1
		self.__b = 2


a = A()
print a._a
# print a.__b  # 报错 AttributeError: A instance has no attribute '__b'

类中的方法

实例方法(Instance Method)

Python 的实例方法用得最多,也最常见。我们先来看 Python 的实例方法。

class Kls(object):
    def __init__(self, data):
        self.data = data

    def printd(self):    # 实例方法,必须传入自身实例(self)作为参数
        print(self.data)


ik1 = Kls('leo')
ik2 = Kls('lee')

ik1.printd()
ik2.printd()

静态方法(Static Method)和类方法(Class Method)

一般来说,要使用某个类的方法,需要先实例化出一个对象后,才能调用这个对象中的方法。

而使用@staticmethod或@classmethod,就可以不需要实例化,直接类名.方法名()来调用。

这有利于组织代码,把某些应该属于某个类的方法给放到那个类里去,同时有利于命名空间的整洁。

既然@staticmethod和@classmethod都可以直接类名.方法名()来调用,那他们有什么区别呢

从它们的使用上来看,

  • @staticmethod不需要 self 参数(表示自身对象)和 cls参数(表示自身类),就跟使用函数一样。
  • @classmethod也不需要self参数,但第一个参数一定是 cls(表示自身类)。

如果在@staticmethod中要调用到这个类的一些属性或者方法,只能直接类名.属性名类名.方法名()

而@classmethod因为持有cls参数,可以来调用类的属性,类的方法,实例化对象等,避免硬编码。

class A(object):
    bar = 1
    def foo(self):
        print 'foo'
 
    @staticmethod
    def static_foo():
        print 'static_foo'
        print A.bar
 
    @classmethod
    def class_foo(cls):      # 类方法,传入类实例
        print 'class_foo'
        print cls.bar
        cls().foo()

a = A()
a.foo()
# output
# foo
A.foo(a) # 这种调用方式的调用副作用(产生的效果)和 a.foo() 完全相同
# output
# foo

A.static_foo()
# output
# static_foo
# 1

A.class_foo()
# output
# class_foo
# 1
# foo

访问修饰符(Access Modifiers)

在 Python 中,只有 public 和 private 两种可见性(visibility)。

如果你想把一个域的访问级别设置为仅仅能在类中被访问,则将其命名为”__xx“即可,比如:

class Test:

    def __init__(self, foo):
        self.__foo = foo

    def __bar(self):
        print(self.__foo)
        print('__bar')


def main():
    test = Test('hello')
    # AttributeError: 'Test' object has no attribute '__bar'
    test.__bar()
    # AttributeError: 'Test' object has no attribute '__foo'
    print(test.__foo)


if __name__ == "__main__":
    main()

Reference