python 中有许多魔法函数(Magic Methods),一般格式为 __func__,常见的有 __init__, __del__, __call__, __str__, __repr__, __len__, __enter__, __exit__, __iter__, __next__, __dict__, __dir__, __all__, __setattr__, __getattr__, __getattribute__, __setitem__, __getitem__, __delitem__, __contains__等,魔法函数能够使类或函数获得特殊的功能。本篇介绍常用的魔法函数,所有代码以 JupyterLab 为平台运行。
__init__ 和 __del__ 像 C++ 等面向对象的语言中有类的构造函数(初始化类,实例化类对象)和析构函数(清理内存)。在 python 中也有类似的函数,__init__ 类似于构造函数,__del__类似于析构函数。
1 2 3 4 5 6 7 class Test (object ): def __init__ (self, name ): self.name = name print ("构造函数被调用" ) def __del__ (self ): print ("析构函数被调用" )
构造函数被调用
析构函数被调用
__call__ __call__ 方法类似于”()”运算符。如类实例化”Company()”和函数调用”depart()”。说明类和函数中自带该方法。另外,如果想要类中的实例也能够像函数一样执行,可以在类中显示定义”__call__()”方法。如下面的例子。
1 2 3 class Company (object ): def __init__ (self ): pass
1 "类 Company 是否有 __call__ 方法: " , hasattr (Company, "__call__" )
('类 Company 是否有 __call__ 方法: ', True)
1 2 3 4 5 6 class Country (object ): def __init__ (self ): self.name = None def __call__ (self ): print ("yes" )
yes
1 "类 Country 的实例 c 是否有 __call__ 方法: " , hasattr (c, "__call__" )
('类 Country 的实例 c 是否有 __call__ 方法: ', True)
1 "类 Country 的属性 name 是否有 __call__ 方法: " , hasattr (c.name, "__call__" )
('类 Country 的属性 name 是否有 __call__ 方法: ', False)
1 2 def depart (): print ("yes" )
1 "函数 depart 是否有 __call__ 方法:" , hasattr (depart, "__call__" )
('函数 depart 是否有 __call__ 方法:', True)
yes
__str__ 和 __repr__ __str__ 和 __repr__ 方法能够用来方便打印类的实例化对象。当使用 “print()” 函数打印时,应该在类中增加 “__str__()” 方法;当既想 “print()” 函数打印,又想在开发模式下直接输出实例化对象时,应该增加 “__repr__()” 方法。
当在类中没有定义魔法函数时
1 2 3 class Test1 (object ): def __init__ (self, name ): self.names = name
1 2 t1 = Test1("Zhangsan" ) print (t1)
<__main__.Test1 object at 0x7fd380359670>
<__main__.Test1 at 0x7fd380359670>
当在类中定义魔法函数: “__str__()” 时
1 2 3 4 5 6 class Test2 (object ): def __init__ (self, name ): self.name = name def __str__ (self ): return self.name
1 2 t2 = Test2("Zhangsan" ) print (t2)
Zhangsan
<__main__.Test2 at 0x7fd3802e6ac0>
'Zhangsan'
当在类中定义魔法函数: “__repr__()” 时
1 2 3 4 5 6 class Test3 (object ): def __init__ (self, name ): self.name = name def __repr__ (self ): return self.name
1 2 t3 = Test3("Zhangsan" ) print (t3)
Zhangsan
Zhangsan
__len__ Python内置函数中有一个 “len()” 函数,这个函数适用于获取序列类型数据的长度,在对一个实例使用 “len()” 方法时,真实输出的其实是 __len__ 的返回值。所以,只要一个类内部实现了 __len__ 方法,就可以对其实例使用 __len__ 方法。
1 2 3 4 5 6 7 class Company1 (object ): def __init__ (self, name, employees ): self.name = name self.employees = employees def __len__ (self ): return len (self.employees)
1 2 c1 = Company1(name="Apple1" , employees=["Zhangsan" , "Lisi" ]) len (c1)
2
__getitem__、__setitem__、__delitem__ 在 python 中有内置的字典类型 dict,通过方括号 “[]” 可以对字典进行赋值、取值和删除值。其实在类中定义魔法函数同样可以达到类似字典的上述操作。
1 2 3 4 5 6 7 8 9 10 11 12 class Company2 (object ): def __init__ (self ): self.company_info = {} def __setitem__ (self, key, value ): self.company_info[key] = value def __getitem__ (self, key ): return self.company_info[key] def __delitem__ (self, key ): del self.company_info[key]
1 2 3 4 c2 = Company2() c2["name" ] = "Apple2" c2["type" ] = "IT" c2["name" ], c2["type" ]
('Apple2', 'IT')
{'name': 'Apple2'}
使用python的反射机制实现
1 2 3 4 5 6 7 8 9 10 11 12 class Company3 (object ): def __init__ (self ): pass def __setitem__ (self, key, value ): setattr (self, key, value) def __getitem__ (self, key ): return getattr (self, key) def __delitem__ (self, key ): delattr (self, key)
1 2 3 4 c3 = Company3() c3["name" ] = "Apple3" c3["type" ] = "IT" c3["name" ], c3["type" ]
('Apple3', 'IT')
__contains__ 在 python 的字典类型中,可以使用关键字 ‘in’ 来判断是否包含有 ‘key’,同样地,在类中定义方法 __contains__ 同样可以获得该种能力
1 2 3 4 5 6 class Company4 (object ): def __init__ (self ): self.company_info = {} def __contains__ (self, key ): return key in self.company_info
1 2 3 c4 = Company4() c4.company_info["name" ] = "Apple4" "name" in c4
True
False
使用反射机制实现
1 2 3 4 5 6 7 8 9 class Company5 (object ): def __init__ (self ): pass def __setitem__ (self, key, value ): setattr (self, key, value) def __contains__ (self, key ): return hasattr (self, key)
1 2 3 c5 = Company5() c5["name" ] = "Apple5" "name" in c5
True
False
__iter__、__next__ 在我的另一篇博文: Python 中的迭代器和生成器 中,我介绍了 python 中的迭代、可迭代对象、迭代器和生成器,核心内容就是 __iter__ 和 __next__ 方法。任何类内部定义有 __iter__ 方法的实例化对象都是可迭代的(Iterable);同时定义有 __iter__ 和 __next__ 的是迭代器(Iterator),生成器(Generator)是一类特殊的迭代器(yield 关键字)。可迭代对象通过函数 “iter()” 可以转化为迭代器。
判断对象是否是可迭代对象、迭代器、生成器
1 from collections.abc import Generator, Iterable, Iterator
1 2 3 class A : def __iter__ (self ): pass
1 2 3 4 5 6 class B : def __iter__ (self ): pass def __next__ (self ): pass
1 2 3 4 5 def FibonacciGenerator (): a, b = 0 , 1 while True : a, b = b, a + b yield a
1 2 3 a = A() b = B() c = FibonacciGenerator()
1 isinstance (a, Iterable), isinstance (a, Iterator), isinstance (a, Generator)
(True, False, False)
1 isinstance (b, Iterable), isinstance (b, Iterator), isinstance (b, Generator)
(True, True, False)
1 isinstance (c, Iterable), isinstance (c, Iterator), isinstance (c, Generator)
(True, True, True)
1 2 d = "abc" isinstance (d, Iterable), isinstance (d, Iterator), isinstance (d, Generator)
(True, False, False)
1 2 dr = iter (d) isinstance (dr, Iterable), isinstance (dr, Iterator), isinstance (dr, Generator)
(True, True, False)
__enter__、__exit__ python 中常常使用 with 上下文管理器,如 with open(file_path, 'r') as f
打开文件获取句柄。这是非常有用的,即使是出现异常等,python 也能够关闭句柄,避免内存资源占用。其实,内部是由两个魔法函数来完成的。__enter__ 方法是在 with 语句开始执行的时候调用;__exit__ 方法是在 with 语句结束的时候调用,注意,无论 with 语句中的代码是否正常执行结束,都会执行 __exit__ 方法。除了读写文件外, python 中的数据库操作(特别注意关闭数据库连接)也可以用 with 上下文管理器。
1 2 3 4 5 6 7 8 import osfrom pathlib import Pathfile = "test.txt" Path(file).touch() with open (file, "w" ) as f: f.write("test" ) os.remove(file)
__getattr__、__setattr__、__getattribute__ python 的类中会有一些属性,属性的获取和设置需要一些魔法函数来支持。__getattribute__ 方法是调用类的属性时首先调用的方法;__getattr__ 方法是当调用类的属性失败时,执行的函数;__setattr__ 方法可以为类设置属性。
1 2 3 4 5 6 class GS (object ): def __init__ (self, name ): self.company = name def __getattr__ (self, name ): print ("__getattr__ 方法被调用,您找的属性不存在" )
找到属性时,不会执行 __getattr__ 方法
1 2 gs = GS("AppleG" ) gs.company
'AppleG'
找不到属性时,会执行 __getattr__ 方法
__getattr__ 方法被调用,您找的属性不存在
当属性找不到时,我们可以进行一些设置,如提醒、设置默认值等。
1 2 3 4 5 6 7 8 9 10 class MyDict (dict ): def __init__ (self, *args, **kwargs ): super (MyDict, self).__init__(*args, **kwargs) def __getattr__ (self, key ): try : return self[key] except KeyError: raise AttributeError(r"'MyDict' object has no attribute '%s'" % key)
1 2 d = MyDict({"a" : 1 , "b" : 2 }) d.a
1
在类中定义方法 __setattr__,使类的对象能够自定义属性
1 2 3 4 5 6 7 8 9 class GS2 (object ): def __init__ (self, name ): self.company = name def __setattr__ (self, name, value ): print ("__setattr__ 方法被调用" ) self.__dict__[name] = value
1 2 3 4 gs2 = GS2("AppleGS" ) print ("*" * 3 )gs2.type = "IT" gs2.type
__setattr__ 方法被调用
***
__setattr__ 方法被调用
'IT'
从上面的运行结果可以知道,__setattr__ 方法被调用了两次。这是因为在构造函数 __init__ 中也进行了一次属性赋值。所以,在定义 __setattr__ 的时候一定要注意,不能采用方法 1 编写代码,因为本身又会再次调用 __setattr__ 方法,这样会造成无限递归。可以采用方法 2 或方法 3.
1 2 3 4 5 6 7 8 9 10 11 12 13 class MyDict2 (dict ): def __init__ (self, *args, **kwargs ): super (MyDict2, self).__init__(*args, **kwargs) def __getattr__ (self, key ): try : return self[key] except KeyError: raise AttributeError(r"'MyDict' object has no attribute '%s'" % key) def __setattr__ (self, key, name ): self[key] = name
1 2 3 d = MyDict2() d.group = "Python" d.group
'Python'
__getattribute__ 方法是获取类的属性时首先调用的方法,因此,必须注意陷入无限递归。在 __getattribute__ 方法的设计中不能执行属性的获取操作,这样会再次除法 __getattribute__ 方法的调用,使程序陷入无限递归。可以通过父类的 __getattribute__ 方法获取对应的属性。
1 2 3 4 5 6 7 class GS3 (object ): def __init__ (self, name ): self.company = name def __getattribute__ (self, name ): print ("__getattribute__ 方法被调用" ) return object .__getattribute__(self, name)
1 2 3 gs3 = GS3("AppleGS3" ) print ("*" * 3 )gs3.company
***
__getattribute__ 方法被调用
'AppleGS3'
__dict__、__dir__、dir() __dict__ 是对象的一个属性,并不是函数,它的作用是返回对象的所有属性名为 key,属性值为 value 的一个字典,注意,这里所说的所有属性是指数据对象本身的属性,例如类的 __dict__ 只包含类本身的属性和函数,而类实例也只包含类实例的属性。这一点与 dir() 函数不同,dir() 将会返回一个列表,列表中包含对象所有有关的属性名。也就是说,__dict__ 是 dir() 的子集。而 dir() 实际上调用的是 __dir__ 方法。
1 2 3 4 5 6 class DD (object ): def __init__ (self, name ): self.company = name def func (self ): print ("func 方法被调用" )
{'company': 'AppleDD'}
mappingproxy({'__module__': '__main__',
'__init__': <function __main__.DD.__init__(self, name)>,
'func': <function __main__.DD.func(self)>,
'__dict__': <attribute '__dict__' of 'DD' objects>,
'__weakref__': <attribute '__weakref__' of 'DD' objects>,
'__doc__': None})
['__class__',
'__delattr__',
'__dict__',
'__dir__',
'__doc__',
'__eq__',
'__format__',
'__ge__',
'__getattribute__',
'__gt__',
'__hash__',
'__init__',
'__init_subclass__',
'__le__',
'__lt__',
'__module__',
'__ne__',
'__new__',
'__reduce__',
'__reduce_ex__',
'__repr__',
'__setattr__',
'__sizeof__',
'__str__',
'__subclasshook__',
'__weakref__',
'company',
'func']
['__repr__',
'__getattribute__',
'__iter__',
'__next__',
'__del__',
'send',
'throw',
'close',
'gi_frame',
'gi_running',
'gi_code',
'__name__',
'__qualname__',
'gi_yieldfrom',
'__doc__',
'__hash__',
'__str__',
'__setattr__',
'__delattr__',
'__lt__',
'__le__',
'__eq__',
'__ne__',
'__gt__',
'__ge__',
'__init__',
'__new__',
'__reduce_ex__',
'__reduce__',
'__subclasshook__',
'__init_subclass__',
'__format__',
'__sizeof__',
'__dir__',
'__class__']
魔法函数一览
参考文献
python析构函数
关于使用 Python 析构函数的正确姿势
Python:实例讲解Python中的魔法函数(高级语法)
python魔法函数