Python文档随笔

坐标

文档

__new__

如果 __new__() 在构造对象期间被发起调用并且它返回了一个实例或 cls 的子类,则新实例的 __init__() 方法将以 __init__(self[, …]) 的形式被发起调用,其中 self 为新实例而其余的参数与被传给对象构造器的参数相同。

如果 __new__() 未返回一个 cls 的实例,则新实例的 __init__() 方法就不会被执行。

__new__() 的目的主要是允许不可变类型的子类 (例如 int, str 或 tuple) 定制实例创建过程。它也常会在自定义元类中被重载以便定制类创建过程。

__init__

在实例 (通过 __new__()) 被创建之后,返回调用者之前调用。其参数与传递给类构造器表达式的参数相同。一个基类如果有 __init__() 方法,则其所派生的类如果也有 __init__() 方法,就必须显式地调用它以确保实例基类部分的正确初始化;例如: super().__init__([args…]).

因为对象是由 __new__() 和 __init__() 协作构造完成的 (由 __new__() 创建,并由 __init__() 定制),所以 __init__() 返回的值只能是 None,否则会在运行时引发 TypeError。

__del__

在实例将被销毁时调用。这还会调用终结器或析构器 (不适当)。如果一个基类具有 __del__() 方法,则其所派生的类如果也有 __del__() 方法,就必须显式地调用它以确保实例基类部分的正确清除。

注意!del x 并不直接调用 x.__del__() — 前者会将 x 的引用计数减一,而后者仅会在 x 的引用计数变为零时被调用。

富比较(rich comparison)

  • object.__lt__(self, other)
  • object.__le__(self, other)
  • object.__eq__(self, other)
  • object.__ne__(self, other)
  • object.__gt__(self, other)
  • object.__ge__(self, other)

以上这些被称为“富比较”方法。运算符号与方法名称的对应关系如下:x<y 调用 x.__lt__(y)、x<=y 调用 x.__le__(y)、x==y 调用 x.__eq__(y)、x!=y 调用 x.__ne__(y)、x>y 调用 x.__gt__(y)、x>=y 调用 x.__ge__(y)。

如果指定的参数对没有相应的实现,富比较方法可能会返回单例对象 NotImplemented。按照惯例,成功的比较会返回 False 或 True。不过实际上这些方法可以返回任意值,因此如果比较运算符是要用于布尔值判断(例如作为 if 语句的条件),Python 会对返回值调用 bool() 以确定结果为真还是假。

在默认情况下 __ne__() 会委托给 __eq__() 并将结果取反,除非结果为 NotImplemented。比较运算符之间没有其他隐含关系,例如 (x<y or x==y) 为真并不意味着 x<=y。这最后一句话我想了半天为什么不相等,又去看了英文版的文档,最后在so上面找到答案(还是文档看少了)。

>>> class eq:
...     def __init__(self, value):
...         self.value = value
...     def __eq__(self, other):
...         return self.value == other.value
...     def __lt__(self, other):
...         return self.value < other.value
...
>>> q = eq(1)
>>> w = eq(2)
>>> q==w
False
>>> q<w
True
>>> q<=w
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: '<=' not supported between instances of 'eq' and 'eq'

__hash__

如果一个类没有定义 __eq__() 方法,那么也不应该定义 __hash__() 操作;如果它定义了 __eq__() 但没有定义 __hash__(),则其实例将不可被用作可哈希集的项。如果一个类定义了可变对象并实现了 __eq__() 方法,则不应该实现 __hash__(),因为可哈希集的实现要求键的哈希集是不可变的(如果对象的哈希值发生改变,它将处于错误的哈希桶中)。

用户定义的类默认带有 __eq__() 和 __hash__() 方法;使用它们与任何对象(自己除外)比较必定不相等,并且 x.__hash__() 会返回一个恰当的值以确保 x == y 同时意味着 x is y 且 hash(x) == hash(y)。

一个类如果重载了 __eq__() 且没有定义 __hash__() 则会将其 __hash__() 隐式地设为 None。当一个类的 __hash__() 方法为 None 时,该类的实例将在一个程序尝试获取其哈希值时正确地引发 TypeError,并会在检测 isinstance(obj, collections.abc.Hashable) 时被正确地识别为不可哈希对象。

如果一个重载了 __eq__() 的类需要保留来自父类的 __hash__() 实现,则必须通过设置 __hash__ = <ParentClass>.__hash__ 来显式地告知解释器。

如果一个没有重载 __eq__() 的类需要去掉哈希支持,则应该在类定义中包含 __hash__ = None。一个自定义了 __hash__() 以显式地引发 TypeError 的类会被 isinstance(obj, collections.abc.Hashable) 调用错误地识别为可哈希对象。

__bool__

