Python-cookbook之类与对象(1)
创建大量对象时节省内存方法
问题:你的程序要创建大量 可能上百万 的对象,导致占用很大的内存
对于主要是用来当成简单的数据结构的类而言,你可以通过给类添加__slots__
属性来极大的减少实例所占的内存,比如:
class Date:
__slots__ = ['year','month','day']
def __init__(self, year, month, day):
self.year = year
self.month = month
self.day = day
- 当你定义
__slots__
后,Python就会为实例使用一种更加紧凑的内容表示。实例通过一个很小的固定大小的数组来构建,而不是为每个实例定义一个字典,这跟元组或列表很类似。在__slots__
中列出的属性名在内部被映射到这个数组的指定小标上。使用__slots__
一个不好的地方就是我们不能再给实例添加新的属性了,只能使用在__slots__
中定义的那些属性名。好处就是内存占用跟一个元组差不多了,省内存。
讨论
尽管__slots__
看上去是一个很有用的特性,很多时候你还是得减少对它的使用冲动。Python的很多特性都依赖于普通的基于字典的实现,用了__slots__
的实例就不用调用__dict__
属性了。另外,定义__slots__
后的类不再支持一些普通类特性了,比如多继承。大多数情况下,你应该只在那些经常被使用到的用作数据结构的类上定义__slots__
,比如在程序中需要创建某个类的几百万个实例对象 。
关于__slots__
的一个常见误区是它可以作为一个封装工具来防止用户给实例增加新的属性。尽管可以达到这样的目的,但是这个并不是它的初衷。__slots__
更多的是用来作为一个内存优化工具。
创建可管理的属性
问题:你想给某个实例attribute增加除访问与修改之外的其他处理逻辑,比如类型检查或合法性验证。
class Person:
def __init__(self, first_name):
self.first_name = first_name
# Getter function
@property
def first_name(self):
return self._first_name
# Setter function
@first_name.setter
def first_name(self, value):
if not isinstance(value, str):
raise TypeError('Expected a string')
self._first_name = value
# Deleter function(optional)
@first_name.deleter
def first_name(self):
raise AttributeError('Can not delete attribute')
- 上述代码中有三个相关联的方法,这三个方法的名字都必须一样。第一个方法是一 个getter函数,它使得first_name成为一个属性。其他两个方法给first_name属性添加了setter和deleter函数。需要强调的是只有在first_name属性被创建后,后面的两个装饰器@first_name.setter和@first_name.deleter才能被定义。
- property的关键特征就是它看上去跟普通attribute没什么两样,但是访问这个property时会自动触发相应的setter,getter, deleter方法
讨论
一个property属性其实就是一系列相关绑定方法的集合。如果你去查看拥有property的类,就会发现property本身的fget、fset和fdel 属性就是类里面的普通方法。
eg:
In [148]: Person.first_name.fset
Out[148]: <function main.Person.first_name(self, value)>
In [149]: Person.first_name.fget
Out[149]: <function main.Person.first_name(self)>
class Person:
def __init__(self, first_name):
self.set_first_name(first_name)
# Getter function
def get_first_name(self):
return self._first_name
# Setter function
def set_first_name(self, value):
if not isinstance(value, str):
raise TypeError('Expected a string')
self._first_name = value
# Deleter function(optional)
def del_first_name(self):
raise AttributeError('Can not delete attribute')
# Make a property from existing get/set/del methods
name = property(get_first_name, set_first_name, del_first_name())
- properties还是一种定义动态计算attribute的方法。这种类型的attribute并不会 被实际的存储,而是在需要的时候计算出来。
调用父类的方法
问题:你想在子类中调用父类的某个已经被覆盖的方法。
- 为了调用父类 超类 的一个方法,可以使用super()函数,比如:
class A:
def spam(self):
print('A.spam')
class B(A):
def spam(self):
print('B.spam')
super().spam() # call parent spam()
- super()函数的一个常见用法是在
__init__()
方法中确保父类被正确的初始化了:
class A:
def __init__(self):
self.x = 0
class B(A):
def __init__(self):
super().__init__()
self.y = 1
- super()的另外一个常见用法出现在覆盖python特殊方法的代码中,比如:
class Proxy:
def __init__(self, obj):
self._obj = obj
# Delegate attribute look up to internal obj
def __getattr__(self, name):
return getattr(self._obj, name)
# Delegate attribute assignment
def __setattr__(self, name, value):
if name.startswith('_'):
super().__setattr__(name, value)
else:
setattr(self._obj, name, value)
在上面代码中,
__setattr__()
的实现包含一个名字检查。如果某个属性名以下划线_开头,就通过super()
调用原始的__setattr__()
,否则的话就委派给内部的 代理对象self.obj
去处理。这看上去有点意思,因为就算没有显式的指明某个类的父类(个人认为这肯定是调用了默认基类object的方法了,其实还是有父类的),super()
仍然可以有效的工作。
-
上述例子中,B中也可以直接使用父类名来调用如
A.__init__(self)
但这样涉及复杂多继承情况就会有可能重复执行父类方法了(eg: A(Base), B(Base), C(A, B)这样的情况下C的init里同时调用了A和B的同个方法,A、B这个方法又用了 Base来直接调用Base方法) -
当你使用super()函数时, Python会在
MRO
列表(C3线性化算法来实现的)上继续搜索下一个类。只要每个重定义的方法统一使用super()
并只调用它一次,那么控制流最终会遍历完整个MRO
列表,每个方法也只会被调用一次。这也是为什么在第二个例子中你不会调用两次Base.__init__()
的原因。
class Base:
def __init__(self):
print('Base.__init__')
class A(Base):
def __init__(self):
super().__init__()
print('A.__init__')
class B(Base):
def __init__(self):
super().__init__()
print('B.__init__')
class C(A, B):
def __init__(self):
super().__init__() # Only one call to super() here
print('C.__init__')
C.mro()
# [<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>,
# <class '__main__.Base'>, <class 'object'>]
c = C()
# Base.__init__
# B.__init__
# A.__init__
# C.__init__