这篇文章和大家一起聊一聊 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
(False, False)
>>> Employee.outsource = True
>>> 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
>>> 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__() 将调用的函数绑定成方法的。