调用此方法以实现真值检测以及内置的 bool() 操作;应该返回 False 或 True。如果未定义此方法,则会查找并调用 __len__() 并在其返回非零值时视对象的逻辑值为真。如果一个类既未定义 __len__() 也未定义 __bool__() 则视其所有实例的逻辑值为真。

模拟可调用对象

object.__call__(self[, args…])
此方法会在实例作为一个函数被“调用”时被调用;如果定义了此方法,则 x(arg1, arg2, …) 就相当于 x.__call__(arg1, arg2, …) 的快捷方式。

模拟容器类型

可以定义下列方法来实现容器对象。 容器通常属于序列(如列表或元组)或映射(如字典),但也存在其他形式的容器。 前几个方法集被用于模拟序列或是模拟映射;两者的不同之处在于序列允许的键应为整数 k 且 0 <= k < N 其中 N 是序列或定义指定区间的项的切片对象的长度。 此外还建议让映射提供 keys(), values(), items(), get(), clear(), setdefault(), pop(), popitem(), copy() 以及 update() 等方法,它们的行为应与 Python 标准字典对象的相应方法类似。 此外 collections.abc 模块提供了一个 MutableMapping 抽象基类以便根据由 __getitem__(), __setitem__(), __delitem__(), 和 keys() 组成的基本集来创建所需的方法。可变序列还应像 Python 标准列表对象那样提供 append(), count(), index(), extend(), insert(), pop(), remove(), reverse() 和 sort() 等方法。 最后,序列类型还应通过定义下文描述的 __add__(), __radd__(), __iadd__(), __mul__(), __rmul__() 和 __imul__() 等方法来实现加法(指拼接)和乘法(指重复);它们不应定义其他数值运算符。 此外还建议映射和序列都实现 __contains__() 方法以允许高效地使用 in 运算符;对于映射,in 应该搜索映射的键;对于序列,则应搜索其中的值。 另外还建议映射和序列都实现 __iter__() 方法以允许高效地迭代容器中的条目;对于映射,iter() 应当迭代对象的键;对于序列,则应当迭代其中的值。

__len__

调用此方法以实现内置函数 len()。应该返回对象的长度,以一个 >= 0 的整数表示。此外,如果一个对象未定义 __bool__() 方法而其 __len__() 方法返回值为零,则在布尔运算中会被视为假值。

__length_hint__

调用此方法以实现 operator.length_hint()。 应该返回对象长度的估计值(可能大于或小于实际长度)。 此长度应为一个 >= 0 的整数。 返回值也可以为 NotImplemented,这会被视作与 __length_hint__ 方法完全不存在时一样处理。 此方法纯粹是为了优化性能,并不要求正确无误。

__getitem__

调用此方法以实现 self[key] 的求值。对于序列类型,接受的键应为整数和切片对象(slice())。请注意负数索引(如果类想要模拟序列类型)的特殊解读是取决于 __getitem__() 方法。如果 key 的类型不正确则会引发 TypeError 异常;如果为序列索引集范围以外的值(在进行任何负数索引的特殊解读之后)则应引发 IndexError 异常。对于映射类型,如果 key 找不到(不在容器中)则应引发 KeyError 异常。

__setitem__

调用此方法以实现向 self[key] 赋值。注意事项与 __getitem__() 相同。为对象实现此方法应该仅限于需要映射允许基于键修改值或添加键,或是序列允许元素被替换时。不正确的 key 值所引发的异常应与 __getitem__() 方法的情况相同。

__delitem__

调用此方法以实现 self[key] 的删除。注意事项与 __getitem__() 相同。为对象实现此方法应该权限于需要映射允许移除键,或是序列允许移除元素时。不正确的 key 值所引发的异常应与 __getitem__() 方法的情况相同。

__missing__

此方法由 dict.__getitem__() 在找不到字典中的键时调用以实现 dict 子类的 self[key]。(collections.defaultdict就是这样实现的)

__iter__

此方法在需要为容器创建迭代器时被调用。此方法应该返回一个新的迭代器对象,它能够逐个迭代容器中的所有对象。对于映射,它应该逐个迭代容器中的键。

迭代器对象也需要实现此方法;它们需要返回对象自身

__reversed__

此方法(如果存在)会被 reversed() 内置函数调用以实现逆向迭代。它应当返回一个新的以逆序逐个迭代容器内所有对象的迭代器对象。

如果未提供 __reversed__() 方法,则 reversed() 内置函数将回退到使用序列协议 (len() 和 __getitem__())。支持序列协议的对象应当仅在能够提供比 reversed() 所提供的实现更高效的实现时才提供 __reversed__() 方法。

__contains__

成员检测运算符 (in 和 not in) 通常以对容器进行逐个迭代的方式来实现。 不过,容器对象可以提供这个特殊方法并采用更有效率的实现,这样也不要求对象必须为可迭代对象。

