如果对你有帮助,欢迎微信搜索【海哥python】关注这个互联网苟且偷生的工具人。
什么是魔术方法
所谓魔法方法,它的官方的名字实际上叫special method
,是Python的一种高级语法,允许你在类中自定义函数,并绑定到类的特殊方法中。比如在类A中自定义__str__()
函数,则在调用str(A())时,会自动调用__str__()
函数,并返回相应的结果。
https://docs.python.org/3/reference/datamodel.html#special-method-names
我们常常看到的Magic Methods
这个名字,在官方的文档里是没有出现过的。
当然无论是magic methods
还是魔术方法,这些词都被广泛的使用着。
所谓的魔术方法,是python提供的,让用户客制化一个类的方式,它顾名思义,就是定义在类里面的一些特殊的方法。
这些special method
的特点,就是它的method的名字前后都有两个下划线,所以这些方法也被称为dunder methods
。那包括这种前后两个下划线的形式,也叫做dunder score
,它的意思就是double underscore
。
在我们平时写程序的时候,已经或多或少的接触过不少的魔术方法。
比如:__init__
就非常非常的常用。
基础的魔术方法
__new__和__init__
首先,我们来聊一下__new__
和__init__
。
这两个方法,可以让你改变从一个类建立一个对象时候的行为,这两个也比较容易被搞混。
如果你不那么了解这两个方法的机制本身,你只需要记住,__new__
是从一个class建立一个object的过程。
而__init__
是有了这个object之后,给这个object初始化的过程。
class A:
def __new__(cls, *args, **kwargs):
print("__new__")
return super().__new__(cls, *args, **kwargs)
def __init__(self):
print("__init__")
if __name__ == '__main__':
a = A()
输出结果为:
__new__
__init__
我们可以看到__new__
和__init__
都被使用了。我们可以粗略的想象成:
obj = __new__(A)
__init__(obj)
如果我们在建立object的时候传入了参数,则参数既会传给__new__
,也会传给__init__
。
在我们实际的应用中,__new__
函数用到的是相对较少的。如果你不需要客制化建立这个object的过程,你只需要初始化这个object,你只需要用到__init__
。
那什么时候用__new__
呢?
比如说,我要创建一个单例,或者一些和metaclass有关的才会用到__new__
函数。
单例模式是指在整个应用程序中,某个类只能有一个实例存在,且该实例可以被任何模块访问到。这种模式的应用场景包括数据库连接池、日志对象等需要全局唯一性的对象。
简单单例
以下是一个简单的单例模式的示例代码:
class Singleton:
_instance = None
def __new__(cls):
if not cls._instance:
cls._instance = super().__new__(cls)
return cls._instance
在这个例子中,我们定义了一个名为 Singleton
的类。它包含一个类变量 _instance
,该变量用于存储唯一实例的引用。
在 __new__
方法中,我们检查 _instance
是否为 None。如果是,则创建一个新的实例并将其分配给 _instance
。如果不是,则直接返回 _instance
,而不创建新的实例。
通过这种方式,我们可以确保在应用程序中只有一个 Singleton
实例。要使用它,只需创建一个 Singleton
对象即可:
s1 = Singleton()
s2 = Singleton()
print(s1 is s2) # 输出 True
以上代码输出结果为 True
,说明 s1
和 s2
引用的是同一个实例,即单例模式实现成功。
__new__和元类(metaclass)
在 Python 中,__new__()
和元类(metaclass
)之间有着紧密的联系,它们都是用来控制类的创建过程的。
元类是 Python 中的一个高级特性,它允许我们在创建类的过程中动态地修改类。元类可以通过定义 __new__()
方法来控制类的实例化过程。
在 Python 中,当我们通过 class
关键字定义一个新的类时,Python 解释器会自动调用元类来实例化类对象。
在 Python 中,使用 __call__()
方法可以实现将类的实例对象作为函数调用的效果,类似于调用一个函数。当我们调用一个类的实例对象时,Python 会自动调用这个实例对象的 __call__()
方法,从而实现了将类的实例对象作为函数调用的效果。
通过在类的 __call__()
方法中间接调用类的 __new__()
方法和 __init__()
方法,可以实现单例模式。具体而言,每次调用类的实例对象时,都会先检查已经创建的实例对象是否存在,如果存在则直接返回该实例对象,如果不存在则通过调用 __new__()
方法和 __init__()
方法来创建一个新的实例对象,并将其存储下来。由于每次都返回同一个实例对象,因此实现了单例模式。
以下是一个使用元类和 __call__()
方法实现单例模式的示例代码:
class SingletonType(type):
_instances = {}
def __call__(cls, *args, **kwargs):
if cls not in cls._instances:
cls._instances[cls] = super().__call__(*args, **kwargs)
return cls._instances[cls]
class MyClass(metaclass=SingletonType):
pass
在上述代码中,我们首先定义了一个元类 SingletonType
,这个元类重写了 __call__()
方法,用于控制类的实例化过程。在 __call__()
方法中,我们首先检查 _instances
字典中是否已经有该类的实例对象,如果已经存在,则直接返回该实例对象;如果不存在,则通过调用父类的 __call__()
方法创建一个新的实例对象,并将其存储到 _instances
字典中,然后返回该实例对象。
接下来,我们定义了一个类 SingletonClass
,并将其元类设置为 SingletonType
。由于 SingletonType
的 __call__()
方法控制着 SingletonClass
的实例化过程,因此每次创建 SingletonClass
的实例对象时,都会经过 SingletonType
的 __call__()
方法来进行处理,从而实现了单例模式。
下面是一个示例,演示了如何使用 SingletonClass
类来创建实例:
a = SingletonClass()
b = SingletonClass()
print(a is b) # True
由于 SingletonClass
类是一个单例类,因此 a
和 b
都是同一个实例对象,所以 a is b
的结果为 True
。
所以必要时,我们可以通过元类和__new__方法来控制类的创建过程。
__new__
是创建object,因此它是有返回值的,必须返回这个object。
而__init__
没有返回值,__init__
函数里面的self
就是你要初始化的对象。
__del__
__del__()
是delete的缩写,这是析构魔术方法。当一块空间没有了任何引用时 默认执行__del__
回收这个类地址,一般我们不自定义__del__
, 有可能会导致问题
-
触发时机:当对象被内存回收的时候自动触发,有下面两种情况:
- 页面执行完毕回收所有变量
- 当多个对象指向同一地址,所有对象被del的时候
-
功能:对象使用完毕后资源回收
-
参数:一个
self
接受对象 -
返回值:无
注意:程序自动调用
__del__()
方法,不需要我们手动调用。
python中对象的释放是比较复杂的。
__del__
和关键字del是没有关系的,我们看到del o没有触发__del__
。
__del__()
和del
关键字是两个不同的概念,虽然它们都与对象的销毁相关。
del
是Python的一个关键字,用于删除变量或对象的引用。当我们执行del obj
语句时,Python会将对象obj
的引用计数减1,如果引用计数为0,则对象被销毁。del
关键字并不会直接调用对象的__del__()
方法,它只是将对象的引用计数减1,由Python自动决定是否调用__del__()
方法。
__del__()
方法是一个特殊方法,用于在对象被销毁时执行一些清理任务。当对象的引用计数为0时,Python会自动调用该对象的__del__()
方法进行清理。__del__()
方法在对象被销毁前最后一次被调用,我们可以在这个方法中执行一些需要进行清理的操作,如释放资源、关闭文件等。
需要注意的是,__del__()
方法不是必须的,大多数情况下,Python会自动处理对象的销毁和内存管理,我们不需要手动定义__del__()
方法。如果我们确实需要进行一些特殊的清理任务,应该尽量避免使用__del__()
方法,而是使用上下文管理器、with
语句等方式来管理资源和清理任务。
__repr__和__str__
__repr__(self)
和__str__(self)
都是用于返回对象的字符串表示形式,但它们的用途不同。__repr__(self)
主要用于调试和开发,而__str__(self)
则主要用于用户友好的输出。
举个例子,考虑下面的Python类:
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
def __repr__(self):
return f"Person(name={self.name}, age={self.age})"
def __str__(self):
return f"{self.name} is {self.age} years old"
在这个例子中,__repr__
返回一个类似于构造函数调用的字符串,用于显示对象的内部状态。而__str__
则返回一个可读的字符串,用于显示对象的外部状态。例如:
>>> person = Person("Alice", 25)
>>> print(person) # 调用 __str__
Alice is 25 years old
>>> person # 调用 __repr__
Person(name=Alice, age=25)
这样,在调试和开发时,我们可以使用__repr__
方法来查看对象的内部状态,而在用户友好的输出中使用__str__
方法来提供易于理解的字符串表示形式。
在实际使用中,我们可以通过内置函数repr()
和str()
来分别获取对象的__repr__
和__str__
表示形式。例如:
p = Person("Alice", 25)
print(repr(p)) # Person('Alice', 25)
print(str(p)) # Alice (25 years old)
__repr__()
是__str__()
的“备胎”,如果找不到__str__()
就会找__repr__()
方法。%r
默认调用的是__repr__()
方法,%s
调用__str__()
方法
__formate__
__format__
方法是Python中用于格式化对象输出的特殊方法。它可以让我们在使用字符串格式化方法(如str.format()
、f-string
等)输出对象时自定义输出格式。
__format__
方法有两个参数,分别为格式字符串和格式参数。格式字符串用于指定输出格式,格式参数则是要输出的参数值。其中,格式字符串是一个包含格式说明符的字符串,可以使用大括号{}
来指定要输出的参数,并在大括号中使用冒号:
来指定参数的格式。
下面是一个简单的例子,展示如何在类中定义__format__
方法来控制输出格式:
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def __format__(self, format_spec):
if format_spec == "r":
return f"({self.y}, {self.x})"
else:
return f"({self.x}, {self.y})"
p = Point(1, 2)
print("Formatted point: {!r}".format(p))
在上面的例子中,我们定义了一个Point
类,包含x
和y
两个属性。我们在类中定义了__format__
方法,用于控制输出格式。如果格式字符串为"r"
,则输出(y, x)
的格式;否则输出(x, y)
的格式。在最后一行,我们使用str.format()
方法并传入"!r"
参数来触发__format__
方法,输出结果为Formatted point: (2, 1)
。
需要注意的是,如果我们定义了__format__
方法,但在格式字符串中没有使用相应的格式说明符,那么__format__
方法将不会被调用。此外,我们还可以在类中定义其他特殊方法来控制对象的输出格式,例如__str__
、__repr__
等。
微信公众号:海哥python