Python魔法方法

魔法方法(Magic Methods)是Python中的内置函数,一般以双下划线开头和结尾,例如__init__、__del__等。之所以称之为魔法方法,是因为这些方法会在进行特定的操作时会自动被调用,魔法方法大致分为如下几类:构造与初始化、类的表示、访问控制、比较、运算等操作、容器类操作、可调用对象、序列化类。在这里我记录一下它的常用魔法方法

1
2
3
# 查看魔法方法
magic = dir("hello")
print(list(filter(lambda x: x.startswith("__"), magic)))
1
['__add__', '__class__', '__contains__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__getnewargs__', '__getstate__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__iter__', '__le__', '__len__', '__lt__', '__mod__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__rmod__', '__rmul__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__']

构造初始化相关

与构造和初始化相关的方法有三个__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
    19
    class 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
    32
    class 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
    12
    class 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
    32
    class 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
    56
    Card = 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
    25
    class 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
2
3
4
5
6
7
8
9
10
11
12
class User:
def __init__(self, name: str, age: int) -> None:
self.name = name
self.age = age

def __call__(self, *args: Any, **kwds: Any) -> Any:
print(f"调用方法__call__,args={args},kwds={kwds}")


user = User("zs", 23)
# 调用方法__call__,args=('1',),kwds={'a': 'a'}
user("1", a="a")

使用场景,通过__call__方法做装饰器

1
2
3
4
5
6
7
8
9
10
11
12
13
class A(object):
def __init__(self,func):
self.func=func
def __call__(self):
print('this is call')
return self.func()
# 语法糖,相当于A(f)
@A
def f():
print('hhh')
f()
# this is call
# hhh

序列化

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
    20
    class 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
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
class Vector:
def __init__(self, x: str, y: int) -> None:
self.x = x
self.y = y

def __repr__(self):
return "Vector(%r, %r)" % (self.x, self.y)

def __bool__(self):
return bool(self.x or self.y)

def __add__(self, other: "Vector"):
print("加法:__add__")
return Vector(self.x + other.x, self.y + other.y)

def __sub__(self, other: "Vector"):
print("减法:__sub__")
return Vector(self.x - other.x, self.y - other.y)

def __abs__(self):
print("绝对值:__abs__")
return hypot(self.x, self.y)

def __mul__(self, scalar):
print("乘法:__mul__")
return Vector(self.x * scalar, self.y * scalar)


v1 = Vector(1, 3)
v2 = Vector(2, 4)
v2 - v1
v2 + v1
v2 * 5

Python魔法方法
https://vegetablest.github.io/2022/09/17/python魔法方法/
作者
af su
发布于
2022年9月17日
许可协议