调用此方法以实现成员检测运算符。如果 item 是 self 的成员则应返回真,否则返回假。对于映射类型,此检测应基于映射的键而不是值或者键值对。

对于未定义 __contains__() 的对象,成员检测将首先尝试通过 __iter__() 进行迭代,然后再使用 __getitem__() 的旧式序列迭代协议。

with 语句上下文管理器

上下文管理器的典型用法包括保存和恢复各种全局状态,锁定和解锁资源,关闭打开的文件等等。

特殊方法查找

对于自定义类来说,特殊方法的隐式发起调用仅保证在其定义于对象类型中时能正确地发挥作用,而不能定义在对象实例字典中。 该行为就是以下代码会引发异常的原因。:

>>> class C:
...     pass
...
>>> c = C()
>>> c.__len__ = lambda: 5
>>> len(c)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: object of type 'C' has no len()

此行为背后的原理在于包括类型对象在内的所有对象都会实现的几个特殊方法,例如 __hash__() 和 __repr__()。 如果这些方法的隐式查找使用了传统的查找过程,它们会在对类型对象本身发起调用时失败:

>>> 1 .__hash__() == hash(1)
True
>>> int.__hash__() == hash(int)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: descriptor '__hash__' of 'int' object needs an argument

以这种方式不正确地尝试发起调用一个类的未绑定方法有时被称为‘元类混淆’,可以通过在查找特殊方法时绕过实例的方式来避免:

>>> type(1).__hash__(1) == hash(1)
True
>>> type(int).__hash__(int) == hash(int)
True

除了为了正确性而绕过任何实例属性之外,隐式特殊方法查找通常也会绕过 __getattribute__() 方法,甚至包括对象的元类:

>>> class Meta(type):
...     def __getattribute__(*args):
...         print("Metaclass getattribute invoked")
...         return type.__getattribute__(*args)
...
>>> class C(object, metaclass=Meta):
...     def __len__(self):
...         return 10
...     def __getattribute__(*args):
...         print("Class getattribute invoked")
...         return object.__getattribute__(*args)
...
>>> c = C()
>>> c.__len__()                 # Explicit lookup via instance
Class getattribute invoked
10
>>> type(c).__len__(c)          # Explicit lookup via type
Metaclass getattribute invoked
10
>>> len(c)                      # Implicit lookup
10

以这种方式绕过 __getattribute__() 机制为解析器内部的速度优化提供了显著的空间,其代价则是牺牲了处理特殊方法时的一些灵活性(特殊方法 必须 设置在类对象本身上以便始终一致地由解释器发起调用)。

这一小节特别绕,我也是看了这个答案才看懂的,下面通俗简单的解释下咯。定义 fidoDog 类的一个实例,但我们调用 fido.walk() 时,Python 会先在实例 fido 里寻找 walk 函数(fido.__dict__ 里),然后以无参数的形式调用它:

def walk():
   print "Yay! Walking! My favourite thing!"

fido.walk = walk

如果我们没有给实例 fido 定义 walk 函数呢?Python 会去 type(fido) (也就是Dog)寻找名为 walk 的属性,然后以自己这个实例作为第一个参数(self)调用它:

class Dog:
    def walk(self):
         print "Yay! Walking! My favourite thing!"

但是当我们调用的函数是特殊函数的隐式调用时,比如说我想调用 repr(fido) ,这是特殊方法 __repr__ 的隐式调用,假如类的实现是这样的:

class Dog:
    def __repr__(self):
          return 'Dog()'

按照我们上面传统的查找过程, fido 自己的字典没有实现 __repr__ 的方法,所以调用 Dog__repr__,看到这里暂时都没问题。但是当我们想要这样调用呢 repr(Dog) ,同样按照上面传统的查找过程,我们在 Dog.__dict__ 里发现了 __repr__ 的项,于是会’这样’调用 Dog.__repr__() ,然后会因为缺少一个参数而报错:

>>> Dog.__repr__()
Traceback (most recent call last):
  File "<pyshell#38>", line 1, in <module>
    Dog.__repr__()
TypeError: __repr__() takes exactly 1 argument (0 given)

当然我们可以对 Dog 里的 __repr__ 的实现做一些处理(默认参数),避免报错,但是没必要。 Python 帮助我们使这个过程变的更简便,这个时候你回到这一小节的第一句话,便能看懂啦。总结一句话就是, repr(foo) 跳过在 foo 实例里查找的过程,而是直接 type(foo).__repr__(foo)

后记

因为基本上都是直接从文档上搬下来的,但是直接复制一些魔法方法的下划线会和MK的语法冲突,所以写了个函数方便:

import re
def my_convert(text)
	return(re.sub('(?P<former>[ .])__', '\g<former>\\_\\_', text))
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值