你应该知道的5对python中的魔术方法

介绍 (Introduction)

When it comes to naming functions in Python, we can use underscores as well as letters and numbers. When underscores are used between words, they don’t mean much — they just help readability by creating spaces between words. This is known as the snake naming style. For instance, calculate_mean_score is easier to read than calculatemeanscore. Besides this common way of using underscores, as you may know, we also prefix function names with one or two underscores (e.g., _func, __func) to denote that these functions are intended for private use within the class or module. Names without an underscore prefix are considered public APIs.

在使用Python命名函数时,我们可以使用下划线以及字母和数字。 在单词之间使用下划线时,它们没有多大意义,它们只是通过在单词之间创建空格来提高可读性。 这就是蛇的命名方式。 例如, calculate_mean_scorecalculatemeanscore更易于阅读。 除了使用下划线,你可能知道这种常见的方式,我们也有一个或两个下划线(例如,前缀函数名_func__func )表示,这些功能是用于类或模块内的私人使用。 没有下划线前缀的名称被认为是公共API。

Another use of underscores in function naming are magic methods, also known as special methods. Specifically, we place two underscores before and two underscores after the function name — something like __func__. Because of the use of double underscores, some people refer to special methods as “dunder methods” or simply“ dunders.” In this article, I’d like to review five closely related pairs of common magic methods, with each pair representing a Python concept.

下划线在函数命名中的另一种用法是魔术方法,也称为特殊方法。 具体来说,我们在函数名称之前放置两个下划线,在函数名称之后放置两个下划线-类似于__func__ 。 由于使用了双下划线,因此某些人将特殊方法称为“弱点方法”或简称为“弱点”。 在本文中,我想回顾五对紧密相关的常用魔术方法,每对方法代表一个Python概念。

1.实例化:__new__和__init__ (1. Instantiation: __new__ and __init__)

After learning the basics of Python data structures (e.g., dictionaries, lists), you should have seen some examples of defining custom classes, where you have the very first contact with a magic method — __init__. This method is used to define the initialization behavior of the instance object. Specifically, in the __init__ method, you want to set the initial attributes for the created instance object. Here’s a simple example:

在学习了Python数据结构的基础知识(例如字典,列表)之后,您应该已经看到了一些定义自定义类的示例,在这些示例中,您第一次接触到了魔术方法__init__ 。 此方法用于定义实例对象的初始化行为。 具体来说,在__init__方法中,您想要为创建的实例对象设置初始属性。 这是一个简单的例子:

class Product:
    def __init__(self, name, price):
        self.name = name
        self.price = price

When we use the __init__ method, we don’t call it directly. Instead, the __init__ method becomes the building basis of the constructor method of the class, which has the same function signature as the __init__ method. For example, to create a new Product instance, use the following code:

当我们使用__init__方法时,我们不会直接调用它。 相反, __init__方法成为该类的构造函数方法的构建基础,该类的构造函数与__init__方法具有相同的功能签名。 例如,要创建一个新的Product实例,请使用以下代码:

product = Product("Vacuum", 150.0)

Closely related to the __init__ method is the __new__ method, which we don’t usually implement in our custom class. In essence, the __new__ method actually creates the instance object, which is passed to the __init__ method to complete the initialization process.

__init__方法密切相关的是__new__方法,我们通常不会在自定义类中实现该方法。 本质上, __new__方法实际上创建了实例对象,该实例对象被传递给__init__方法以完成初始化过程。

In other words, constructing a new instance object (a process known as instantiation) involves calling both __new__ and __init__ methods sequentially.

换句话说,构造新的实例对象(称为实例化的过程)涉及 依次 调用 __new__ __init__ 方法。

The following code shows you such a chain of reactions:

以下代码向您展示了这样的一系列React:

>>> class Product:
...     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.字符串表示形式:__repr__和__str__ (2. String Representation: __repr__ and __str__)

Both of these methods are important for setting up the proper string representations for your custom class. Before explaining them, let’s take a quick look at the implementation below:

这两种方法对于为自定义类设置正确的字符串表示形式都很重要。 在解释它们之前,让我们快速看一下下面的实现:

