在Python中用双下划线__包裹起来的方法被成为magic method,可以用来给类提供算术、逻辑运算等功能,让这些类能够像原生的对象一样用更标准、简洁的方式进行这些操作。
构造方法
__new__(cls, […])
__new__是Python中对象实例化时所调用的第一个函数,在__init__之前被调用。__new__将class作为他的第一个参数, 并返回一个这个class的 instance。而__init__是将 instance 作为参数,并对这个 instance 进行初始化操作。每个实例创建时都会调用__new__函数,但不一定会调用__init__,比如从pickle中载入一个对象的时候。
# PEP中重写__new__的例子
class inch(float):
"Convert from inch to meter"
def __new__(cls, arg=0.0):
return float.__new__(cls, arg*0.0254)
print inch(12)
通过上面这个例子可以创建一个12 inch但是以米为单位的新的实例,并输出其值。不可变对象(比如float)的值是在调用__new__的时候确定的,因此不能改变一个已经存在的不可变对象的值,这保证了它们的immutability。
__new__的属性:
__new__是静态方法, 而不是类方法- 第一个参数必须是一个类,其余的参数被传入构造方法
- 新的
__new__方法会以一个 class 为参数重写基类的__new__方法,如果要使用基类的 class 需要事先获取一个基类的对象。__new__会调用基类的__new__方法 - 对于小值的不可变类,
__new__可能会返回一个与你所需要的值相同的被缓存的对象(已完成初始化)。如果要实现一个不可变类型的可变子类,应该在__init__中进行值的初始化
__new__实现单例模式:
先构造一个Singleton类,然后继承这个类去构造其他的类, 这样就可以使每个子类都只有一个实例。
class Singleton(object):
def __new__(cls, *args, **kwds):
it = cls.__dict__.get("__it__")
if it is not None:
return it
cls.__it__ = it = object.__new__(cls)
it.init(*args, **kwds)
return it
def init(self, *args, **kwds):
pass
Singleton的子类应该重写 init方法,而不是 __init__,因为每次构造都会调用__init__
class MySingleton(Singleton):
def init(self):
print("calling init")
def __init__(self):
print("calling __init__")
进行测试:
>>> a = MySingleton()
calling init
calling __init__
>>> b = MySingleton()
calling __init__
>>> assert a == b
>>>
说明此时的a和b是同一个实例
__init__与__del__
__init__和__new__一起组成了对象的构造器,__init__是类的初始化方法,将会接收到传给构造器的参数,比如a = MyClass("arg1", "arg2")中的”arg1”和”arg2”将会直接传给__init__
__del__是对象的析构器, 定义了对象被垃圾回收时的行为,可以用来在销毁对象时做一些处理,比如关闭socket或文件对象。但是当Python解释器退出,对象仍然存活时__del__不会执行,因此最好还是手动关闭连接。
from os.path import join
class FileObject:
"""Ensure file is closed when deleted"""
def __init__(self, filepath='~', filename='sample.txt'):
self.file = open(join(filepath, filename), 'r+')
def __del__(self):
self.file.close()
del self.file
__del__并非实现del x语句,不等同于x.__del__()而只是定义了销毁时的行为。
操作符
定义操作符相关的magic methods可以使一个对象像内建类型的对象一样进行操作,实现instance + insatnce 或 instance == instance等行为
比较操作符
| method | function |
|---|---|
__cmp__(self, other) |
进行比较操作,在self < other时返回一个负整数, 在self == other时返回0, 在self > other时返回一个正整数。⚠️Python3中已经放弃了这种方式,需要实现其他的所有方法进行代替 |
__eq__ |
定义 == 的行为 |
__ne__ |
定义 != 的行为 |
__lt__ |
定义 < 的行为 |
__gt__ |
定义 > 的行为 |
__le__ |
定义 <= 的行为 |
__ge__ |
定义 >= 的行为 |
例子:
使用一个字符串构造MyClass类的对象,然后用这个字符串的长度进行比较操作
class MyClass:
def __init__(self, s):
self.content = s
self.length = len(self.content)
def __gt__(self, other):
return self.length > other.length
def __lt__(self, other):
return self.length < other.length
def __ge__(self, other):
return self.length >= other.length
def __le__(self, other):
return self.length <= other.length
>>> a = MyClass("stirng1")
>>> b = MyClass("str2")
>>> a >= b
True
>>> a <= b
False
>>> a > b
True
>>> a < b
False
使用functools中的total_ordering类装饰器可以只需自己定义__eq__或__ne__和另外任意一个比较操作就能实现所有的比较
from functools import total_ordering
@total_ordering
class MyClass:
def __init__(self, s):
self.content = s
self.length = len(self.content)
def __eq__(self, other):
return self.length == other.length
def __ge__(self, other):
return self.length >= other.length
数值操作符
一元操作符
| method | function |
|---|---|
__pos__(self) |
取正操作 |
__neg__(self) |
取负操作 |
__abs__(self) |
abs()操作 |
__invert__(self) |
取反~操作 |
__round__(self, n) |
round()操作, n是小数点的位数 |
__floor__(self) |
math.floor()向下取整 |
__ceil__(self) |
math.ceil()向上取整 |
__trunc__(self) |
math.trunc()绝对值最小的整数 |
算术操作
| method | function |
|---|---|
__add__(self, other) |
加法(+)操作 |
__sub__(self, other) |
减法(-)操作 |
__mul__(self, other) |
乘法(*)操作 |
__div__(self, other) |
除法(/)操作 |
__floordiv__(self, other) |
地板除(//) |
__truediv__(self, other) |
真正的除法, 不同除法的关系下面会解释 |
__mod__(self, other) |
取余(%)操作 |
__divmod__(self, other) |
divmod(a, b)操作, 结果和(a // b, a % b)相同 |
__pow__(self, other) |
幂(**)操作 |
__lshift__(self, other) |
左移位(<<) |
__rshift__(self, other) |
右移位(>>) |
__and__(self, other) |
按位与(&) |
__or__(self, other) |
按位或 |
__xor__(self, other) |
按位异或(^) |
Python不同版本的除法:
| Operator | 2.1- | 2.2+ | 3.x |
|---|---|---|---|
| / | classic | classic | true |
| // | n/a | floor | floor |
classic 以一种两个数或者多个数出现一个浮点数结果就以浮点数的形式表示
floor 不管两者出现任何数,都以整除结果为准,不对小数部分进行处理,直接抛弃
true 则是真正的除法,即使参数中没有小数,不能整除时也会用小数表示(classic中将抛弃)
如果要在Python 2.x中使用真除法需要先from __future__ import division
反射算术运算
反射算术运算和前面的算术操作是一一对应的,只不过是交换了两个操作数的位置,大多数定义的反射算术运算只是调用了相应的算术运算。在一个运算arg1 + arg2中只有当 arg1 没有定义__add__时才会调用 arg2 的__radd__
反射算术运算有 __radd__, __rsub__, __rmul__, __rfloordiv__, __rdiv__, __rtruediv__, __rmod__, __rdivmod__, __rpow__, __rlshift__,
__rrshift__, __rand__, __ror__, __rxor__
增强赋值运算
增强赋值操作将运算和赋值相结合,例如+=, -=这些操作
| method | function |
|---|---|
__iadd__ |
加法赋值(+=) |
__isub__ |
减法赋值(-=) |
__imul__ |
乘法赋值(*=) |
__ifloordiv__ |
整除赋值(//=) |
__idiv__ |
除法赋值(/=) |
__itruediv__ |
真除法赋值 |
__imod__ |
取余赋值(%=) |
__ipow__ |
**= |
__ilshift__ |
左移位赋值(<<=) |
__irshift__ |
右移位赋值(>>=) |
__iand__ |
按位与赋值(&=) |
__ior__ |
按位或赋值 |
__ixor__ |
按位异或赋值(^=) |
类型转换
定义类型转换的操作之后可以通过int(), float()等内建的类型转换函数进行操作
| method | function |
|---|---|
__int__(self) |
int() |
__long__(self) |
long() |
__float__(self) |
float() |
__complex__(self) |
complex() |
__oct__(self) |
转换成八进制 |
__hex__(self) |
转换成十六进制 |
__index__(self) |
作为切片表达式时到整数的转换 |
__coerce__(self, other) |
混合模式运算下的转换,将other和self转换成相同类型并返回 |
例子:
class MyNum:
def __init__(self, num):
self.value = num
def __int__(self):
return int(self.value)
def __repr__(self):
return "Type: {}; Value: {}".format(type(self.value), self.value)
>>> a = MyNum(1.0)
>>> a
Type: <class 'float'>; Value: 1.0
>>> int(a)
1
类
| method | function |
|---|---|
__str__(self) |
str() 时的行为 |
__repr__(self) |
repr() 时的行为。repr() 通常产生机器可读的输出,而 str() 则产生人类可读的输出。 |
__unicode__(self) |
unicode() 时的行为。 与str() 类似,但只返回unicode字符串。⚠️ Python3中默认为unicode, 因此没有这个方法 |
__format__(self) |
用于新式字符串格式化时的行为,Object: {}”.format(obj) 会导致调用 obj.__format__() 。 |
__hash__(self) |
hash() 时的行为。它必须返回一个整数。 |
__nonzero__(self) |
bool() 时的行为,应返回True或False。 |
__dir__(self) |
dir() 时的行为,返回一个属性列表。在定义了__getattr__、__getattribute__、使用动态生成的属性等情况下才需要实现这个方法。 |
例子:
class MyClass:
def __init__(self, name):
self.name = name
def __hash__(self):
return hash(self.name)
def __nonzero__(self):
return len(self.name)
def __repr__(self):
return "Name: " + self.name
def __str__(self):
return "Str: " + self.name
>>> a = MyClass("somebody")
>>> bool(a)
True
>>> str(a)
'Str: somebody'
>>> repr(a)
'Name: somebody'
>>> hash(a)
69316793141516223
>>> dir(a)
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__nonzero__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__unicode__', '__weakref__', 'name']
访问控制
| method | function |
|---|---|
__getattr__(self, name) |
访问不存在的属性时调用 |
__setattr__(self, name, value) |
定义属性的赋值行为 |
__delattr__(self, name) |
删除属性时的行为 |
__getattribute__(self, name) |
每次查询一个属性的时候(不一定是不存在时)都会被调用,但在新式类中才可以使用 |
⚠️ 以上访问控制方法容易出现无限递归的问题:
举个例子:下面的__setattr__中的self.name = value实际上是调用了self.__setattr__('name', value)因此会造成无限递归
class MyClass:
def __setattr__(self, name, value):
self.name = value
正确的方式是用__dict__进行赋值:
class MyClass:
def __setattr__(self, name, value):
self.__dict__("name") = value
序列
Python的内建序列有字典,元组,列表,字符串等。通过下面这些magic methods可以使自己定义的类像这些内建序列一样工作
| method | function |
|---|---|
__len__(self) |
返回容器的长度 |
__getitem__(self, key) |
定义对容器中某一项使用 self[key] 的方式进行读取操作时的行为。可变和不可变容器类型都需要实现。key的类型错误时应该产生TypeError,没有相应的value时应该产生KeyError |
__setitem__(self, key, value) |
定义self[key]形式的赋值,可变容器必须实现 |
__iter__(self) |
返回一个迭代器,用iter()调用 |
__contains__(self, item) |
定义了in, not in的行为,如果没有定义则会迭代整个序列找到就返回True |
__missing__(self, key) |
当访问字典中不存在的key时被调用 |
class MyDict:
def __init__(self, values=None):
self.values = values
def __getitem__(self, key):
return self.values[key]
def __setitem__(self, key, value):
self.values[key] = value
print("set values[{}] to {}".format(key, value))
def __delitem__(self, key):
del self.values[key]
print("delete values[{}]".format(key))
def __repr__(self):
s = ""
s += "\tKey\t|\tValue\t\n"
for k, v in self.values.items():
s += "\t{}\t|\t{}\t\n".format(k, v)
return s
>>> a = MyDict({0:1})
>>> a
Key | Value
0 | 1
>>> a[2] = 10
set values[2] to 10
>>> a
Key | Value
0 | 1
2 | 10
>>> del a[0]
delete values[0]
>>> a
Key | Value
2 | 10
可调用对象
要在Python中构造一个可调用对象,这个对象的类必须实现__call__(self, [args...])方法。然后这个对象就能够像函数一样使用括号表达式进行调用。func()就相当于func.__call__()
class CallableClass:
def __call__(self, *args):
print("you call this object with args:", end="")
for i in args:
print(" "+str(i), end="")
print("")
>>> c = CallableClass()
>>> c("hello", "world", "something")
you call this object with args: hello world something
上下文
PEP 343中上下文管理成为了一种一级语言结构。通过with关键字可以进行上下文的管理,例如
with open("sample.txt") as f:
# Do something
这个过程实际上调用了__enter__(self)和__exit__(self, exception_type, exception_value, traceback)这两个magic method。通过它们可以进行一些初始化和清理的工作,__enter__返回需要绑定到的对象,__exit__在顺利完成工作之后返回一个True,如果语句块顺利执行, exception_type , exception_value
和 traceback 会是 None。
class Closer:
def __init__(self, obj):
self.obj = obj
def __enter__(self):
print("Enter Context")
return self.obj
def __exit__(self, exception_type, exception_value, traceback):
print("Try Closing")
try:
self.obj.close()
except AttributeError:
print("Not closable")
return True
>>> c = Closer(1)
>>> with c:
... print("hello world!")
Enter Context
hello world!
Try Closing
Not closable
描述符对象
当 __get__(), __set__(), __delete__()中的任意一个被定义时,这个对象就可以被称为描述符,描述符可以进行访问对象属性的操作,默认情况下a.x将通过一条查找链查询相应的属性,首先访问a.__dict__['x'], 然后访问type(a).__dict__['x'],接着访问其他父类的属性。
descr.__get__(self, obj, type=None)返回获取到的值 value, descr.__set__(self, obj, value)和descr.__delete__(self, obj)返回空值。
定义了__get__和__set__的对象被称为数据描述符,只定义了__get__的被称为非数据描述符。在与实例的字典中有相同名字的属性时,访问的顺序是:先访问数据描述符再访问实例的字典,先访问实例的字典再访问非数据描述符。在__set__中引发AttributeError可以实现只读数据描述符。
例如obj.x在 obj 的字典中查找 x, 但是如果 x 实现了__get__方法将会先调用d.__get__(obj)
class RevealAccess:
def __init__(self, initval=None, name='var'):
self.val = initval
self.name = name
def __get__(self, obj, objtype):
print('Retrieving', self.name)
return self.val
def __set__(self, obj, val):
print('Updating', self.name)
self.val = val
>>> class MyClass:
... x = RevealAccess(10, 'var "x"')
... y = 5
...
>>> m = MyClass()
>>> m.x
Retrieving var "x"
10
>>> m.x = 20
Updating var "x"
>>> m.x
Retrieving var "x"
20
>>> m.y
5
其他
拷贝
| method | function |
|---|---|
__copy__(self) |
定义对类的实例使用 copy.copy() 浅拷贝时的行为 |
__deepcopy__(self, memodict=) |
定义对类的实例使用 copy.deepcopy() 深拷贝时的行为 |
反射
| method | function |
|---|---|
__instancecheck__(self, instance) |
检查实例是否属于某一个类, isinstance(instance, class) |
__subclasscheck__(self, subclass) |
检查是否子类 |
Pickling
通过Python中的pickle模块可以进行序列化相关的操作,pickle也有相应的magic method
| method | function |
|---|---|
__getinitargs__(self) |
将一个参数元组传递给 __init__ 。⚠️只用于旧式类 |
__getnewargs__(self) |
改变在反pickle时传递给 __new__ 的参数。返回一个参数元组 |
__getstate__(self) |
自定义对象被pickle时被存储的状态, 反pickle时会被 __setstate__ 使用 |
__setstate__(self) |
反pickle时对象的状态会被传递给这个方法 |
__reduce__(self) |
扩展类型对象被Pickle时就会被调用。它要么返回一个代表全局名称的字符串,Pyhton会查找它并pickle,要么返回一个元组,其中包括:1⃣️一个可调用的对象,用于重建对象时调用;2⃣️一个参数元素,供那个可调用对象使用;3⃣️被传递给 __setstate__ 的状态(可选);4⃣️一个产生被pickle的列表元素的迭代器(可选);5⃣️一个产生被pickle的字典元素的迭代器(可选) |
reduce_ex(self)
reduce_ex 的存在是为了兼容性。如果它被定义,在pickle时 reduce_ex 会代替 reduce 被调用。 reduce 也可以被定义,用于不支持 reduce_ex 的旧版pickle的API调用