Python中必须知道的5对魔术方法
共 7571字,需浏览 16分钟
·
2021-03-05 14:05
简介
calculate_mean_score
比 calculatemeanscore
更易于阅读。你可能已经知道,除了这种使用下划线的常用方式之外,我们还为函数名称加上一个或两个下划线(例如, _func
, __func
),以表示这些函数供类或模块内的私有使用。没有下划线前缀的名称被认为是公共API。__func__
。由于使用了双下划线,因此某些人将特殊方法称为 “dunder方法” 或简称为 “dunders” (译者注:dunder 是 double underscore 的缩写,即双下划线)。在本文中,我们将讨论5对紧密相关的常用魔术方法,每对方法代表一个 Python 概念。1. 实例化: __new__
和 __init__
__init__
。此方法用于定义实例对象的初始化行为。具体来说,在 __init__
方法中,你想要为创建的实例对象设置初始属性。这是一个简单的示例:
classProduct:
def __init__(self, name, price):
self.name = name
self.price = price
__init__
方法时,我们不会直接调用它。取而代之的是, __init__
方法成为该类的构造函数方法的构建基础,该类的构造函数与 __init__
方法具有相同的功能签名。例如,要创建一个新的 Product
实例,请使用以下代码:
product = Product("Vacuum", 150.0)
__init__
方法最接近的是 __new__
方法,我们通常不会在自定义类中实现该方法。本质上, __new__
方法实际上创建了实例对象,该实例对象被传递给 __init__
方法以完成初始化过程。__new__
和 __init__
方法。
>>> classProduct:
... def __new__(cls, *args):
... new_product = object.__new__(cls)
... print("Product __new__ gets called")
... return new_product
...
... def __init__(self, name, price):
... self.name = name
... self.price = price
... print("Product __init__ gets called")
...
>>> product = Product("Vacuum", 150.0)
Product __new__ gets called
Product __init__ gets called
2. 字符串(String)的表现形式: __repr__
和 __str__
classProduct:
def __init__(self, name, price):
self.name = name
self.price = price
def __repr__(self):
return f"Product({self.name!r}, {self.price!r})"
def __str__(self):
return f"Product: {self.name}, ${self.price:.2f}"
__repr__
方法应该返回一个字符串,该字符串显示如何创建实例对象。具体来说,可以将该字符串传递给 eval()
来重新构造实例对象。以下代码段向你展示了这样的操作:
>>> product = Product("Vacuum", 150.0)
>>> repr(product)
"Product('Vacuum', 150.0)"
>>> evaluated = eval(repr(product))
>>> type(evaluated)
<class'__main__.Product'>
__str__
方法可以返回有关实例对象的更多描述。应该注意的是, print()
函数使用 __str__
方法来显示与实例相关的信息,如下所示:
>>> print(product)
Product: Vacuum, $150.00
__repr__
方法通常是供开发人员使用的,因此我们希望显示实例化信息,而 __str__
方法是针对常规用户的,因此我们希望显示更多的信息。3. 迭代: __iter__
和 __next__
for item in iterable:
# Operations go here
__iter__
特殊方法来完成的。另外,检索迭代器的下一项涉及 __next__
特殊方法的实现。让我们继续前面的示例,并使我们的 Product 类作为 for 循环中的迭代器工作:
>>> classProduct:
... def __init__(self, name, price):
... self.name = name
... self.price = price
...
... def __str__(self):
... return f"Product: {self.name}, ${self.price:.2f}"
...
... def __iter__(self):
... self._free_samples = [Product(self.name, 0) for _ in range(3)]
... print("Iterator of the product is created.")
... return self
...
... def __next__(self):
... if self._free_samples:
... return self._free_samples.pop()
... else:
... raiseStopIteration("All free samples have been dispensed.")
...
>>> product = Product("Perfume", 5.0)
>>> for i, sample in enumerate(product, 1):
... print(f"Dispense the next sample #{i}: {sample}")
...
Iterator of the product is created.
Dispense the next sample #1: Product: Perfume, $0.00
Dispense the next sample #2: Product: Perfume, $0.00
Dispense the next sample #3: Product: Perfume, $0.00
__iter__
方法中保存了免费样本 (free samples),这些样本为自定义类实例提供了迭代器。为了实现迭代行为,我们通过提供免费样本列表中的对象来实现 __next__
方法。当我们用完免费样本时,迭代结束。4. 上下文管理器: __enter__
和 __exit__
with open('filename.txt') as file:
# Your file operations go here
with
语句的使用被称为上下文管理器技术。具体来说,在上面的文件操作示例中, with
语句将为文件对象创建一个上下文管理器,并且在文件操作之后,上下文管理器将帮助我们关闭文件对象,以便共享资源(即文件) 可以用于其他进程。__enter__
和 __exit__
方法。 __enter__
方法设置了上下文管理器,该上下文管理器为我们进行操作准备了所需的资源,而 __exit__
方法则是清理应释放的所有已使用资源,以使其可用。让我们考虑下面的简单示例,其中包含先前的 “ Product” 类:
>>> classProduct:
... def __init__(self, name, price):
... self.name = name
... self.price = price
...
... def __str__(self):
... return f"Product: {self.name}, ${self.price:.2f}"
...
... def _move_to_center(self):
... print(f"The product ({self}) occupies the center exhibit spot.")
...
... def _move_to_side(self):
... print(f"Move {self} back.")
...
... def __enter__(self):
... print("__enter__ is called")
... self._move_to_center()
...
... def __exit__(self, exc_type, exc_val, exc_tb):
... print("__exit__ is called")
... self._move_to_side()
...
>>> product = Product("BMW Car", 50000)
>>> with product:
... print("It's a very good car.")
...
__enter__ is called
The product (Product: BMW Car, $50000.00) occupies the center exhibit spot.
It's a very good car.
__exit__ is called
Move Product: BMW Car, $50000.00 back.
__enter__
方法。当在 with 语句中完成该操作时,将调用 __exit__
方法。__enter__
和 __exit__
方法来创建上下文管理器。使用上下文管理器“装饰器”函数,可以更轻松地完成此操作。5. 更精细的属性访问控制: __getattr__
和 __setattr__
__getattr__
和 __setattr__
方法来进行控制。具体来说,访问实例对象的属性时将调用 __getattr__
方法,而当我们设置实例对象的属性时将调用 __setattr__
方法。
>>> classProduct:
... def __init__(self, name):
... self.name = name
...
... def __getattr__(self, item):
... if item == "formatted_name":
... print(f"__getattr__ is called for {item}")
... formatted = self.name.capitalize()
... setattr(self, "formatted_name", formatted)
... return formatted
... else:
... raiseAttributeError(f"no attribute of {item}")
...
... def __setattr__(self, key, value):
... print(f"__setattr__ is called for {key!r}: {value!r}")
... super().__setattr__(key, value)
...
>>> product = Product("taBLe")
__setattr__ is called for'name': 'taBLe'
>>> product.name
'taBLe'
>>> product.formatted_name
__getattr__ is called for formatted_name
__setattr__ is called for'formatted_name': 'Table'
'Table'
>>> product.formatted_name
'Table'
__setattr__
方法。若要正确使用它,必须通过使用 super()
使用超类方法。否则,它将陷入无限递归。__dict__
对象的一部分,因此不会调用 __getattr__
。__getattribute__
,它类似于 __getattr__
,但是每次访问属性时都会被调用。在这方面,它类似于 __setattr__
,同样,你应使用super()实现 __getattribute__
方法,以避免无限递归错误。小结
如果文章对你有帮助,欢迎转发/点赞/收藏~
作者:Yong Cui
原文:
https://medium.com/better-programming/5-pairs-of-magic-methods-in-python-you-should-know-f98f0e5356d6
_往期文章推荐_