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. 类属性和实例属性之间的访问顺序

因为当调用实例的属性的时候,经过的内容比较复杂,需要考虑(描述符等内容),这里不考虑描述符等内容,可以将搜索过程概括如下:

  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. 类方法、类实例方法、静态方法

  1. 类方法: 是类对象的方法,在定义时需要在上方使用 @classmethod 进行装饰,形参为cls,表示类对象,类对象和实例对象都可调用
  2. 类实例方法: 是类实例化对象的方法,只有实例对象可以调用,形参为self,指代对象本身;
  3. 静态方法: 是一个任意函数,在其上方使用 @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)

参考here.参考here

python中用过3中不同的MRO算法,分别是:经典类所用的算法,Python2.2中的新式类使用的算法,以及Python2.3中的新式类使用的算法(C3算法)。在Python3中,由于Python3.x仅支持新式类,所以该版本只使用C3算法。

  1. 经典类使用的算法比较简单。
    如下这种类的继承关系,经典类的搜索方案是:从左至右的深度优先搜索方案,所以搜索的顺序是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())
  2. 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被继承时,它本身的搜索顺序发生了变化,违反了单调性原则。

  1. 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就是一个只读的属性了。

2.2. 描述符