Python魔法方法

文章目录

  • Python魔法方法
    • 一、 概述
      • 1、 魔法方法
      • 2、 常用的魔法方法
    • 二、 构造方法
      • 1、 `__new__`
      • 2、 `__init__`
      • 3、 `__del__`
    • 三、 操作符
      • 1、 简介
      • 2、 比较操作符
      • 3、 数值操作符
        • 3.1 一元操作符
        • 3.2 常见算数操作符
        • 3.3 反射算数运算符
        • 3.4 增强赋值运算符
        • 3.5 类型转换运算符
    • 四、 类的表示
    • 五、 访问控制
    • 六、 自定义序列
      • 1、 协议
      • 2、 容器内部魔法方法
    • 七、 其他
      • 1、 可调用对象
      • 2、 上下文管理器
      • 3、 拷贝
      • 4、 描述符对象
    • 八、 总结

Python魔法方法

一、 概述

1、 魔法方法

从入门到进阶,一个很重要的点就是Python中的魔法方法,魔法方法就是可以给你的类增加魔力的特殊方法,如果你的对象实现了这些方法中的某一个,那么这个方法就会在特殊的情况下被 Python 所调用,你可以定义自己想要的行为,而这一切都是自动发生的。它们经常是两个下划线包围来命名的(比如 __init__/__new__等等),Python的魔法方法是非常强大的。

如果你学习过Java,那你会发现Python中的魔法方法像是Java中的重载,Python中的魔法方法可以理解为:对类中的内置方法的重载,注意这里不是重写。

举个例子,Python中有个比较操作符==用来比较两个变量的大小,而这个操作符是通过内置函数__eq__来实现的,所以我们只需要通过改变这个内置函数代码,就可以改变重新定义这个操作符的行为。

如,我们需要比较一个字符串的长度是否相同,我们可以这么做:

class MyStr(str):

    def __eq__(self, __x: object) -> bool:
        return len(self) == len(__x)

a, b = MyStr("123"), MyStr("abc")
print(a == b)  # 返回True

再举个例子,Python中的__new__方法是对象实例化时调用的第一个方法,该方法仅读取一个cls参数后再把其他参数都传给用于指明对象初始化行为的__init__方法,也就是说我们可以在一个对象初始化之前进行其他操作,比如检查是否合法等;而另一个方法__del__可以用来销毁对象,定义了对象被垃圾回收的行为,我们可以利用该方法进行资源回收等操作。

我们可以通过重写__new__方法实现一个单例模式,在每次实例化之前检查该对象是否有已有实例。

class Singleton:
    def __new__(cls, *args, **kwargs):
        if not hasattr(cls, "_instance"):
            print("这个类没有创建")
            cls._instance = super().__new__(cls, *args, **kwargs)  # 使用父类的方法来创建类
        print("这个类已经创建")
        return cls._instance

s = Singleton()
s2 = Singleton()  # s 和 s2 是同一个类,其共用一个内存地址

2、 常用的魔法方法

通过这两个例子相信你已经对Python的魔法方法比较理解了,但是Python中的魔法方法远不止两三个,这里有一些比较常用的魔法方法,供我们学习。

Python魔法方法_第1张图片

二、 构造方法

我们最为熟知的基本的魔法方法就是 __init__ ,我们可以用它来指明一个对象初始化的行为。然而,当我们调用 x = SomeClass() 的时候, __init__ 并不是第一个被调用的方法。事实上,第一个被调用的是__new__,这个方法才真正地创建了实例。当这个对象的生命周期结束的时候,__del__会被调用。让我们近一步理解这三个方法:

1、 __new__

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

比如,我们可以使用__new__方法来实现单例模式,只创建一个类。

2、 __init__

