Python 常用的魔法方法

Python 常用魔法方法

https://pyzh.readthedocs.io/en/latest/python-magic-methods-guide.html#id2

为了实现更好的可扩展性,Python 语言提供了大量的特殊方法,它们大致可分为以下几类:

  • 特性访问(Attribute Access)这类特殊方法实现了对象的特性访问,使用方法为 object.attribute ,既可以用来赋值,也可以在 del 语句中执行删除操作。

  • 可调用对象(Callables): 这个方法的适用对象为参数,就像 Python 内部的 len() 函数

  • 集合(Collections)这类方法提供了很多集合操作的功能。类似这类方法的使用有 sequence[index]、mapping[key] 和 some_set | another_set

  • 数字(Numbers): 这类方法提供了大量的数学运算符和比较运算符。

  • 上下文(Context):这类函数通常使用 with 语句来实现上下文的管理

  • 迭代器(Iterator):可以使用这类方法定义迭代器

构造方法

__new__

在我们调用 x=SomeClass() 的时候,__init__ 并不是第一个被调用的方法,事实上,第一个被调用的是 __new__ ,这个方法才真正地创建了实例。当这个对象的生命周期结束的时候,__del__ 会被调用。

  • __new__(cls, [...])

    __new__ 是对象实例化时第一个调用的方法,它只取下 cls 参数,并将其它参数传给 __init____init__ 很少使用,但是它也有它合适的场景,尤其是当类继承自一个像元组或者字符串这样不经常改变的类型的时候。但是其并不是很有用。

__init__

  • 对象的初始化

  • __init__ 方法的参数可以多种形式来完成赋值

object 类是所有类的基类,对于任何自定义类,都会隐式继承 object。

1
2
3
4
5
6
class X:
pass
X.__class__
<class 'type'>
X.__class__.__base__
<class 'object'>

对于每个 __init__方法,都应当显式地指定要初始化的变量。

__del__

__new____init__ 是对象的构造器,__del__是对象的销毁器。它并非实现了语句 del x

而是定义了当对象被垃圾回收时的行为。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# __new__ 方法实现单例模式
class SingleNode(object):
_instance = None
init_flag = None
def __new__(cls, *args, **kwargs):
if cls._instance is None:
cls._instance = super().__new__(cls)
return cls._instance

def __init__(self):
if not SingleNode.init_flag:
self.do_something()
SingleNode.init_flag = True

def do_something(self):
print("do something...")


if __name__ == '__main__':
single = SingleNode()
print(single, id(single))
single2 = SingleNode()
print(single2, id(single))

集合、序列相关

有许多办法可以让你的 Python 类表现得像是内建序列类型(字典,元组,列表,字符串等)

在 Python 中,协议完全是非正式的,也不需要显式的声明,事实上,它们更像是一种参考标准。

在 Python 中实现自定义容器类型需要用到一些协议。首先,不可变容器类型有如下协议:想实现一个不可变容器,你需要定义__len____getitem__ 。可变容器的协议除了上面提到的两个方法之外,还需要定义 __setitem____delitem__ 。最后,如果你想让你的对象可迭代,你需要定义__iter__,这个方法返回一个迭代器。迭代器必须遵守迭代器协议,需要定义 __iter__ 和 next 方法。

  • __len__(self)

    返回容器的长度,可变和不可变类型都需要实现

  • __getitem__(self, key)

    定义对容器中某一项使用 self[key] 的方式读取操作时的行为。这也是可变和不可变容器类型都需要实现的一个方法。它应该在键的类型错误时产生 TypeError 异常,同时在没有与键值相匹配的内容时产生 KeyError 异常

  • __setitem__(self, key)

    定义对容器中某一项使用 self[key] 的方式进行赋值操作时的行为。它是可变容器类型必须实现的一个方法,同样应该在合适的时候产生 KeyError 和 TypeError 异常

  • __reversed__(self)

    定义了对容器使用 reversed() 内建函数时的行为。它应该返回一个反转之后的序列。

  • __contains__(self, item)

    定义了使用 in 和 not in 进行成员测试时的行为。

  • __missing__(self, key)

    __missing__ 在字典的子类中使用,它定义了当试图访问一个字典中不存在的键的行为。例如 d[“george”] 中不存在 george key 时,就会调用 d.__missing__

