Python魔术方法
在Python中,所有以__双下划线包起来的方法,都统称为"魔术方法"。
魔术方法
1. new, init, del, 垃圾回收机制
当我们调用x = SomeClass()的时候,首先调用new来创建类并返回这个类的实例, 接着调用init将传入的参数来初始化该实例。new和init共同构成了"构造函数".
__new__在创建一个实例的过程中必定会被调用,但__init__就不一定,比如通过pickle.load的方式反序列化一个实例时就不会调用__init__。 __new__方法总是需要返回该类的一个实例,而__init__不能返回除了None的任何值。
在对象的生命周期结束时, del会被调用,可以将del理解为"析构函数"。del定义的是当一个对象进行垃圾回收时候的行为。
1.1. new, init
参考here
# class A(object): python2 必须显示地继承object class A: def __new__(cls): print("__new__ ") self = super(A, cls).__new__(cls) print(self) return self def __init__(self): print("__init__ ") print(self) super(A, self).__init__() def __call__(self): # 可以定义任意参数 print('__call__ ') a = A()
__new__ <__main__.A object at 0x000001A4A0595D48> __init__ <__main__.A object at 0x000001A4A0595D48>
从实验结果可以分析得到,new方法的返回值就是类的实例对象self,然后传递到init方法中用于初始化实例对象。如果new方法中不返回值,那么init方法就不会被调用,因为实例对象都没有创建出来,就不会调用init方法。
python中垃圾回收机制
无论是手动销毁,还是 Python 自动帮我们销毁,都会调用 del() 方法
Python 采用自动引用计数(简称 ARC)的方式实现垃圾回收机制。该方法的核心思想是:每个 Python 对象都会配置一个计数器,初始 Python 实例对象的计数器值都为 0,如果有变量引用该实例对象,实例对象计数器的值会加 1,依次类推;反之,每当一个变量取消对该实例对象的引用,计数器会减 1。如果一个 Python 对象的的计数器值为 0,则表明没有变量引用该 Python 对象,即证明程序不再需要它,此时 Python 就会自动调用 del() 方法将其回收。
class CLanguage: def __init__(self): print("调用 __init__() 方法构造对象") def __del__(self): print("调用__del__() 销毁对象,释放其空间") clangs = CLanguage() # 添加一个引用clangs对象的实例对象 cl = clangs del clangs print("***********")
out
调用 __init__() 方法构造对象 *********** 调用__del__() 销毁对象,释放其空间
以上面程序中的 clangs 为例,实际上构建 clangs 实例对象的过程分为 2 步,先使用 CLanguage() 调用该类中的 init() 方法构造出一个该类的对象(将其称为 C,计数器为 0),并立即用 clangs 这个变量作为所建实例对象的引用( C 的计数器值 + 1)。在此基础上,又有一个 clang 变量引用 clangs(其实相当于引用 CLanguage(),此时 C 的计数器再 +1 ),这时如果调用del clangs语句,只会导致 C 的计数器减 1(值变为 1),因为 C 的计数器值不为 0,因此 C 不会被销毁(不会执行 del() 方法)。而在程序的最后,也就是程序执行完毕之后,此时会自动回收变量,才会执行del()方法。
对于计数器为 0 的实例对象,Python 会自动将其视为垃圾进行回收。
下面的例子是手动调用del,此时当del的对象引用为0的时候,会执行del方法。
class CLanguage: def __init__(self): print("调用 __init__() 方法构造对象") def __del__(self): print("调用__del__() 销毁对象,释放其空间") clangs = CLanguage() # 添加一个引用clangs对象的实例对象 cl = clangs del clangs print("***********") del cl print("-------")
out
调用 __init__() 方法构造对象 *********** 调用__del__() 销毁对象,释放其空间 -------
1.2. new实现单例模式
new方法用于创建对象,是一个工厂函数,专用于生产实例对象。如果想要设计单利模式,可以使用下面的代码。
class singleton: _singleton = None def __new__(cls, *args, **kwargs): if not cls._singleton: # 将_singleton对象绑定在singleton这个类上面,因此就可以创建出单个实例。 cls._singleton = super().__new__(cls, *args, **kwargs) return cls._singleton def __init__(self): pass sing = singleton() sing2 = singleton() id(sing) == id(sing2)
2. call
首先,了解一个概念可调用对象,自定义的函数,内置函数和类都属于可调用对象,但凡是可以把一对括号应用到某个对象身上都可称为可调用对象,可以使用callable来判断。
# class A(object): python2 必须显示地继承object class A: def __init__(self): pass def __call__(self): # 可以定义任意参数 print('__call__ ') a = A() callable(a) # True
如果在类中实现了call方法,那么实例对象也将成为一个可调用对象。
此时可以使用a()来调用可调用对象。
a() # 此时就会执行__call__方法,输出__call__
2.1. 一种应用场景-基于类的装饰器
类可以记录数据,而函数不行,因此可以在函数上增加一个类装饰器,从而使用类来帮助函数记录东西
class Counter: def __init__(self, func): self.func = func self.count = 0 def __call__(self, *args, **kwargs): self.count += 1 return self.func(*args, **kwargs) @Counter # foo = Counter(foo),此时的foo相当于类Counter的实例。 def foo(): pass for i in range(5): foo() # 调用实例对象,此时会执行__call__()方法。 print(foo.count)
上面的例子中可以看出结果中记录了函数的执行次数。
3. 2.getattr, setattr, delattr,getattribute 属性访问
- getattr(self, name)该方法定义了你试图访问一个不存在的属性时的行为。因此,重载该方法可以实现捕获错误拼写然后进行重定向, 或者对一些废弃的属性进行警告。
- setattr(self, name, value) 是实现封装的解决方案,它定义了你对属性进行赋值和修改操作时的行为。
不管对象的某个属性是否存在,它都允许你为该属性进行赋值,因此你可以为属性的值进行自定义操作。有一点需要注意,实现setattr时要避免"无限递归"的错误 - delattr(self, name)与setattr很像,只是它定义的是你删除属性时的行为。实现delattr是同时要避免"无限递归"的错误。
- getattribute(self, name)定义了你的属性被访问时的行为,相比较,getattr只有该属性不存在时才会起作用。因此,在支持getattribute的Python版本,调用getattr前必定会调用 getattribute。getattribute同样要避免"无限递归"的错误。需要提醒的是,最好不要尝试去实现getattribute,因为很少见到这种做法,而且很容易出bug。
无限递归:当采用self.name赋值的时候,会调用setattr方法,然后又出现self.name,从而产生无限递归的效果。
def __setattr__(self, name, value): self.name = value # 每一次属性赋值时, __setattr__都会被调用,因此不断调用自身导致无限递归了。
正确写法
def __setattr__(self, name, value): self.__dict__[name] = value
class Access(object): def __getattr__(self, name): print('__getattr__') return super().__getattr__(name) def __setattr__(self, name, value): print('__setattr__') return super().__setattr__(name, value) def __delattr__(self, name): print('__delattr__') return super().__delattr__(name) def __getattribute__(self, name): print('__getattribute__') return super().__getattribute__(name) access = Access() access.attr1 = True # __setattr__调用 access.attr1 # 属性存在,只有__getattribute__调用 try: access.attr2 # 属性不存在, 先调用__getattribute__, 后调用__getattr__ except AttributeError: pass del access.attr1 # __delattr__调用
out
__setattr__ __getattribute__ __getattribute__ __getattr__ __delattr__
4. 描述器 get, set, delete
一个类中定义get,set ,delete中的一个或者多个的时候,此时该类的实例就可以称为是描述器。 描述器是@property,super,静态方法,类方法等的背后的实现机制。
现在我们定义一个类来表示距离,它有两个属性: 米和英尺。
class Meter(object): '''Descriptor for a meter.''' def __init__(self, value=0.0): self.value = float(value) def __get__(self, instance, owner): return self.value def __set__(self, instance, value): self.value = float(value) class Foot(object): '''Descriptor for a foot.''' def __get__(self, instance, owner): return instance.meter * 3.2808 def __set__(self, instance, value): instance.meter = float(value) / 3.2808 class Distance(object): meter = Meter() foot = Foot() d = Distance() print(d.meter, d.foot) # 0.0, 0.0 d.meter = 1 print(d.meter, d.foot) # 1.0 3.2808 d.meter = 2 print(d.meter, d.foot) # 2.0 6.5616
在上面例子中,在还没有对Distance的实例赋值前, 我们认为meter和foot应该是各自类的实例对象, 但是输出却是数值。这是因为get发挥了作用. 我们只是修改了meter,并且将其赋值成为int,但foot也修改了。这是set发挥了作用.
描述器(meter、foot)不能独立存在, 它需要被另一个所有者类(Distance)所持有。
4.1. 描述器对象
上面的例子中,meter,foot为描述器对象,Distance为拥有者。描述器对象可以访问到其拥有者实例的属性,比如例子中Foot的instance.meter(这里的instance就是Distance的实例。)
一个类要成为描述器,必须实现 get,set, delete 中的至少一个方法。下面简单介绍下:
__get__(self, instance, owner) print(isinstance(instance, owner)) True 参数instance是拥有者类的实例,参数owner是拥有者类本身。__get__在其拥有者对其读值的时候调用。就上面的例子而言,owner表示Distance这个类, instance相当于Distance类的实例对象。 __set__(self, instance, value)在其拥有者对其进行修改值的时候调用。 __delete__(self, instance)在其拥有者对其进行删除的时候调用
5. 构造自定义容器
在Python中,常见的容器类型有: dict, tuple, list, string。
其中tuple, string是不可变容器,dict, list是可变容器。
可变容器和不可变容器的区别在于,不可变容器一旦赋值后,不可对其中的某个元素进行修改。
比如定义了l = [1, 2, 3]和t = (1, 2, 3)后, 执行l[0] = 0是可以的,但执行t[0] = 0则会报错。
如果我们要自定义一些数据结构,使之能够跟以上的容器类型表现一样,那就需要去实现某些协议。这里的协议跟其他语言中所谓的"接口"概念很像,一样的需要你去实现才行,只不过没那么正式而已。
5.1. 方法
len(self)需要返回数值类型,以表示容器的长度。该方法在可变容器和不可变容器中必须实现。
getitem(self, key)当你执行self[key]的时候,调用的就是该方法。该方法在可变容器和不可变容器中也都必须实现。
调用的时候,如果key的类型错误,该方法应该抛出TypeError;
如果没法返回key对应的数值时,该方法应该抛出ValueError。
setitem(self, key, value)当你执行self[key] = value时,调用的是该方法。
delitem(self, key)当你执行del self[key]的时候,调用的是该方法。
iter(self)该方法需要返回一个迭代器(iterator)。当你执行for x in container: 或者使用iter(container)时,该方法被调用。
reversed(self)如果想要该数据结构被內建函数reversed()支持,就还需要实现该方法。
contains(self, item)如果定义了该方法,那么在执行item in container 或者 item not in container时该方法就会被调用。
如果没有定义,那么Python会迭代容器中的元素来一个一个比较,从而决定返回True或者False。
missing(self, key)dict字典类型会有该方法,它定义了key如果在容器中找不到时触发的行为。
比如d = {'a': 1}, 当你执行d[notexist]时,d.missing('notexist')就会被调用。
5.2. 自定义不可变容器
如果要自定义不可变容器类型,只需要定义len 和 getitem方法;
# 自定义容器 class Function: def __init__(self, values=None): if values: self.values = values else: self.values = [] def __len__(self): return len(self.values) def __getitem__(self, key): return self.values[key] f = Function([1,2,3]) f[0]
5.3. 自定义可变容器
如果要自定义可变容器类型,还需要在不可变容器类型的基础上增加定义setitem 和 delitem。
# 自定义容器 class Function: def __init__(self, values=None): if values: self.values = values else: self.values = [] def __len__(self): return len(self.values) def __getitem__(self, key): return self.values[key] def __setitem__(self, key, value): self.values[key] = value def __delitem__(self, key): del self.values[key] f = Function([1,2,3]) f[0] = 2 del f[0] f[0]
5.4. 一个比较复杂的例子
# -*- coding: utf-8 -*- class FunctionalList: ''' 实现了内置类型list的功能,并丰富了一些其他方法: head, tail, init, last, drop, take''' def __init__(self, values=None): if values is None: self.values = [] else: self.values = values def __len__(self): return len(self.values) def __getitem__(self, key): return self.values[key] def __setitem__(self, key, value): self.values[key] = value def __delitem__(self, key): del self.values[key] def __iter__(self): return iter(self.values) def __reversed__(self): return FunctionalList(reversed(self.values)) def append(self, value): self.values.append(value) def head(self): # 获取第一个元素 return self.values[0] def tail(self): # 获取第一个元素之后的所有元素 return self.values[1:] def init(self): # 获取最后一个元素之前的所有元素 return self.values[:-1] def last(self): # 获取最后一个元素 return self.values[-1] def drop(self, n): # 获取所有元素,除了前N个 return self.values[n:] def take(self, n): # 获取前N个元素 return self.values[:n]