类的初始化方法。它获取任何传给构造器的参数(比如我们调用 x = SomeClass(10, 'foo')__init__ 就会接到参数 10 和 ‘foo’ 。 __init__ 在Python的类定义中用的最多。

3、 __del__

__new____init__是对象的构造器, __del__ 是对象的销毁器。它并非实现了语句 del x (因此该语句不等同于 x.__del__())。而是定义了当对象被垃圾回收时的行为。 当对象需要在销毁时做一些处理的时候这个方法很有用,比如 socket 对象、文件对象。但是需要注意的是,当Python解释器退出但对象仍然存活的时候,__del__并不会 执行。 所以养成一个手工清理的好习惯是很重要的,比如及时关闭连接。

使用案例:

from httpx import Client

class Crawl:
    def __init__(self) -> None:
        """初始化类,有点像C++里面的构造函数"""
        print("正在开启连接")
        self.client = Client() 
    
    def use(self):
        print("正在使用连接", self.client.is_closed)
    
    def __del__(self) -> None:
        """在类关闭的时候,释放内存,有点像C++里面的析构函数"""
        print("正在关闭连接")
        if self.client.is_closed:
            self.client.close()
            del self.client

c = Crawl()
c.use()

三、 操作符

1、 简介

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

if instance.equals(other_instance):
    # do something

你当然可以在Python也这么做,但是这样做让代码变得冗长而混乱。不同的类库可能对同一种比较操作采用不同的方法名称,这让使用者需要做很多没有必要的工作。运用魔法方法的魔力,我们可以定义方法 __eq__

if instance == other_instance:
    # do something

这是魔法力量的一部分,这样我们就可以创建一个像内建类型那样的对象了!

2、 比较操作符

Python包含了一系列的魔法方法,用于实现对象之间直接比较,而不需要采用方法调用。同样也可以重载Python默认的比较方法,改变它们的行为。下面是这些方法的列表:

  • __eq__(self, other)

    定义等于操作符(==)的行为。

  • __ne__(self, other)

    定义不等于操作符(!=)的行为。

  • __lt__(self, other)

    定义小于操作符(<)的行为。

  • __gt__(self, other)

    定义大于操作符(>)的行为。

  • __le__(self, other)

    定义小于等于操作符(<)的行为。

  • __ge__(self, other)

    定义大于等于操作符(>)的行为。

举个例子,假如我们想用一个类来存储单词。我们可能想按照字典序(字母顺序)来比较单词,字符串的默认比较行为就是这样。我们可能也想按照其他规则来比较字符串,像是长度,或者音节的数量。在这个例子中,我们使用长度作为比较标准,下面是一种实现:

class Word(str):
    '''单词类,按照单词长度来定义比较行为'''

    def __new__(cls, word):
        # 注意,我们只能使用 __new__ ,因为str是不可变类型
        # 所以我们必须提前初始化它(在实例创建时)
        if ' ' in word:
            print "Value contains spaces. Truncating to first space."
            word = word[:word.index(' ')]
            # Word现在包含第一个空格前的所有字母
        return str.__new__(cls, word)

    def __gt__(self, other):
        return len(self) > len(other)
    def __lt__(self, other):
        return len(self) < len(other)
    def __ge__(self, other):
        return len(self) >= len(other)
    def __le__(self, other):
        return len(self) <= len(other)

3、 数值操作符

就像你可以使用比较操作符来比较类的实例,你也可以定义数值操作符的行为。固定好你的安全带,这样的操作符真的有很多。看在组织的份上,我把它们分成了五类:一元操作符,常见算数操作符,反射算数操作符(后面会涉及更多),增强赋值操作符,和类型转换操作符。

3.1 一元操作符

一元操作符只有一个操作符。

  • __pos__(self)

    实现取正操作,例如 +some_object。

  • __neg__(self)

    实现取负操作,例如 -some_object。

  • __abs__(self)

    实现内建绝对值函数 abs() 操作。

  • __invert__(self)

    实现取反操作符 ~。

  • __round__(self, n)

    实现内建函数 round() ,n 是近似小数点的位数。

  • __floor__(self)

    实现 math.floor() 函数,即向下取整。

  • __ceil__(self)

    实现 math.ceil() 函数,即向上取整。

  • __trunc__(self)

    实现 math.trunc() 函数,即距离零最近的整数。

3.2 常见算数操作符

现在,我们来看看常见的二元操作符(和一些函数),像+,-,*之类的,它们很容易从字面意思理解。

  • __add__(self, other)

    实现加法操作。

  • __sub__(self, other)

    实现减法操作。

  • __mul__(self, other)

    实现乘法操作。

  • __floordiv__(self, other)

    实现使用 // 操作符的整数除法。

  • __div__(self, other)

    实现使用 / 操作符的除法。

  • __truediv__(self, other)

    实现 _true_ 除法,这个函数只有使用from __future__ import division时才有作用。

  • __mod__(self, other)

    实现 % 取余操作。

  • __divmod__(self, other)

    实现 divmod 内建函数。

  • __pow__

    实现 ** 操作符。

  • __lshift__(self, other)

    实现左移位运算符 << 。

  • __rshift__(self, other)

    实现右移位运算符 >> 。

  • __and__(self, other)

    实现按位与运算符 & 。

  • __or__(self, other)

    实现按位或运算符 | 。

  • __xor__(self, other)

    实现按位异或运算符 ^ 。

3.3 反射算数运算符

还记得刚才我说会谈到反射运算符吗?可能你会觉得它是什么高端霸气上档次的概念,其实这东西挺简单的,下面举个例子:

some_object + other

这是“常见”的加法,反射是一样的意思,只不过是运算符交换了一下位置:

other + some_object

所有反射运算符魔法方法和它们的常见版本做的工作相同,只不过是处理交换连个操作数之后的情况。绝大多数情况下,反射运算和正常顺序产生的结果是相同的,所以很可能你定义 __radd__ 时只是调用一下 __add__。注意一点,操作符左侧的对象(也就是上面的 other )一定不要定义(或者产生 NotImplemented 异常) 操作符的非反射版本。例如,在上面的例子中,只有当 other 没有定义 __add__some_object.__radd__ 才会被调用。

  • __radd__(self, other)

    实现反射加法操作。

  • __rsub__(self, other)

    实现反射减法操作。

  • __rmul__(self, other)

    实现反射乘法操作。

  • __rfloordiv__(self, other)

    实现使用 // 操作符的整数反射除法。

  • __rdiv__(self, other)

    实现使用 / 操作符的反射除法。

  • __rtruediv__(self, other)

    实现 _true_ 反射除法,这个函数只有使用 from __future__ import division 时才有作用。

  • __rmod__(self, other)

    实现 % 反射取余操作符。

  • __rdivmod__(self, other)

    实现调用 divmod(other, self) 时 divmod 内建函数的操作。

  • __rpow__

    实现 ** 反射操作符。

  • __rlshift__(self, other)

    实现反射左移位运算符 << 的作用。

  • __rshift__(self, other)

    实现反射右移位运算符 >> 的作用。

  • __rand__(self, other)

    实现反射按位与运算符 & 。

  • __ror__(self, other)

    实现反射按位或运算符 | 。

  • __rxor__(self, other)

    实现反射按位异或运算符 ^ 。

3.4 增强赋值运算符

Python同样提供了大量的魔法方法,可以用来自定义增强赋值操作的行为。或许你已经了解增强赋值,它融合了“常见”的操作符和赋值操作,如果你还是没听明白,看下面的例子:

x = 5
x += 1 # 也就是 x = x + 1

这些方法都应该返回左侧操作数应该被赋予的值(例如, a += b __iadd__ 也许会返回 a + b ,这个结果会被赋给 a ),下面是方法列表:

  • __iadd__(self, other)

    实现加法赋值操作。

  • __isub__(self, other)

    实现减法赋值操作。

  • __imul__(self, other)

    实现乘法赋值操作。

  • __ifloordiv__(self, other)

    实现使用 //= 操作符的整数除法赋值操作。

  • __idiv__(self, other)

    实现使用 /= 操作符的除法赋值操作。

  • __itruediv__(self, other)

    实现 _true_ 除法赋值操作,这个函数只有使用from __future__ import division时才有作用。

  • __imod__(self, other)

    实现 %= 取余赋值操作。

  • __ipow__

    实现 **= 操作。

  • __ilshift__(self, other)

    实现左移位赋值运算符 <<= 。

  • __irshift__(self, other)

    实现右移位赋值运算符 >>= 。

  • __iand__(self, other)

    实现按位与运算符 &= 。

  • __ior__(self, other)

    实现按位或赋值运算符 | 。

  • __ixor__(self, other)

    实现按位异或赋值运算符 ^= 。

3.5 类型转换运算符

Python也有一系列的魔法方法用于实现类似 float() 的内建类型转换函数的操作。它们是这些:

  • __int__(self)

    实现到int的类型转换。

  • __long__(self)

    实现到long的类型转换。

  • __float__(self)

    实现到float的类型转换。

  • __complex__(self)

    实现到complex的类型转换。

  • __oct__(self)

    实现到八进制数的类型转换。

  • __hex__(self)

    实现到十六进制数的类型转换。

  • __index__(self)

    实现当对象用于切片表达式时到一个整数的类型转换。如果你定义了一个可能会用于切片操作的数值类型,你应该定义 __index__

  • __trunc__(self)

    当调用 math.trunc(self) 时调用该方法, __trunc__ 应该返回 self 截取到一个整数类型(通常是long类型)的值。

  • __coerce__(self)

    该方法用于实现混合模式算数运算,如果不能进行类型转换,__coerce__应该返回 None 。反之,它应该返回一个二元组 self 和 other ,这两者均已被转换成相同的类型。

四、 类的表示

使用字符串来表示类是一个相当有用的特性。在Python中有一些内建方法可以返回类的表示,相对应的,也有一系列魔法方法可以用来自定义在使用这些内建函数时类的行为。

  • __str__(self)

    定义对类的实例调用 str() 时的行为。

  • __repr__(self)

    定义对类的实例调用 repr() 时的行为。 str() 和 repr() 最主要的差别在于“目标用户”。 repr() 的作用是产生机器可读的输出(大部分情况下,其输出可以作为有效的Python代码),而 str() 则产生人类可读的输出。

  • __format__(self)

    定义当类的实例用于新式字符串格式化时的行为,例如, “Hello, 0:abc!”.format(a) 会导致调用a.__format__(“abc”)。当定义你自己的数值类型或字符串类型时,你可能想提供某些特殊的格式化选项,这种情况下这个魔法方法会非常有用。

  • __hash__(self)

    定义对类的实例调用 hash() 时的行为。它必须返回一个整数,其结果会被用于字典中键的快速比较。同时注意一点,实现这个魔法方法通常也需要实现 __eq__ ,并且遵守如下的规则: a == b 意味着 hash(a) == hash(b)。

  • __nonzero__(self)

    定义对类的实例调用 bool() 时的行为,根据你自己对类的设计,针对不同的实例,这个魔法方法应该相应地返回True或False。

  • __dir__(self)

    定义对类的实例调用 dir() 时的行为,这个方法应该向调用者返回一个属性列表。一般来说,没必要自己实现__dir__。但是如果你重定义了 __getattr__ 或者 __getattribute__ ,乃至使用动态生成的属性,以实现类的交互式使用,那么这个魔法方法是必不可少的。

到这里,我们基本上已经结束了魔法方法指南中无聊并且例子匮乏的部分。既然我们已经介绍了较为基础的魔法方法,是时候涉及更高级的内容了。

五、 访问控制

在反射机制这一篇文章中,有对反射机制的详细讲解:https://blog.csdn.net/qq_62789540/article/details/126265034,这个原理就是运用了python内置的魔法方法。

很多从其他语言转向Python的人都抱怨Python的类缺少真正意义上的封装(即没办法定义私有属性然后使用公有的getter和setter)。然而事实并非如此。实际上Python不是通过显式定义的字段和方法修改器,而是通过魔法方法实现了一系列的封装。

  • __getattr__(self, name)

当用户试图访问一个根本不存在(或者暂时不存在)的属性时,你可以通过这个魔法方法来定义类的行为。这个可以用于捕捉错误的拼写并且给出指引,使用废弃属性时给出警告(如果你愿意,仍然可以计算并且返回该属性),以及灵活地处理AttributeError。只有当试图访问不存在的属性时它才会被调用,所以这不能算是一个真正的封装的办法。

  • __setattr__(self, name, value)

__getattr__不同,__setattr__可以用于真正意义上的封装。它允许你自定义某个属性的赋值行为,不管这个属性存在与否,也就是说你可以对任意属性的任何变化都定义自己的规则。然后,一定要小心使用__setattr__,这个列表最后的例子中会有所展示。

  • __delattr__(self, name)

这个魔法方法和__setattr__几乎相同,只不过它是用于处理删除属性时的行为。和 __setattr__ 一样,使用它时也需要多加小心,防止产生无限递归(在__delattr__的实现中调用 del self.name 会导致无限递归)。

  • __getattribute__(self, name)

__getattribute__ 看起来和上面那些方法很合得来,但是最好不要使用它。__getattribute__只能用于新式类。在最新版的Python中所有的类都是新式类,在老版Python中你可以通过继承 object 来创建新式类。__getattribute__允许你自定义属性被访问时的行为,它也同样可能遇到无限递归问题(通过调用基类的 __getattribute__ 来避免)。 __getattribute__ 基本上可以替代__getattr__。只有当它被实现,并且显式地被调用,或者产生 AttributeError 时它才被使用。 这个魔法方法可以被使用(毕竟,选择权在你自己),我不推荐你使用它,因为它的使用范围相对有限(通常我们想要在赋值时进行特殊操作,而不是取值时),而且实现这个方法很容易出现Bug。

使用示例,我们做一个自增的计数器:

class AccessCounter(object):
    ''' 一个包含了一个值并且实现了访问计数器的类
    每次值的变化都会导致计数器自增'''

	def __init__(self, val):
        super(AccessCounter, self).__setattr__('counter', 0)
        super(AccessCounter, self).__setattr__('value', val)

    def __setattr__(self, name, value):
        if name == 'value':
            super(AccessCounter, self).__setattr_('counter', self.counter + 1)
        # 使计数器自增变成不可避免
        # 如果你想阻止其他属性的赋值行为
        # 产生 AttributeError(name) 就可以了
        super(AccessCounter, self).__setattr__(name, value)

    def __delattr__(self, name):
        if name == 'value':
            super(AccessCounter, self).__setattr__('counter', self.counter + 1)
            super(AccessCounter, self).__delattr__(name)

六、 自定义序列

1、 协议

既然讲到创建自己的序列类型,就不得不说一说协议了。协议类似某些语言中的接口,里面包含的是一些必须实现的方法。在Python中,协议完全是非正式的,也不需要显式的声明,事实上,它们更像是一种参考标准。

为什么我们要讲协议?因为在Python中实现自定义容器类型需要用到一些协议。首先,不可变容器类型有如下协议:想实现一个不可变容器,你需要定义 __len____getitem__ 。可变容器的协议除了上面提到的两个方法之外,还需要定义__setitem__ __delitem__ 。最后,如果你想让你的对象可以迭代,你需要定义 __iter__ ,这个方法返回一个迭代器。迭代器必须遵守迭代器协议,需要定义 __iter__ (返回它自己)和__next__ 方法。

2、 容器内部魔法方法

  • __len__(self)

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

  • __getitem__(self, key)

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

  • __setitem__(self, key)

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

  • __iter__(self, key)

    它应该返回当前容器的一个迭代器。迭代器以一连串内容的形式返回,最常见的是使用 iter() 函数调用,以及在类似 for x in container: 的循环中被调用。迭代器是他们自己的对象,需要定义__iter__方法并在其中返回自己。

  • __reversed__(self)

    定义了对容器使用 reversed() 内建函数时的行为。它应该返回一个反转之后的序列。当你的序列类是有序时,类似列表和元组,再实现这个方法,

  • __contains__(self, item)

    __contains__ 定义了使用 in 和 not in 进行成员测试时类的行为。你可能好奇为什么这个方法不是序列协议的一部分,原因是,如果 __contains__ 没有定义,Python就会迭代整个序列,如果找到了需要的一项就返回 True 。

  • __missing__(self ,key)

    __missing__ 在字典的子类中使用,它定义了当试图访问一个字典中不存在的键时的行为(目前为止是指字典的实例,例如我有一个字典 d , ‘george’ 不是字典中的一个键,当试图访问 d['george'] 时就会调用 d.__missing__("george") )

七、 其他

1、 可调用对象

你可能已经知道了,在Python中,函数是一等的对象。这意味着它们可以像其他任何对象一样被传递到函数和方法中,这是一个十分强大的特性。

Python中一个特殊的魔法方法允许你自己类的对象表现得像是函数,然后你就可以“调用”它们,把它们传递到使用函数做参数的函数中,等等等等。这是另一个强大而且方便的特性,让使用Python编程变得更加幸福。

  • __call__(self, [args...])

    允许类的一个实例像函数那样被调用。本质上这代表了 x() 和 x.__call__() 是相同的。注意 __call__ 可以有多个参数,这代表你可以像定义其他任何函数一样,定义 __call__ ,喜欢用多少参数就用多少。

__call__ 在某些需要经常改变状态的类的实例中显得特别有用。“调用”这个实例来改变它的状态,是一种更加符合直觉,也更加优雅的方法。

类装饰器中就有用到该方法:https://blog.csdn.net/qq_62789540/article/details/124513178

2、 上下文管理器

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

  • __enter__(self)

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

  • __exit__(self, exception_type, exception_value, traceback)

    定义当 with 声明语句块执行完毕(或终止)时上下文管理器的行为。它可以用来处理异常,进行清理,或者做其他应该在语句块结束之后立刻执行的工作。如果语句块顺利执行, exception_type , exception_value 和 traceback 会是 None 。否则,你可以选择处理这个异常或者让用户来处理。如果你想处理异常,确保 __exit__ 在完成工作之后返回 True 。如果你不想处理异常,那就让它发生吧。

比如,自己编写一个上下文管理协议:

class Closer:
    '''一个上下文管理器,可以在with语句中
    使用close()自动关闭对象'''

    def __init__(self, obj):
        self.obj = obj

    def __enter__(self, obj):
        return self.obj # 绑定到目标

    def __exit__(self, exception_type, exception_value, traceback):
    	try:
            self.obj.close()
        except AttributeError: # obj不是可关闭的
            print 'Not closable.'
            return True # 成功地处理了异常

我们的httpxx.Client()就是使用类似这样的方法来实现的,httpx语法:https://blog.csdn.net/qq_62789540/article/details/126781102

我们来阅读一下这个类实现的源码:

class Client:
    def __enter__(self: T) -> T:
        if self._state != ClientState.UNOPENED:
            msg = {
                ClientState.OPENED: "Cannot open a client instance more than once.",
                ClientState.CLOSED: "Cannot reopen a client instance, once it has been closed.",
            }[self._state]# (variable) _state: Literal[ClientState.OPENED, ClientState.CLOSED]
            raise RuntimeError(msg)  

        self._state = ClientState.OPENED  # 把连接的状态标记为开启

        self._transport.__enter__()  # 发起请求,创建响应通道
        for transport in self._mounts.values():  # 遍历所有注册了的URLPattern
            if transport is not None:
                transport.__enter__()  # 进行发起网络请求
        return self  # 返回传输的实例对象

    def __exit__(
        self,
        exc_type: typing.Optional[typing.Type[BaseException]] = None,
        exc_value: typing.Optional[BaseException] = None,
        traceback: typing.Optional[TracebackType] = None,
    ) -> None:
        self._state = ClientState.CLOSED  # 把连接的状态标记为关闭

        self._transport.__exit__(exc_type, exc_value, traceback)  # 关闭所有请求创建的通道,关闭响应
        for transport in self._mounts.values():  # 遍历所有注册了的URLPattern
            if transport is not None:  
                transport.__exit__(exc_type, exc_value, traceback)

3、 拷贝

有些时候,特别是处理可变对象时,你可能想拷贝一个对象,改变这个对象而不影响原有的对象。这时就需要用到Python的 copy 模块了。然而(幸运的是),Python模块并不具有感知能力, 因此我们不用担心某天基于Linux的机器人崛起。但是我们的确需要告诉Python如何有效率地拷贝对象。

  • __copy__(self)

    定义对类的实例使用 copy.copy() 时的行为。 copy.copy() 返回一个对象的浅拷贝,这意味着拷贝出的实例是全新的,然而里面的数据全都是引用的。也就是说,对象本身是拷贝的,但是它的数据还是引用的(所以浅拷贝中的数据更改会影响原对象)。

  • __deepcopy__(self, memodict=)

    定义对类的实例使用 copy.deepcopy() 时的行为。 copy.deepcopy() 返回一个对象的深拷贝,这个对象和它的数据全都被拷贝了一份。 memodict 是一个先前拷贝对象的缓存,它优化了拷贝过程,而且可以防止拷贝递归数据结构时产生无限递归。当你想深拷贝一个单独的属性时,在那个属性上调用 copy.deepcopy() ,使用 memodict 作为第一个参数。

这些魔法方法有什么用武之地呢?像往常一样,当你需要比默认行为更加精确的控制时。例如,如果你想拷贝一个对象,其中存储了一个字典作为缓存(可能会很大),拷贝缓存可能是没有意义的。如果这个缓存可以在内存中被不同实例共享,那么它就应该被共享。

4、 描述符对象

描述符是一个类,当使用取值,赋值和删除 时它可以改变其他对象。描述符不是用来单独使用的,它们需要被一个拥有者类所包含。描述符可以用来创建面向对象数据库,以及创建某些属性之间互相依赖的类。描述符在表现具有不同单位的属性,或者需要计算的属性时显得特别有用(例如表现一个坐标系中的点的类,其中的距离原点的距离这种属性)。

要想成为一个描述符,一个类必须具有实现 __get__ , __set____delete__ 三个方法中至少一个。

让我们一起来看一看这些魔法方法:

  • __get__(self, instance, owner)

    定义当试图取出描述符的值时的行为。 instance 是拥有者类的实例, owner 是拥有者类本身。

  • __set__(self, instance, owner)

    定义当描述符的值改变时的行为。 instance 是拥有者类的实例, value 是要赋给描述符的值。

  • __delete__(self, instance, owner)

    定义当描述符的值被删除时的行为。 instance 是拥有者类的实例:

class TestDes:
    def __get__(self, instance, owner):  # 在获取TestDes的值时调用
        print('TestDes:__get__', instance, owner)
        return 'TestDes:__get__'
 
    def __set__(self, instance, value):  # 在给TestDes设置值时调用
        print('TestDes:__set__', instance, value)


class TestMain:
    des = TestDes()
 
if __name__ == '__main__':
    t = TestMain()
    print(t.des)
    print(TestMain.des)
 
    print()
 
    t.des = 1
    print(t.des)
    print(TestMain.des)
 
    print()
 
    TestMain.des = 2
    print(t.des)
    print(TestMain.des)

首先明确一点,拥有这个方法的类,应该(也可以说是必须)产生一个实例,并且这个实例是另外一个类的类属性(注意一定是类属性,通过self的方式产生就不属于__get__范畴了)。

八、 总结

一些魔法方法直接和内建函数对应,这种情况下,如何调用它们是显而易见的。然而,另外的情况下,调用魔法方法的途径并不是那么明显。这个附录旨在展示那些不那么明显的调用魔法方法的语法。

魔法方法 什么时候被调用 解释
__new__(cls [,...]) instance = MyClass(arg1, arg2) __new__在实例创建时调用
__init__(self [,...]) instance = MyClass(arg1,arg2) __init__在实例创建时调用
__cmp__(self) self == other, self > other 等 进行比较时调用
__pos__(self) +self 一元加法符号
__neg__(self) -self 一元减法符号
__invert__(self) ~self 按位取反
__index__(self) x[self] 当对象用于索引时
__nonzero__(self) bool(self) 对象的布尔值
__getattr__(self, name) self.name #name不存在 访问不存在的属性
__setattr__(self, name) self.name = val 给属性赋值
__delattr_(self, name) del self.name 删除属性
__getattribute__(self,name) self.name 访问任意属性
__getitem__(self, key) self[key] 使用索引访问某个元素
__setitem__(self, key) self[key] = val 使用索引给某个元素赋值
__delitem__(self, key) del self[key] 使用索引删除某个对象
__iter__(self) for x in self 迭代
__contains__(self, value) value in self, value not in self 使用in进行成员测试
__call__(self [,...]) self(args) “调用”一个实例
__enter__(self) with self as x: with声明的上下文管理器
__exit__(self, exc, val, trace) with self as x: with声明的上下文管理器
__getstate__(self) pickle.dump(pkl_file, self) Pickle
__setstate__(self) data = pickle.load(pkl_file) Pickle

你可能感兴趣的:(#,Python底层,python,重构,1024程序员节)