Python Magic Methods

python基础

浏览数:779

2018-2-20

在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__

initnew一起组成了对象的构造器,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类装饰器可以只需自己定义eqne和另外任意一个比较操作就能实现所有的比

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: ; 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)返回空值。

定义了getset的对象被称为数据描述符,只定义了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调用

Post author: Kasheem Lew
Post link: http://kasheemlew.github.io/2017/10/06/python-magic-method/
Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 3.0 unless stating additionally.