Magic Method
Magic Method,魔法函数,语法为__method__
,一种Python高级语法,允许用户自定义函数,并绑定到类的特殊方法上,实现用户定制类。
1.构造和初始化
1.1__init__
当类被调用,实例化的第一步是创建对象。一旦对象创建了,Python检查是否实现了__init__
()方法。默认情况下,如果没有定义(或覆盖)特殊方法__init__
(),对实例不会施加任何特别的操作。任何所需的特定操作,都需要程序员实现__init__
(),覆盖它的默认行为。如果__init__
()没有实现,则返回它的对象,实例化过程完毕。如果__init__
()已经被实现,那么它将被调用,实例对象作为第一个参数(self)被传递进去。调用类时,传进的任何参数都交给了__init__
()方法。
对 __init__
()方法的调用发生在实例被创建之后,也就是说系统在执行该方法前,对象已经存在了
class Person:
def __init__(self, name):
print('__init__ called')
self.name = name
Person('Bob')
__init__ called
<__main__.Person at 0x7f39bc6214e0>
1.2 __new__
__new__
方法用于控制实例对象的创建过程,该方法的返回值就是类的实例对象。
class Person:
def __new__(cls, name):
print('__new__ called!')
return super(Person, cls).__new__(cls, name)
def __init__(self, name):
print('__init__ called')
self.name = name
Person('Bob')
__new__ called!
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
in ()
8
9
---> 10 Person('Bob')
in __new__(cls, name)
2 def __new__(cls, name):
3 print('__new__ called!')
----> 4 return super(Person, cls).__new__(cls, name)
5 def __init__(self, name):
6 print('__init__ called')
TypeError: object() takes no parameters
可以看到引发了错误,但是同样的代码在Python2中不会出现,原因为:
(1)在__new__
被overridden或者__init__
没有被overridden 的情况下,如果调用object.__new__
的时候传递了除cls之外的参数将会报错;
(2)在__new__
没有被overridden或者__init__
被overridden 的情况下,如果调用 object.__init__
的时候传递了除cls之外的参数将会报错;
(3)args,和kwds 在object.__new__
除了用来判断报错,并没有什么其它用处
在shuiyutian博客有原因分析。
将上面的代码修改:
class Person:
def __new__(cls, name):
print('__new__ called!')
return super().__new__(cls)
def __init__(self, name):
print('__init__ called')
self.name = name
Person('Bob')
__new__ called!
__init__ called
<__main__.Person at 0x7f42d4361f28>
正常运行!可以看到先调用了__new__
来生成实例,在调用__init__
来初始化实例。
所以,__init__
和 __new__
最主要的区别在于:
__init__
通常用于初始化一个新实例,控制这个初始化的过程,比如添加一些属性, 做一些额外的操作,发生在类实例被创建完以后。它是实例级别的方法。
__new__
通常用于控制生成一个新实例的过程。它是类级别的方法。
class PositiveIntegerInit(int):
def __init__(self, value):
super().__init__(abs(value))
class PositiveIntegerNew(int):
def __new__(cls, value):
return super().__new__(cls, abs(value))
num_init = PositiveIntegerInit(-3)
num_new = PositiveIntegerNew(-3)
print("In PositiveIntegerInit() class return num is : %d" % num_init)
print("In PositiveIntegerNew() class return num is : %d" % num_new)
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
in ()
8
9
---> 10 num_init = PositiveIntegerInit(-3)
11 num_new = PositiveIntegerNew(-3)
12 print("In PositiveIntegerInit() class return num is : %d" % num_init)
in __init__(self, value)
1 class PositiveIntegerInit(int):
2 def __init__(self, value):
----> 3 super().__init__(abs(value))
4
5 class PositiveIntegerNew(int):
TypeError: object.__init__() takes no parameters
不明白为什么出错!!!
class PositiveIntegerInit(int):
def __init__(self, value):
object.__init__(abs(value))
class PositiveIntegerNew(int):
def __new__(cls, value):
return super().__new__(cls, abs(value))
num_init = PositiveIntegerInit(-3)
num_new = PositiveIntegerNew(-3)
print("In PositiveIntegerInit() class return num is : %d" % num_init)
print("In PositiveIntegerNew() class return num is : %d" % num_new)
In PositiveIntegerInit() class return num is : -3
In PositiveIntegerNew() class return num is : 3
class PositiveIntegerInit(int):
def __init__(self, value):
int.__init__(abs(value))
class PositiveIntegerNew(int):
def __new__(cls, value):
return super().__new__(cls, abs(value))
num_init = PositiveIntegerInit(-3)
num_new = PositiveIntegerNew(-3)
print("In PositiveIntegerInit() class return num is : %d" % num_init)
print("In PositiveIntegerNew() class return num is : %d" % num_new)
In PositiveIntegerInit() class return num is : -3
In PositiveIntegerNew() class return num is : 3
class PositiveIntegerInit(int):
def __init__(self, value):
super().__init__()
self.value = abs(value)
class PositiveIntegerNew(int):
def __new__(cls, value):
return super().__new__(cls, abs(value))
num_init = PositiveIntegerInit(-3)
num_new = PositiveIntegerNew(-3)
print("In PositiveIntegerInit() class return num is : %d" % num_init)
print("In PositiveIntegerNew() class return num is : %d" % num_new)
In PositiveIntegerInit() class return num is : -3
In PositiveIntegerNew() class return num is : 3
1.3 __del__
:不推荐使用
这个函数要知道对象的所有引用被清除后才会执行,在对象生命周期调用结束时,__del__
方法会被调用
解构器只能被调用一次,在调用过程中不能忘记调用父类的__del__
class C:
def __init__(self):
print('***********INIT************')
def __del__(self):
print('-----------DELETE-------------')
c = C()
del c
***********INIT************
-----------DELETE-------------
用户不需要手工管理对象,python自带的垃圾回收机制会通过引用计数来判断对象是否可以销毁。当一个对象的引用计数为0时,python解析器会自动销毁该对象,同时调用该对象的__del__
方法
__del__()
方法可能(尽管不推荐!)通过创建对它的新引用来推迟对实例的销毁。然后可以在以后删除该新引用时调用它。不能保证当解释器退出时仍然存在的对象调用 __del__()
方法。
2.访问控制
2.1 __getattr__
__getattr__(self, name)
:
当用户试图访问一个根本不存在(或者暂时不存在)的属性时,你可以通过这个魔法方法来定义类的行为。这个可以用于捕捉错误的拼写并且给出指引,使用废弃属性时给出警告(如果你愿意,仍然可以计算并且返回该属性),以及灵活地处理AttributeError。只有当试图访问不存在的属性时它才会被调用,所以这不能算是一个真正的封装的办法。
注意,只有在没有找到属性的情况下,才调用getattr,已有的属性,比如name,不会在getattr中查找。
class Person():
def __init__(self, name):
self.name = name
def __getattr__(self, attr):
if attr == 'age':
return '没有年龄啊!!!'
else:
return 'No This Attr'
p = Person('zhang')
print(p.name)
print(p.age)
zhang
没有年龄啊!!!
print(p.address)
No This Attr
2.2 __getattribute__
__getattribute__
允许你自定义属性被访问时的行为,如果定义了这个方法,在引用任何的属性和方法的时候都会调用它。
它也同样可能遇到无限递归问题(通过调用基类的 __getattribute__
来避免)。
class GetAB:
def __getattribute__(self, attr):
print('__getattribute__ is called')
if attr == 'Attr':
return 'AB'
super().__getattribute__(attr)
def output(self):
print('OUTPUT_AB')
gab = GetAB()
gab.Attr
__getattribute__ is called
'AB'
gab.output()
__getattribute__ is called
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
in ()
----> 1 gab.output()
TypeError: 'NoneType' object is not callable
不明白为啥出错,下面的却没有错??????
gab.output
__getattribute__ is called
class Rastan:
def __getattribute__(self, key):
raise AttributeError
def swim(self):
pass
hero = Rastan()
hero.sing
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
in ()
----> 1 hero.sing
in __getattribute__(self, key)
1 class Rastan:
2 def __getattribute__(self, key):
----> 3 raise AttributeError
4 def swim(self):
5 pass
AttributeError:
hero.swim()
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
in ()
----> 1 hero.swim()
in __getattribute__(self, key)
1 class Rastan:
2 def __getattribute__(self, key):
----> 3 raise AttributeError
4 def swim(self):
5 pass
AttributeError:
可以看到任何属性或者方法的调用都会经过__getattribute__
,实现这个方法很容易出现Bug。
2.3 __setattr__
__setattr__(self, name, value)
和__getattr__
不同,__setattr__
可以用于真正意义上的封装。它允许你自定义某个属性的赋值行为,不管这个属性存在与否,也就是说你可以对任意属性的任何变化都定义自己的规则。然后,一定要小心使用 __setattr__
class Person:
name = ''
def __setattr__(self, name, value):
print('__setattr__ is called!!!')
super().__setattr__(name, value)
p = Person()
p.name = 'Sunny'
__setattr__ is called!!!
p.age = 1
__setattr__ is called!!!
print(p.name)
print(p.age)
Sunny
1
可见,不论类是否具有属性,都可以赋值。
2.4 __delattr__
__delattr__(self, name)
这个魔法方法和 __setattr__
几乎相同,只不过它是用于处理删除属性时的行为。和__setattr__
一样,使用它时也需要多加小心,防止产生无限递归(在 __delattr__
的实现中调用 del self.name 会导致无限递归)。
3.容器
__len__(self)
返回容器的长度,可变和不可变类型都需要实现。
__getitem__(self, key)
定义对容器中某一项使用 self[key] 的方式进行读取操作时的行为。这也是可变和不可变容器类型都需要实现的一个方法。它应该在键的类型错误式产生 TypeError 异常,同时在没有与键值相匹配的内容时产生 KeyError 异常。
__setitem__(self, key)
定义对容器中某一项使用 self[key] 的方式进行赋值操作时的行为。它是可变容器类型必须实现的一个方法,同样应该在合适的时候产生 KeyError 和 TypeError 异常。
__iter__(self, key)
它应该返回当前容器的一个迭代器。迭代器以一连串内容的形式返回,最常见的是使用 iter() 函数调用,以及在类似 for x in container: 的循环中被调用。迭代器是他们自己的对象,需要定义__iter__
方法并在其中返回自己。
__reversed__(self)
定义了对容器使用 reversed() 内建函数时的行为。它应该返回一个反转之后的序列。当你的序列类是有序时,类似列表和元组,再实现这个方法,
__contains__(self, item)
__contains__
定义了使用 in 和 not in 进行成员测试时类的行为。你可能好奇为什么这个方法不是序列协议的一部分,原因是,如果 __contains__
没有定义,Python就会迭代整个序列,如果找到了需要的一项就返回 True 。
__missing__(self ,key)
__missing__
在字典的子类中使用,它定义了当试图访问一个字典中不存在的键时的行为(目前为止是指字典的实例,例如我有一个字典 d , “george” 不是字典中的一个键,当试图访问 d[“george’] 时就会调用 d.__missing__(“george”) )
。
class FunctionalList:
'''一个列表的封装类,实现了一些额外的函数式
方法,例如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 reversed(self.values)
def append(self, value):
self.values.append(value)
def head(self):
# 取得第一个元素
return self.values[0]
def tail(self):
# 取得除第一个元素外的所有元素
return self.valuse[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]
4.可调用对象
__call__
可以将类作为像函数调用
class Fib(object):
def __call__(self, num):
a, b, fib_list = 0, 1, []
for i in range(num):
fib_list.append(a)
a, b = b, a + b
return fib_list
f = Fib()
print(f(20))
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597, 2584, 4181]
print(f(10))
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
5.__slots__
__slots__
的目的是限制当前类所能拥有的属性,如果不需要添加任意动态的属性,使用__slots__
也能节省内存。
class Person(object):
__slots__ = ('name', 'gender')
def __init__(self, name, gender, score):
self.name = name
self.gender = gender
self.score = score
#__slots__定义的属性仅对当前类起作用,对继承的子类是不起作用的。除非在子类中也定义__slots__,
#子类允许定义的属性就是自身的__slots__加上父类的__slots__
class Student(Person):
__slots__ = ('name', 'gender', 'score')
def __init__(self, name, gender, score):
self.name = name
self.gender = gender
self.score = score
p = Person('Sunny', 'male', 90)
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
in ()
----> 1 p = Person('Sunny', 'male', 90)
in __init__(self, name, gender, score)
6 self.name = name
7 self.gender = gender
----> 8 self.score = score
9
10 #__slots__定义的属性仅对当前类起作用,对继承的子类是不起作用的。除非在子类中也定义__slots__,
AttributeError: 'Person' object has no attribute 'score'
s = Student('Sunny', 'male', 90)
print(s.name)
print(s.gender)
print(s.score)
Sunny
male
90
阅读
- Special Method Names
- Python Magic Methods
- Python 魔术方法 - Magic Method
- Python进阶:实例讲解Python中的魔法函数(Magic Methods)
- 简述
__init__、__new__、__call__
方法 - (译)Python魔法方法指南