class Product:
    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}"

The __repr__ method should return a string that shows how the instance object can be created. Specifically, the string can be passed to eval() to re-construct the instance object. The following code snippet shows you such an operation:

__repr__ 方法应该返回,显示了如何用实例对象可以创建一个字符串。 具体来说,可以将字符串传递给eval()来重新构造实例对象。 以下代码段向您展示了这样的操作:

>>> product = Product("Vacuum", 150.0)
>>> repr(product)
"Product('Vacuum', 150.0)"
>>> evaluated = eval(repr(product))
>>> type(evaluated)
<class '__main__.Product'>

The __str__ method can return something more descriptive about the instance object. It should be noted that the __str__ method is used by the print() function to display the instance-related information, as shown below.

__str__ 方法可以返回更具描述性的有关实例对象。 应该注意的是, print()函数使用__str__方法来显示与实例相关的信息,如下所示。

>>> print(product)
Product: Vacuum, $150.00

Although both methods should return strings, the __repr__ method is usually intended for developers, so we want to show the instantiation information, while the __str__ method is for regular users, so we want to show something more informational.

尽管这两种方法都应返回字符串,但是__repr__方法通常是供开发人员使用的,因此我们希望显示实例化信息,而__str__方法是针对常规用户的,因此我们希望显示更多信息。

3.迭代:__ iter__和__next__ (3. Iteration: __iter__ and __next__)

One key operation that we can automate with our code is to repeat a certain job for us, the implementation of which involves the for loop as the logic flow. In terms of pertinent objects, it’s the iterable that can be used in a for loop. The very basic form of the for loop is shown below:

我们可以使代码自动化的一个关键操作是为我们重复执行一项工作,该工作的实现涉及到for循环作为逻辑流程。 就相关对象而言,它可以在for循环中使用。 for循环的最基本形式如下所示:

for item in iterable:
# Operations go here

Under the hood, the iterable is converted to an iterator, which presents items from the iterable for each loop. Generally speaking, iterators are Python objects that can be used to render items to be iterated. The conversion is done by implementing the __iter__ special method. In addition, retrieving the next item of the iterator involves the implementation of the __next__ special method. Let’s follow up with the previous example and make our Product class work as an iterator in a for loop:

在底层,可迭代对象转换为迭代器,该迭代器为每个循环显示可迭代对象。 一般来说,迭代器是Python对象,可用于渲染要迭代的项目。 转换是通过实现__iter__特殊方法完成的。 另外,检索迭代器的下一项涉及__next__特殊方法的实现。 让我们继续前面的示例,并使我们的Product类在for循环中作为迭代器工作:

