阅读 37

走进 Python 类的内部

这篇文章和大家一起聊一聊 Python中类和对象背后的一些概念和实现原理,
主要尝试解释 Python 类和对象属性的存储,函数和方法,描述器,对象内存占用的优化支持,以及继承与属性查找等相关问题。

让我们从一个简单的例子开始:
class Employee:  # 关于员工的一个类
    outsource = False  # 该类属性是字符串,是不可变的类型,所以下面在实例对象层面更改类属性的时候,是不能更改的

    def __init__(self, department, name):  # 初始化函数,每一个员工都有部门和姓名
        self.department = department
        self.name = name

    @property
    def inservice(self):
        return self.department is not None

    def __repr__(self):
        return f"<Employee: {self.department}-{self.name}>"

employee = Employee('研发线-效率工程部', 'houzhen03')
employee 对象是 Employee 类的一个实例,它有两个属性 department 和 name,这2个属性属于该实例化对象
outsource 是类属性,所有者是类,该类的所有实例对象共享此属性值,这跟其他面向对象语言一致。


更改类变量会影响到该类的所有实例对象:
    >>> e1 = Employee('IT', 'bobo')
    >>> e2 = Employee('HR', 'cici')
    >>> e1.outsource, e2.outsource  # 本来类的outsource属性值是False
    (False, False)
    >>> Employee.outsource = True  # 现在把类的属性outsource改成True,则该类的2个实例化对象在调用该属性时,也发生了变化
    >>> e1.outsource, e2.outsource
    >>> (True, True)
    
    
这仅限于从类更改,当我们从实例更改类变量时:
    >>> e1 = Employee('研发线-效率工程部', 'houzhen03')
    >>> e2 = Employee('研发线-效率工程部', 'zhangwei06')
    >>> e1.outsource, e2.outsource  
    (False, False)
    >>> e1.outsource = True  # 在实例层面更改类属性,则只会影响该实例,不会影响别的实例
    >>> e1.outsource, e2.outsource
    (True, False)
    
是的,当你试图从实例对象层面去修改类属性时,Python 不会更改该类的属性值,而是去创建一个同名的实例属性,这是非常正确且安全的。
在访问属性时,实例属性会优先于类属性,也就是说先去找该实例是否有这个属性,如果没有再去找类的属性
这将在继承与属性查找一节中详细解释。

值得特别注意的是,当类属性的类型是可变类型时,你是可以在实例对象层面更改它们的:
    class HOUZHEN:
        country = "中国" # 不可变类型,字符串是不可变类型
        temp_list = [1,2,3]  # 可变类型

        def __init__(self,provence=None,city=None,name=None):
            self.provence = provence
            self.city = city
            self.name = name

        def eat(self):
            print("我爱吃香蕉")

        houzhen = HOUZHEN("山东省","临沂市","侯振")
        print(houzhen.temp_list)
        houzhen.temp_list.append(444)  # 在实例对象层面修改可变的类属性,则会影响其他的实例对象
        print(houzhen.temp_list)

        sun = HOUZHEN("山东省","青岛市","sun")
        print(sun.temp_list)

######################################################################
######################################################################
######################################################################

<1> 实例属性:
    在 Python 中,所有实例属性都存储在 __dict__ 字典中,这就是一个常规的 dict,对于实例属性的维护即是从该字典中获取和修改,它对开发者是完全开放的。
    >>> e = Employee('IT', 'bobo')  # 初始化一个实例对象
    >>> e.__dict__
        {'department': 'IT', 'name': 'bobo'}
    >>> type(e.__dict__)
        dict
    >>> e.name is e.__dict__['name']
        True
    >>> e.__dict__['department'] = 'HR'
    >>> e.department
        'HR'
    
    正因为实例属性是采用字典来存储,所以任何时候我们都可以方便的给对象添加或删除字段:
    >>> e.age = 30 # 并没有定义 age 属性
    >>> e.age
        30
    >>> e.__dict__
        {'department': 'IT', 'name': 'bobo', 'age': 30}
    >>> del e.age
    >>> e.__dict__
        {'department': 'IT', 'name': 'd'}
        
<2> 类属性
       同样的,类属性也在存储在类的 __dict__ 字典中:
       与实例字典的『开放』不同,类属性使用的字典是一个 MappingProxyType 对象,它是一个不能 setattr 的字典。
       这意味着它对开发者是只读的,其目的正是为了保证类属性的键都是字符串,以简化和加快新型类属性的查找和 __mro__ 的搜索逻辑。

       >>> Employee.__dict__
           mappingproxy({'__module__': '__main__',
                      'outsource': True,
                      '__init__': <function __main__.Employee.__init__(self, department, name)>,
                      'inservice': <property at 0x108419ea0>,
                      '__repr__': <function __main__.Employee.__repr__(self)>,
                      '__str__': <function __main__.Employee.__str__(self)>,
                      '__dict__': <attribute '__dict__' of 'Employee' objects>,
                      '__weakref__': <attribute '__weakref__' of 'Employee' objects>,
                      '__doc__': None}

       >>> type(Employee.__dict__)
           mappingproxy
           
<3> 继承与属性查找
     一个大原则:自己有,就用自己的,如果自己没有,就去用所继承的
     目前为止,我们已经知道,所有的属性和方法都存储在两个 __dict__ 字典中,现在我们来看看 Python 是如何进行属性查找的。
     Python 3 中,所有类都隐式的继承自 object,所以总会有一个继承关系,而且 Python 是支持多继承的:
        >>> class A:
        ...     pass
        ...
        >>> class B:
        ...     pass
        ...
        >>> class C(B):
        ...     pass
        ...
        >>> class D(A, C):  # 多继承
        ...     pass
        ...
        >>> D.mro()
        [<class '__main__.D'>, <class '__main__.A'>, <class '__main__.C'>, <class '__main__.B'>, <class 'object'>]
        
<4> 函数与方法
     我们知道方法是属于特定类的函数,唯一的不同(如果可以算是不同的话)是方法的第一个参数往往是为类或实例对象保留的,
     在 Python 中,我们约定为 cls 或 self, 当然你也可以取任何名字如 this(只是最好不要这样做)。
     
     上一节我们知道,函数实现了 __get__() 方法的对象,所以它们是非数据描述器。
     在 Python 访问(调用)方法支持中正是通过调用 __get__() 将调用的函数绑定成方法的。
复制代码
文章分类
阅读
文章标签