深圳幻海软件技术有限公司 欢迎您!

聊聊Python中常见魔法方法

2023-02-28

​什么是魔法方法?魔法方法(MagicMethods)是Python中的内置函数,一般以双下划线开头和结尾,例如__init__、__del__等。之所以称之为魔法方法,是因为这些方法会在进行特定的操作时会自动被调用。在Python中,可以通过dir()方法来查看某个对象的所有方法和属性,其中双下划

​什么是魔法方法?

魔法方法(Magic Methods)是Python中的内置函数,一般以双下划线开头和结尾,例如__init__、__del__等。之所以称之为魔法方法,是因为这些方法会在进行特定的操作时会自动被调用。

在Python中,可以通过dir()方法来查看某个对象的所有方法和属性,其中双下划线开头和结尾的就是该对象的魔法方法。以字符串对象为例:

>>> dir("hello")
['__add__', '__class__', '__contains__', '__delattr__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__',
'__getitem__', '__getnewargs__', '__getslice__', '__gt__', '__hash__', '__init__', '__le__', '__len__', '__lt__', '__mo
d__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__rmod__', '__rmul__', '__setattr__',
'__sizeof__', '__str__', '__subclasshook__', '_formatter_field_name_split', '_formatter_parser', 'capitalize', 'center',
'count', 'decode', 'encode', 'endswith', 'expandtabs', 'find', 'format', 'index', 'isalnum', 'isalpha', 'isdigit', 'isl
ower', 'isspace', 'istitle', 'isupper', 'join', 'ljust', 'lower', 'lstrip', 'partition', 'replace', 'rfind', 'rindex', '
rjust', 'rpartition', 'rsplit', 'rstrip', 'split', 'splitlines', 'startswith', 'strip', 'swapcase', 'title', 'translate'
, 'upper', 'zfill']
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.

可以看到字符串对象有__add__方法,所以在Python中可以直接对字符串对象使用"+"操作,当Python识别到"+"操作时,就会调用该对象的__add__方法。有需要时我们可以在自己的类中重写__add__方法来完成自己想要的效果。

class A(object):
  def __init__(self, str):
      self.str = str


•   def __add__(self, other):
•       print ('overwrite add method')
•       return self.str + "---" + other.str


>>>a1 = A("hello")
>>>a2 = A("world")
>>>print (a1 + a2)
>>>overwrite add method
>>>"hello---world"
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.

我们重写了__add__方法,当Python识别"+"操作时,会自动调用重写后的__add__方法。可以看到,魔法方法在类或对象的某些事件出发后会自动执行,如果希望根据自己的程序定制特殊功能的类,那么就需要对这些方法进行重写。使用魔法方法,我们可以非常方便地给类添加特殊的功能。

常用的魔法方法

1.构造与初始化

__new__、__init__ 这两个魔法方法常用于对类的初始化操作。上面我们创建a1 = A("hello")时,但首先调用的是__new__;初始化一个类分为两步:    

  • a.调用该类的new方法,返回该类的实例对象  
  • b.调用该类的init方法,对实例对象进行初始化

__new__(cls, *args, **kwargs)至少需要一个cls参数,代表传入的类。后面两个参数传递给__init__。在__new__可以决定是否继续调用__init__方法,只有当__new__返回了当前类cls的实例,才会接着调用__init__。结合__new__方法的特性,我们可以通过重写__new__方法实现Python的单例模式:

class Singleton(object):
  def __init__(self):
      print("__init__")

•   def __new__(cls, *args, **kwargs):
•       print("__new__")
•       if not hasattr(Singleton, "_instance"):
•           print("创建新实例")
•           Singleton._instance = object.__new__(cls)
•       return Singleton._instance

>>> obj1 = Singleton()
>>> __new__
>>> 创建新实例
>>> __init__
>>> obj2 = Singleton()
>>> __new__
>>> __init__
>>> print(obj1, obj2)
>>> (<__main__.Singleton object at 0x0000000003599748>, <__main__.Singleton object at 0x0000000003599748>)
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.

可以看到虽然创建了两个对象,但两个对象的地址相同。

2.控制属性访问这类魔法

方法主要对对象的属性进行访问、定义、修改时起作用。主要有:

  • __getattr__(self, name): 定义当用户试图获取一个属性时的行为。
  • __getattribute__(self, name):定义当该类的属性被访问时的行为(先调用该方法,查看是否存在该属性,若不存在,接着去调用getattr)。
  • __setattr__(self, name, value):定义当一个属性被设置时的行为。

当初始化属性时如self.a=a时或修改实例属性如ins.a=1时本质时调用魔法方法self.__setattr__(name,values);当实例访问某个属性如ins.a本质是调用魔法方法a.__getattr__(name)

3.容器类操作

有一些方法可以让我们自己定义自己的容器,就像Python内置的List,Tuple,Dict等等;容器分为可变容器和不可变容器。

如果自定义一个不可变容器的话,只能定义__len__和__getitem__;定义一个可变容器除了不可变容器的所有魔法方法,还需要定义__setitem__和__delitem__;如果容器可迭代。还需要定义__iter__。

  • __len__(self):返回容器的长度
  •  __getitem__(self,key):当需要执行self[key]的方式去调用容器中的对象,调用的是该方法    __setitem__(self,key,value):当需要执行self[key] = value时,调用的是该方法
  • __iter__(self):当容器可以执行 for x in container:,或者使用iter(container)时,需要定义该方法

下面举一个例子,实现一个容器,该容器有List的一般功能,同时增加一些其它功能如访问第一个元素,最后一个元素,记录每个元素被访问的次数等。

class SpecialList(object):
    def __init__(self, values=None):
        self._index = 0
        if values is None:
            self.values = []
        else:
            self.values = values
        self.count = {}.fromkeys(range(len(self.values)), 0)

    def __len__(self):  # 通过len(obj)访问容器长度
        return len(self.values)

    def __getitem__(self, key):  # 通过obj[key]访问容器内的对象
        self.count[key] += 1
        return self.values[key]

    def __setitem__(self, key, value):  # 通过obj[key]=value去修改容器内的对象
        self.values[key] = value

    def __iter__(self):  # 通过for 循环来遍历容器
        return iter(self.values)

    def __next__(self):
        # 迭代的具体细节
        # 如果__iter__返回时self 则必须实现此方法
        if self._index >= len(self.values):
            raise StopIteration()
        value = self.values[self._index]
        self._index += 1
        return value

    def append(self, value):
        self.values.append(value)

    def head(self):
        # 获取第一个元素
        return self.values[0]
    
    def last(self):
        # 获取最后一个元素
        return self.values[-1]
  • 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.

 这类方法的使用场景主要在你需要定义一个满足需求的容器类数据结构时会用到,比如可以尝试自定义实现树结构、链表等数据结构(在collections中均已有),或者项目中需要定制的一些容器类型。

总结

魔法方法在Python代码中能够简化代码,提高代码可读性,在常见的Python第三方库中可以看到很多对于魔法方法的运用。因此当前这篇文章仅是抛砖引玉,真正的使用需要在开源的优秀源码中以及自身的工程实践中不断加深理解并合适应用。