>>> class Product:
...     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:
...             raise StopIteration("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

As shown above, we create a list of object that holds the free samples in the __iter__ method, which make an iterator for the custom class instance. To implement the iteration behavior, we implement the __next__ method by presenting the object from the list of free samples. The iteration ends when we run out of free samples.

如上所示,我们创建了一个对象列表,该对象在__iter__方法中保存了免费样本,这些样本构成了自定义类实例的迭代器。 为了实现迭代行为,我们通过提供免费样本列表中的对象来实现__next__方法。 当我们用完免费样本时,迭代结束。

4.上下文管理器:__ enter__和__exit__ (4. Context Manager: __enter__ and __exit__)

When we deal with file objects in Python, the most frequent syntax you may have come across is probably something like this:

当我们使用Python处理文件对象时,您可能遇到的最常见的语法可能是这样的:

with open('filename.txt') as file:
# Your file operations go here

The use of the with statement is known as the context manager technique. Specifically, in the file operation example above, the with statement will create a context manager for the file object and after the file operations, the context manager will help us close the file object such that the shared resource (i.e., the file) can become available to other processes.

with语句的使用被称为上下文管理器技术。 具体来说,在上面的文件操作示例中, with语句将为文件对象创建一个上下文管理器,并且在文件操作之后,上下文管理器将帮助我们关闭文件对象,以便共享资源(即文件)可以成为可用于其他进程。

So, in general, context managers are Python objects that manage shared resources, such as open and close, for us. Without them, we have to manage them manually, which can be error-prone.

因此, 通常来说,上下文管理器是Python对象 ,它们为我们管理共享资源 ,例如打开和关闭。 没有它们,我们必须手动管理它们,这很容易出错。

To implement such behavior with a custom class, our class needs to implement the __enter__ and __exit__ methods. The __enter__ method sets up the context manager that prepares the needed resource for us to operate on, while the __exit__ method is to clean up any used resources that should be released to make them available. Let’s consider the following trivial example with the previous Product class:

要使用自定义类实现这种行为,我们的类需要实现__enter____exit__方法。 __enter__方法设置上下文管理器,该上下文管理器为我们进行操作准备所需的资源,而__exit__方法则是清理应释放的所有已使用资源以使其可用。 让我们考虑下面的简单示例,其中包含先前的Product类:

>>> class Product:
...     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.

As you can see, when the instance object is embedded in a with statement, the __enter__ method is called. When the operation is completed in the with statement, the __exit__ method is called.

如您所见,当实例对象嵌入在with语句中时,将__enter__方法。 在with语句中完成操作后,将__exit__方法。

However, it should be noted that we can implement the __enter__ and __exit__ methods to create context managers. It can be done much more easily with the context manager decorator function — you can find more information on this topic in my earlier article.

但是,应该注意,我们可以实现__enter____exit__方法来创建上下文管理器。 使用上下文管理器装饰器功能可以更轻松地完成它-您可以在我之前的文章中找到有关此主题的更多信息。

5.更精细的属性访问控制:__getattr__和__setattr__ (5. Finer Attribute Access Control: __getattr__ and __setattr__)

If you have programming experience in other languages, you may have been used to set explicit getters and setters for instance attributes. In Python, we don’t need to use these access control techniques for each individual attribute. However, it’s possible that we can have some control by implementing the __getattr__ and __setattr__ methods. Specifically, the __getattr__ method is called when attributes of an instance object are accessed, while the __setattr__ method is called when we’re setting attributes of an instance object.

如果您有其他语言的编程经验,则可能已习惯于为实例属性设置显式的getter和setter。 在Python中,我们不需要为每个单独的属性使用这些访问控制技术。 但是,可能可以通过实现__getattr____setattr__方法来进行控制。 具体来说,当访问实例对象的属性时,将调用__getattr__方法,而当我们设置实例对象的属性时,将调用__setattr__方法。

>>> class Product:
...     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:
...             raise AttributeError(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'

The __setattr__ method is called every time we try to set an attribute of the object. To use it correctly, you have to use the superclass method by using super(). Otherwise, it will be running into an infinite recursion.

每次尝试设置对象的属性时,都会调用__setattr__方法。 若要正确使用它,必须通过使用super()使用超类方法。 否则,它将陷入无限递归。

After setting the formatted_name attribute, the attribute will become part of the __dict__ object, so __getattr__ won’t be called.

设置formatted_name属性后,该属性将成为__dict__对象的一部分,因此将不会调用__getattr__

As a side note, there’s another special method closely related to access control called __getattribute__, which is similar to __getattr__, but is called every time when an attribute is accessed. In this regard, it’s similar to __setattr__ — likewise, you should use super() to implement the __getattribute__ method to avoid infinite recursion error.

附带说明一下,还有另一个与访问控制紧密相关的特殊方法,称为__getattribute__ ,它类似于__getattr__ ,但是每次访问属性时都会调用它。 在这方面,它类似于__setattr__同样,您应该使用super()实现__getattribute__方法,以避免无限递归错误。

结论 (Conclusion)

In this article, we reviewed five important pairs of special methods, with which we learned five Python concepts that are related to each of these pairs. I hope that you have a better understanding of these concepts and how to use special methods in your own Python projects.

在本文中,我们回顾了五对重要的特殊方法,通过它们我们学习了与这两个对有关的五个Python概念。 我希望您对这些概念以及如何在自己的Python项目中使用特殊方法有更好的理解。

翻译自: https://medium.com/better-programming/5-pairs-of-magic-methods-in-python-you-should-know-f98f0e5356d6

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值