迭代相关

  • __iter__

    它应该返回当前容器的一个迭代器。迭代器以一连串内容的形式返回,最常见的是使用

    iter() 函数调用,以及在类似 for x in container 的循环中被调用。

  • __next__
    这个方法每次返回迭代的值,在没有可迭代元素的时候,抛出 StopIteration 异常
    实现迭代器协议的例子:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    class A:
    """A 实现了迭代器协议 它的实例就是一个迭代器"""
    def __init__(self, n):
    self.idx = 0
    self.n = n

    def __iter__(self):
    print("__iter__")
    return self

    def __next__(self):
    if self.idx < self.n:
    val = self.idx
    self.idx += 1
    return val
    else:
    raise StopIteration()

    可调用对象

比较操作符

使用 Python 魔法方法的一个巨大优势就是可以构建一个拥有 Python 内置类型行为的对象。这意味着你可以避免使用非标准的、丑陋的方式来表达简单的操作。

  • __cmp__是所有比较魔法方法中最基础的一个,它实际上定义了所有比较操作符的行为(<, ==, != 等等)
  • __lt__ __le__() __eq__ __ne__ __gt__() __get__

访问控制 && 属性相关

Python 不是通过显式定义的字段和方法修改器,而是通过魔法方法实现了一系列的封装

  • _getattr__(self, name)

    当用户试图访问一个根本不存在的属性时,你可以通过这个魔法方法来定义类的行为。这个可以用于捕捉错误的拼写并且给出指引。只有当试图访问不存在的属性时它才会被调用。

  • __setattr__(self, name, value)

    它可以用于真正意义上的封装。允许你自定义某个属性的赋值行为,不管这个属性存在与否,也就是说你可以对任意属性的任何变化都定义自己的规则。

  • __delattr__(self, name)

    用于处理删除属性时的行为。和 __setattr__一样,使用它时也需要多加小心,防止产生无限递归

  • __getattribute__看起来和上面那些方法很合得来,但是最好不要使用它。

    允许你自定义属性被访问时的行为,它也同样可能遇到无限递归问题。__getattribute__基本上可以替代__getattr__ 只有当它被使用,并且显式地被调用,或者产生 AttributeError 时它才被调用。不推荐被使用

与 Python 无缝集成——基本特殊方法

Python 中有一些特殊方法,它们允许我们的类和 Python 更好地集成。在标准库参考中,它们被称为基本特殊方法,是与 Python 的其他特性无缝集成的基础

例如:

  • __repr__ __str__ 标准化表示对象的值
  • __hash__ __bool__ __byts__ 转换方法
  • __lt__ __le__() __eq__ __ne__ __gt__() __get__
  • __new__ __del__

__repr__&& __str__ 方法

对于一个对象,Python 提供了两种字符串表示方法。它们和内建函数 repr() str() print string.format() 功能是一致的

https://stackoverflow.com/questions/1436703/what-is-the-difference-between-str-and-repr

  • 通常,str() 方法表示的对象对用户更加友好。这个方法是由对象的 __str__ 方法实现的

  • repr() 方法的表示通常会更加技术化,甚至有可能是一个完整的 Python 表达式

  • 容器 __str__ 使用包含的对象 __repr__

默认情况下,二者的行为一致

1
2
3
4
5
6
>>> import datetime
>>> today = datetime.datetime.now()
>>> str(today)
'2012-03-14 09:21:58.130922'
>>> repr(today)
'datetime.datetime(2012, 3, 14, 9, 21, 58, 130922)'

上下文管理器

上下文管理的概念在 Python 中并不是全新引入的(之前它作为标准库的一部分实现),直到 PEP 343 才被接受,它才成为一种一级的语言结构

类似:

1
2
with. open("f.txt") as bar:
f.read()

当对象使用 with 声明创建时,上下文管理器允许类做一些设置和清理工作。上下文管理器的行为由下面两个魔法方法所定义:

  • __enter__

    定义使用 with 声明创建的语句块最开始上下文管理器应该做些什么。__enter__ 的返回值会赋给 with 声明的目标,也就是 as 之后的东西。

  • __exit__

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class MyContext:
def __init__(self, name):
self.name = name

def __enter__(self):
# __enter__的返回值被as后面的变量接收
self.name = f'Big {self.name}'
return self

def __exit__(self, exc_type, exc_val, exc_tb):
# __exit__在with中的语句结束之后立即执行
# 假如with管辖的上下文内没有抛出任何异常,当解释器出发__exit__方法时,其三个参数都为None,如果有异常抛出则三个参数会有具体内容
# exc_type:异常类型,exc_value:异常对象,traceback:错误的堆栈对象
# __exit__返回值:True:异常被忽略, False:异常正常抛出
print('Exiting MyContext')
return False


if __name__ == '__main__':
with MyContext('王大锤') as me:
print(me.name)

refs

https://pyzh.readthedocs.io/en/latest/python-magic-methods-guide.html#id5

-------------THANKS FOR READING-------------