Python类
python3 类的知识整理。
[TOC]
1. 基础知识
1.1. 类变量和实例变量(属性)
类属性在类内定义(一般在类的开始),在init函数外面。一般是类所共有的属性定义为类属性。
在类内部用类名.类属性名调用,外部既可以用类名.类属性名又可以用实例名.类属性名来调用。
实例属性一般在类中的函数中定义,实例属性可能为某个实例独有。内部调用时为self.实例属性名,外部调用时用实例名.实例属性名。here
类变量就是在类内定义的,但是不在方法内定义的,而且前缀无self作为引用。实例变量就是有self作为引用的存在类中的变量。类变量是所有对象共享的,在类中修改时,其他的对象也会跟着变。但是需要注意的是,如果是用对象来引用类变量进行修改的话,这里只是新建了和类变量同名的实例变量,并没有修改类变量。
class Student(object): conutry = 'China' # 这个是类变量 def __init__(self, name, sex): self.name = name # 这个是实例变量,也就是对象变量 self.sex = sex # 对象变量 s1 = Student('张三', 'man') s2 = Student('里斯', 'woman') print(s1.conutry) print(s2.conutry) print(Student.conutry) Student.conutry = 'cn' # 这个是用类引用来进行修改 print(s1.conutry) print(s2.conutry) print(Student.conutry) s1.conutry = 'zhongguo' # 用实例来引用进行修改 print(s1.conutry) print(s2.conutry) print(Student.conutry)
out:
China China China cn cn cn zhongguo cn cn
使用vars查看对象的属性信息
print(vars(s1)) print(vars(s2)) print(vars(Student))
out
{'name': '张三', 'sex': 'man', 'conutry': 'zhongguo'} {'name': '里斯', 'sex': 'woman'} {'__module__': '__main__', 'conutry': 'cn', '__init__': <function Student.__init__ at 0x000001BDDF4C7D90>, '__dict__': <attribute '__dict__' of 'Student' objects>, '__weakref__': <attribute '__weakref__' of 'Student' objects>, '__doc__': None}
1.2. 类属性和实例属性之间的访问顺序
因为当调用实例的属性的时候,经过的内容比较复杂,需要考虑(描述符等内容),这里不考虑描述符等内容,可以将搜索过程概括如下:
- 首先,查找这个对象是不是包含这个属性,如果没有查找这个类是否包含这个属性。
- 以类的继承顺序查找继承的类中是否包含这个类属性,如果存在就返回。
class B(): female = "sex" def __init__(self): self.scores = 100 class A(B): name = 'A' age = 20 def __init__(self): self.name = 'a' a = A() print(a.name) print(a.female) # 输出sex # print(a.scores) # 错误
1.3. 类方法、类实例方法、静态方法
- 类方法: 是类对象的方法,在定义时需要在上方使用 @classmethod 进行装饰,形参为cls,表示类对象,类对象和实例对象都可调用
- 类实例方法: 是类实例化对象的方法,只有实例对象可以调用,形参为self,指代对象本身;
- 静态方法: 是一个任意函数,在其上方使用 @staticmethod 进行装饰,可以用对象直接调用,静态方法实际上跟该类没有太大关系。
普通实例方法,第一个参数需要是self,它表示一个具体的实例本身。
而对于classmethod,它的第一个参数不是self,是cls,它表示这个类本身。
class Foo(object): def instance_method(self): print("实例方法") @staticmethod def static_method(): print("是静态方法") @classmethod def class_method(cls): print("是类方法") foo = Foo() foo.instance_method() foo.static_method() foo.class_method() print('----------------') Foo.static_method() Foo.class_method()
静态方法和类方法可以通过对象或者类名直接调用。
1.4. 类的多继承和查找顺序
1.4.1. 方法解析顺序(Method Resolution Order, 简称MRO)
python中用过3中不同的MRO算法,分别是:经典类所用的算法,Python2.2中的新式类使用的算法,以及Python2.3中的新式类使用的算法(C3算法)。在Python3中,由于Python3.x仅支持新式类,所以该版本只使用C3算法。
经典类使用的算法比较简单。
如下这种类的继承关系,经典类的搜索方案是:从左至右的深度优先搜索方案,所以搜索的顺序是D->B->A->C->A.显然,根据这种搜索方法,得到的是A中的method方法,而我们想要的是C中的方法。class A: def method(self): print("CommonA") class B(A): pass class C(A): def method(self): print("CommonC") class D(B, C): pass print(D().method())
python2.2中新式类使用的算法。
为了解决上面的问题,这种搜索方案为:依然采用从左至右(从左至右的意思是当继承多个类的时候的顺序,比如下面例子中A继承X和Y类,X在前,Y在后)的深度优先遍历,但是如果遍历中存在重复的类,只保留最后一个。因此上面的搜索顺序从D->B->A->C->A变为了D->B->C->A。但是又产生了新的问题,比如下面的例子:class X(object): pass class Y(object): pass class A(X,Y): pass class B(Y,X): pass class C(A, B): pass
使用这种方案的搜索顺序为C->A->X->object->Y->B->Y->object->X->object,简化后C->A->B->Y->X->object.Python 2.2 在实现该方法的时候进行了调整,使其更尊重基类中类出现的顺序,其实际结果为 C->A->B->X->Y->object
分析每个类的方法搜索顺序。
- 对于A,其搜索顺序为A->X->Y->object
- 对于B,其搜索顺序为B->Y->X->object
- 对于C,其搜索顺序为C->A->B->X->Y->object
可以看到B,C中X,Y的搜索顺序是相反的。当B被继承时,它本身的搜索顺序发生了变化,违反了单调性原则。
- C3算法-解决单调性问题
这里here给出了详细的计算方法,通过这种方法可以得到具有单调性的方法解析顺序。
1.4.2. mro查看继承类的访问顺序
参考here
主要有两种查找顺序,一种是深度查找,一种是广度查找,使用哪种查找结构取决于类继承的结构。
""" 主要说明了C3算***自适应的根据继承树的结构选择合适的查找顺序。 """ # 适合深度优先查找的。 class E(): pass class D(): pass class C(E): pass class B(D): pass class A(B, C): pass # 具体的继承结构图可以自己动手画一下 # __mro__属性是获取属性的查找顺序。 print(A.__mro__) # (<class '__main__.A'>, <class '__main__.B'>, <class '__main__.D'>, <class '__main__.C'>, <class '__main__.E'>, <class 'object'>)
# 适合广度优先查找的继承结构 class D(): pass class C(D): pass class B(D): pass class A(B, C): pass print(A.__mro__) # (<class '__main__.A'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.D'>, <class 'object'>)
1.5. 常用方法属性
1.5.1. vars()函数返回对象object的属性和属性值的字典对象。
vars([object])就是返回对象__dict__的内容,无论是类对象还是实例对象,vars([object]) == object.__dict__。
print(vars(s1))
out: {'name': '张三', 'sex': 'man', 'conutry': 'zhongguo'}
1.5.2. dict
这个属性的内容和vars得到的结果是一样的。但是内建类型对象中是不存在这个属性的。内建对象访问会出现AttributeError错误。
li = [1,2] # 下面这两个都会报错 li.__dict__ vars(li)
1.5.3. dir()
查看实例对象的所有属性和方法。
dir(instance_object)
2. 高级知识
2.1. @property
参考here
2.1.1. 背景知识
在绑定属性的时候,如果直接将属性暴露出去,虽然写起来简单,但是,没有办法检查参数设置的是否合理,因此可能出现随意更改数据的情况,这显然不合逻辑,因此最开始通过自己定义set和get方法来设置和获取属性,如下所示:
class Student: def get_score(self): return self._score def set_score(self, value): if not isinstance(value, int): raise ValueError('Score must be int type') if value < 0 or value > 100: raise ValueError('Score must between 1,100') self._score = value s = Student() s.set_score(100) s.get_score()
通过同时设置set和get之后就可以随意设置score而不用担心逻辑错误了。但是这样写很麻烦,没有直接使用属性那么简单。
2.1.2. 使用@property
@property装饰器是python内置的,负责把一个方法变成属性来调用。
class Student: @property def score(self): return self._score @score.setter def score(self, value): if not isinstance(value, int): raise ValueError('Score must be int type') if value < 0 or value > 100: raise ValueError('Score must between 1,100') self._score = value s = Student() s.score = 100 s.score
此时,@property将score方法变为属性,然后创建score.setter装饰器,负责score的setter方法变成属性赋值,此时调用起来比较简单。如果只定义@property装饰器,此时相当于score就是一个只读的属性了。