Python魔法方法
魔法方法(Magic Methods)是Python中的内置函数,一般以双下划线开头和结尾,例如__init__、__del__等。之所以称之为魔法方法,是因为这些方法会在进行特定的操作时会自动被调用,魔法方法大致分为如下几类:构造与初始化、类的表示、访问控制、比较、运算等操作、容器类操作、可调用对象、序列化类。在这里我记录一下它的常用魔法方法
1 |
|
1 |
|
构造初始化相关
与构造和初始化相关的方法有三个__init__、del__、__new
- __new__方法是创建类的实例并返回,再进入__init__对实例进行初始化,使用场景如单例模式、工厂模式,以及一些不可变对象的继承上
- __init__通过调用属性操作相关的方法,对属性进行初始化
- 而__del__方法则是当对象被系统回收的时候调用的魔法方法,在对象生命周期调用结束时调用该方法。Python 采用自动引用计数(ARC)方式来回收对象所占用的空间
- 执行顺序如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19class User:
def __new__(cls, *args, **kwargs) -> Self:
print("构造方法__new__")
# 创建实例并返回,如果不返回则不会进入到初始化方法
# __new__返回的是其它实例,当前实例的__init__也不会调用
return object.__new__(cls)
def __init__(self, name: str, age: int) -> None:
# self就是__new__返回的对象实例
self.name = name
self.age = age
print("初始化方法__init__")
def __del__(self):
print("del方法被执行")
user = User("zs", 23)
del user # python使用引用计数法,在没有被引用的时候会被回收1
2
3
4执行顺序:
构造方法__new__
初始化方法__init__
del方法被执行 - 引入父类之后执行顺序如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32class People:
def __new__(cls, *args, **kwargs) -> Self:
print("父类构造方法__new__")
return object.__new__(cls)
def __init__(self, id_card: str) -> None:
self.id_card = id_card
print("父类初始化方法__init__")
def __del__(self):
print("父类的del方法被执行")
class User(People):
def __new__(cls, *args, **kwargs) -> Self:
print("子类构造方法__new__")
# 创建实例并返回,如果不返回则不会进入到初始化方法
# __new__返回的是其它实例,当前实例的__init__也不会调用
return super().__new__(cls, *args, **kwargs)
def __init__(self, id_card: str, name: str, age: int) -> None:
# self就是__new__返回的对象实例
super().__init__(id_card)
self.name = name
self.age = age
print("子类初始化方法__init__")
def __del__(self):
print("子类的del方法被执行")
user = User("10010", "zs", 23)1
2
3
4
5子类构造方法__new__
父类构造方法__new__
父类初始化方法__init__
子类初始化方法__init__
子类的del方法被执行 - 通过new方法实现单例模式
1
2
3
4
5
6
7
8
9
10
11
12class Single(object):
_instance = None
def __new__(cls, *args, **kw):
if cls._instance is None:
cls._instance = object.__new__(cls, *args, **kw)
return cls._instance
def __init__(self):
pass
single1 = Single()
single2 = Single()
print(id(single1) == id(single2))
控制属性访问
这类魔法方法主要再对对象的属性进行访问、定义、修改时起作用。主要有:
- getattr(self, name): 定义当用户试图获取一个属性时的行为
- getattribute(self, name):定义当该类的属性被访问时的行为(先调用该方法,查看是否存在该属性,若不存在,接着去调用__getattr__)
- setattr(self, name, value):定义当一个属性被设置时的行为
- delattr(self, name):定义当一个属性被删除时的行为
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32class User:
def __init__(self, name: str, age: int) -> None:
# 会调用__setattr__方法复制
# 每次复制都会把属性写入dict字典中
self.age = age
# print(self.__dict__)
self.name = name
# print(self.__dict__)
def __setattr__(self, __name: str, __value: Any) -> None:
# 由于每次类实例进行属性赋值时都会调用__setattr__(),所以也可以重载__setattr__()方法,来动态的观察每次实例属性赋值时__dict__()的变化
print(f"__setattr__被调用了,设置属性{__name}值为{__value}")
# self.__dict__会去调用__getattribute__方法确认__dict__是否存在
self.__dict__[__name] = __value
def __getattribute__(self, __name: str) -> Any:
print(f"__getattribute__方法被调用了,属性名称{__name}")
return object.__getattribute__(self, __name)
def __getattr__(self, __name):
# 如果class中定义了__getattr__(),则__getattr__()不会被调用(除非显示调用或引发AttributeError异常)
print(f"调用__getattr__方法获取属性{__name}")
return self.__dict__[__name]
def __delattr__(self, __name):
print(f"调用了__delattr__方法,删除了{__name}属性")
self.__dict__.pop[__name]
user = User("zs", 23)
delattr(user, "name")
print(user.name)
print(user.a) # 因为__getattribute__抛出AttributeError异常,触发__getattr__
容器类操作
有一些方法可以让我们自己定义自己的容器,就像python内置的list,tuple,dict等等;容器分为可变容器和不可变容器,这里的细节需要去了解相关的协议。如果自定义一个不可变容器的话,只能定义__len__和__getitem__;定义一个可变容器除了不可变容器的所有魔法方法,还需要定义__setitem__和__delitem__;如果容器可迭代还需要定义__iter__
- len(self):返回容器的长度
- getitem(self,key):当需要执行self[key]的方式去调用容器中的对象,调用的时该方法
- setitem(self,key,value):当需要执行self[key] = value时,调用的是该方法。
- delitem(self, key):当需要执行 del self[key]时,需要调用该方法;
- iter(self):当容器可以执行 for x in container: ,或者使用iter(container)时,需要定义该方法
- reversed(self):实现当reversed()被调用时的行为。应该返回序列反转后的版本。仅当序列可以是有序的时候实现它,例如对于列表或者元组。
- contains(self, item):定义了调用in和not in来测试成员是否存在的时候所产生的行为。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56Card = collections.namedtuple("Card", ["rank", "suit"])
class FrenchDeck:
ranks = [str(n) for n in range(2, 11)] + list("JQKA")
suits = "spades diamonds clubs hearts".split()
def __init__(self, cards: list[Card] = None):
if cards is not None:
self._cards = cards
return
self._cards = [Card(rank, suit) for suit in self.suits for rank in self.ranks]
def __len__(self):
# 集合相关操作,返回集合的长度len(FrenchDeck())
print("调用__len__方法获取集合的长度")
return len(self._cards)
def __getitem__(self, position):
# 仅仅实现了 __getitem__ 方法,这一摞牌就变成可迭代的了
print("调用__getitem__方法返回改索引或者切片位置的数据")
return self._cards[position]
def __setitem__(self, position, value):
# 通过obj[key]=value去修改容器内的对象
print(f"调用__setitem__方法设置索引或者切片位置{position}的数据为{value}")
self._cards[position] = value
def __delitem__(self, position):
# 通过del obj[key]来删除容器内的对象
print(f"调用__getitem__方法删除索引或者切片位置{position}的数据")
del self._cards[position]
def __iter__(self):
# 通过for 循环来遍历容器
print(f"调用__iter__方法迭代数据")
return iter(self._cards)
def __reversed__(self):
# 通过reverse(obj)来反转容器内的对象
print(f"调用___reversed__方法翻转内部cards")
return FrenchDeck(reversed(self._cards))
def __contains__(self, item):
# 通过 item in obj来判断元素是否在容器内
print(f"调用__contains__方法翻转判断是否包含")
return item in self._cards
frenchDeck = FrenchDeck()
for card in frenchDeck:
print(card)
print(frenchDeck[1])
print(frenchDeck[1::2])
spades2 = Card(rank='12', suit='spades')
print(spades2 in frenchDeck)
类的表示
类的表示相关的魔法方法主要有__str__、repr__和__bool
- __str__主要是在打印对象print(obj)时,会隐式调用str(obj),即调用类中的__str__方法;定了该方法就可以通过str(obj)来调用;
- __repr__主要式在直接输出对象时的显示,会调用__repr__方法;定义了该方法就可以通过repr(obj)来调用。
- __bool__主要表示对象作为判断条件时候反映的结果
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25class User:
def __init__(self, name: str, age: int) -> None:
self.name = name
self.age = age
def __str__(self) -> str:
print("调用了方法__str__")
return f"name={self.name},age={self.age}"
def __repr__(self) -> str:
print("调用了方法__repr__")
return f"cls={self.__class__.__name__},id={id(self)}"
def __bool__(self) -> bool:
print("调用了方法__bool__")
if self.name and self.age > 0:
return True
return False
user = User("zs", 23)
print(user)
user
if user:
pass
可调用对象
方法也是一种高等的对象。通过对对象实现__call__就可以实现像调用方法一样去调用类
1 |
|
使用场景,通过__call__方法做装饰器
1 |
|
序列化
python中有一个pickle模块来对实例化对象进行序列化;如pickle.loads(obj),pickle.dumps(obj)等;在序列化的时候也是调用的内置魔法方法:
- getstate():用于Python 对象的序列化,指定在序列化时将哪些信息记录下来
- setstate():用于Python 对象的反序列化,指定在反序列化时指明如何利用信息
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20class User:
def __init__(self, name: str, age: int) -> None:
self.name = name
self.age = age
def __getstate__(self):
print("调用方法__getstate__")
# 序列化时返回的,即为在反序列化时传入的state
return {"name": self.name, "age": self.age}
def __setstate__(self, state):
print("调用方法__setstate__")
self.name = state["name"]
self.age = 300
user = User("zs","23")
import pickle
ser = pickle.dumps(user)
user1 = pickle.loads(ser)
print(user1.age)
反射
我们可以控制怎么使用内置在函数isinstance()和issubclass()方法 反射定义魔术方法. 这个魔术方法是:
instancecheck(self, instance)
subclasscheck(self, subclass)
比较运算
通过定义各类比较、运算、类型相关的魔法方法,来实现对象之间的各类比较、运算等操作。这类魔法方法非常多,不一一展开,比如__eq__ 魔法方法,如果自定义的对象要实现==的比较功能,则必须在类中实现__eq__:
1 |
|