Python神奇方法全解析:从初学者到行家的终极指南

编译者:三石

Python神奇方法全解析:从初学者到行家的终极指南

【新智慧入门】Python魔法方法是指允许为自定义类添加“神奇”功能的方法。在Python官方文档中,对这些方法的介绍或描述不仅内容分散,而且组织结构也比较松散。本文系统回顾了Python的神奇方法。对于初学者或者Python专家或多或少都会有帮助。

废话不多说,我们直接进入主题吧!

Python 魔法指南目录

介绍

设置和初始化

在自定义类中使用运算符

魔法方法比较

魔术方法数字

描述自定义类

控制属性访问

制作自定义序列

反射

可调用对象

上下文管理器

构建描述符对象

介绍

到底有什么神奇的方法呢?它们适用于 Python 中的所有内容,并且是特殊方法,允许您向自己的定义类添加“神奇”功能。他们总是使用双下划线(例如 __init__ 或 __lt__),但他们的文档并不能很好地代表它们。所有这些神奇的方法都出现在Python的官方文档中,但内容比较分散,组织结构松散。此外,您将很难找到示例(尽管它们设计良好并且在语言参考中得到了很好的描述,但随后是无聊的语法描述等)。

为了弥补Python官方文档中的这些缺陷,作者整理了这篇关于magic方法的文章,旨在作为教程、复习或参考文档。

设置和初始化

相信大家都熟悉这个最基本的神奇方法__init__。它允许您自定义对象的初始化行为。当我调用 x=SomeClass() 时,__init__ 不是第一个被调用的。实际上有一个名为 __new__ 的方法,它实际上创建实例并将任何参数传递给初始化程序以达到创建的目的。在对象的生命周期结束时,调用 __del__ 。让我们仔细看看这 3 个令人惊奇的方法:

__new__(cls, […)

当一个对象被实例化时,__new__ 是第一个调用的方法。将任何其他参数传递给类中的 __init__ 。 __new__ 很少使用,但确实有其用途,特别是当子类继承不可变类型(元组或字符串)时。

__init__(self, […)

类初始化。创建对象后,Python 解释器默认调用 __init__() 方法。无论主构造函数被调用什么,它都会被传递。 __init__ 几乎普遍用于 Python 类定义中。

__del__(self)

如果 __new__ 和 __init__ 构成对象的构造函数,则 __del__ 是析构函数。删除对象时,Python解释器默认也会调用 __del__() 方法。在Python中,开发人员很少直接销毁对象(如果需要,应该使用del关键字来销毁)。 Python的内存管理机制非常有能力完成这项工作。也就是说,无论你手动调用del,还是python自动回收,都会触发__del__方法的执行。

以下是 __init__ 和 __del__ 的示例:

from os.path import join
class FileObject:
    '''对文件对象的包装,确保文件在关闭时得到删除'''

    def __init__(self, filepath='~', filename='sample.txt'):
        # 按filepath,读写模式打开名为filename的文件
        self.file=open(join(filepath,filename), 'r+')

    def __del__(self):
        self.file.close()
        del self.file

在自定义类中使用运算符

魔法方法对比:

Python有很多神奇的方法,它们旨在使用运算符来实现对象之间的直观比较,而不是尴尬的方法调用。它们还提供了一种覆盖对象比较的默认 Python 行为的方法。以下是这些方法及其作用的列表:

__cmp__(self, other)

__cmp__ 是最基本的魔法方法。它实际上实现了所有比较运算符的行为(其他,返回正整数)。通常最好定义它们,而不要求您一次定义它们,但是当您需要对所有比较使用类似的指导方针时, __cmp__ 可能是避免重复和提高清晰度的好方法。

__eq__(self, other)

定义相等运算符 == 的行为。

__ne__(self, other)

定义不等式运算符 != 的行为。

__lt__(self, other)

定义小于运算符的行为。

__le__(self, other)

定义小于或等于运算符 = 的行为。

举个例子,想象一个单词的类定义。我们可能希望根据字符串的内部默认比较行为(即字典顺序(按字母))或基于某些其他标准(例如长度或音节数)来比较单词。本例中我们按照字长排序,实现如下:

class Word(str):
   '''单词类,比较定义是基于单词长度的'''

    def __new__(cls, word):
        # 注意,我们使用了__new__,这是因为str是一个不可变类型,
        # 所以我们必须更早地初始化它(在创建时)
        if ' ' in word:
            print "单词内含有空格,截断到第一部分"
            word = word[:word.index(' ')] # 在出现第一个空格之前全是字符了现在
        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)

魔术方法编号:

就像您可以通过重载比较运算符来创建自己的类实例一样,您也可以重载数字运算符。

一元运算符:

一元运算和函数只有一个操作数,例如负数、绝对值等。

__pos__(self)

实现一元正数的行为(如:+some_object)

__neg__(self)

实现负数行为(例如:-some_object)

__abs__(self)

实现内置abs()函数的行为

__invert__(self)

使用 ~ 运算符实现否定行为。

常规算术运算符:

现在我们介绍基本的二元运算符:+、-、* 等。其中大部分都是不言自明的。

__add__(self, other)

实施加法

__sub__(self, other)

实施减法

__mul__(self, other)

实现乘法

__floordiv__(self, other)

要实现楼层划分,请使用 // 运算符

__div__(self, other)

要实现传统除法,请使用 / 运算符

__truediv__(self, other)

实现真正的分裂。请注意,这仅在您 from __future__ import division 时才有效

__mod__(self, other)

要实现模数,请使用 % 运算符

__divmod__(self, other)

实现内置函数 divmod() 的行为

__pow__(self, other)

要实现求幂,请使用 ** 运算符

__lshift__(self, other)

要实现左移位,请使用运算符

__and__(self, other)

要实现按位 AND,请使用 & 运算符

__or__(self, other)

要实现按位或,请使用 |操作员

__xor__(self, other)

要实现按位异或,请使用 ^ 运算符

反射算术运算符:

首先举个例子:some_object + other。这是“常规”添加。反射其实等价于同一个东西,只不过操作数变了,位置也变了:other + some_object。大多数情况下,反射算术运算的结果与常规算术运算等效,因此您可以在重载 __radd__ 后调用 __add__ 。玩得开心:

__radd__(self, other)

实施反射加法

__rsub__(self, other)

实施反射减法

__rmul__(self, other)

实现反射乘法

__rfloordiv__(self, other)

要实现反射地板移除,请使用 // 运算符

__rdiv__(self, other)

要实现传统除法,请使用 / 运算符

__rturediv__(self, other)

实行实分。请注意,只有当您从 __future__ 导入部门时,它才有效

__rmod__(self, other)

要实现反射模,请使用 % 运算符

__rdivmod__(self, other)

实现内置函数 divmod() 的长除法行为,该函数在调用 divmod(other, self) 时调用

__rpow__(self, other)

要实现反射求幂,请使用 ** 运算符

__rlshift__(self, other)

要实现反射左按位位移,请使用运算符

__rand__(self, other)

要实现反射按位 AND,请使用 & 运算符

__ror__(self, other)

要实现反射按位或,请使用 |操作员

__rxor__(self, other)

要实现反射按位异或,请使用 ^ 运算符

增量赋值:

Python还有多种神奇的方法可以让用户自定义增量赋值行为。

这些方法都不返回值,因为在 Python 中赋值不会返回任何内容。相反,他们只是改变类的状态。名单如下:

__rxor__(self, other)

实现加法和赋值

__isub__(self, other)

实现减法和赋值

__imul__(self, other)

实现乘法和赋值

__ifloordiv__(self, other)

要实现楼层划分和分配,请使用 //= 运算符

__idiv__(self, other)

要实现传统的除法和赋值,请使用 /= 运算符

__iturediv__(self, other)

实行真正的划分和分配。请注意,这仅在您 from __future__ import division 时才有效

__imod__(self, other)

要实现取模和赋值,请使用 %= 运算符

__ipow__(self, other)

要实现求幂和赋值,请使用 **= 运算符

__ilshift__(self, other)

要实现左移和赋值,请使用 = 运算符

__iand__(self, other)

要实现按位 AND 和赋值,请使用 &= 运算符

__ior__(self, other)

要实现按位 OR 和赋值,请使用 |= 运算符

__ixor__(self, other)

要实现按位异或和赋值,请使用 ^= 运算符

类型转换的神奇方法:

Python 还有一组神奇的方法,旨在实现内置类型转换函数的行为,例如 float()。

__int__(self)

实现类型转换为int

__long__(self)

实现类型转换为 long

__float__(self)

实现类型转换为float

__complex__(self)

实现复数的类型转换

__oct__(self)

实现八进制类型转换

__hex__(self)

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

__index__(self)

当对象被切片为int时实现类型转换。如果自定义数字类型,则应该重载 __index__ 因为它可能会被切片。

__trunc__(self)

当调用 math.trunc(self) 时调用。 __trunc__ 应该返回一个截断的整数(通常是长整数)。

__coerce__(self, other)

该方法用于实现混合模式算术。如果类型转换不可能,则 __coerce__ 应返回 None。否则,它应该返回一对包含 self 和 other (一个 2 元组),并调整为具有相同类型。

描述自定义类

使用字符串来描述类通常很有用。 Python 提供了一些方法,允许您在自己的类中自定义内置函数,以返回类行为的描述。

__str__(self)

当您定义的类的实例调用 str() 时,它用于定义它的行为

__repr__(self)

当您定义的类的实例调用 repr() 时,它会为其定义行为。 str() 和 repr() 之间的主要区别在于其读取对象。 repr() 生成的输出主要是计算机可读的(在许多情况下,这甚至可能是一些有效的 Python 代码),而 str() 旨在是人类可读的。

__unicode__(self)

当您定义的类的实例调用 unicode() 时,它会为其定义行为。 unicode() 与 str() 类似,不同之处在于它返回一个 unicode 字符串。警报!如果用户使用类的实例调用 str(),而您只定义 __unicode__(),则它将不起作用。为了以防万一,您应该始终定义 __str__(),即使用户不知道 unicode。

__hash__(self)

当您定义的类的实例调用 hash() 时,它用于定义它的行为。它必须返回一个整数,其结果用作字典中的快速键比较。

__nonzero__(self)

当您定义的类的实例调用 bool() 时,它用于定义其行为。返回 True 或 False,具体取决于您认为实例是 True 还是 False。

我们已经很好地完成了魔术方法的无聊部分(没有示例),现在我们已经讨论了一些基本的魔术方法,是时候让我们继续讨论高级主题了。

控制属性访问

Python通过神奇的手段实现了很多封装,而不是通过显式的方法或者字段修饰符。例如:

__getattr__(self, name)

您可以定义当用户尝试访问不存在的类属性(无论它存在还是尚未创建)时的行为。这对于捕获和重定向常见的拼写错误、给出有关使用属性的警告(如果您愿意,您仍然可以选择计算并返回该属性)或抛出 AttributeError 异常非常有用。该方法仅适用于访问不存在的属性,因此它不是真正的封装解决方案。

__setattr__(self, name, value)

与 __getattr__ 不同,__setattr__ 是一个封装的解决方案。它允许您在为属性赋值时进行操作,无论该属性是否存在。这意味着您可以为属性值的任何更改定义自定义规则。但是,使用 __setattr__ 时需要小心,列表后面将作为示例给出。

__delattr__

这相当于 __setattr__,但删除类属性而不是设置它们。它需要与 __setattr__ 相同的预防措施,防止无限递归(当在 __delattr__ 中调用 del self.name 时发生这种情况)。

__getattribute__(self, name)

__getattribute__ 非常适合其同伴 __setattr__ 和 __delattr__。但我不建议你使用它。

__getattribute__ 只能用在新式类中(在最新版本的Python中,所有的类都是新式类,在旧版本中你可以通过继承object类来创建新式类。它允许你设置规则,在(无论当时某个类属性的值是否可访问)都会由于其同伴的错误而遭受一些无限递归问题(可以通过调用基类的 __getattribute__ 方法来防止这种情况发生)。当实现 __getattribute__ 时,只有显式调用 __getattribute__ 或抛出 AttributeError 异常时才会调用此方法,同时也主要避免对 __getattr__ 的依赖。可以使用此方法,但我不推荐它,因为它的用例很小。 (虽然很少见,但我们需要特殊的行为来获取值而不是赋值)而且要实现零错误确实很难。

当您自定义任何类属性访问方法时,很容易导致问题。考虑这个例子:

def __setattr__(self, name, value):
    self.name = value
    # 当每次给一个类属性赋值时,会调用__setattr__(),这就形成了递归
    # 因为它真正的含义是 self.__setattr__('name', value)
    # 所以这方法不停地调用它自己,变成了一个无法退出的递归最终引发crash

def __setattr__(self, name, value):
    self.__dict__[name] = value # 给字典中的name赋值
    # 在此自定义行为

这是一个特殊属性访问方法的实际示例(请注意,我们使用 super 因为并非所有类都有 __dict__ 类属性):

class AccessCounter:
    '''一个类包含一个值和实现了一个访问计数器。
    当值每次发生变化时,计数器+1'''


    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)
        # Make this unconditional.
        # 如果你想阻止其他属性被创建,抛出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)

制作自定义序列

有很多方法可以使您的类表现得像内置序列(字典、元组、列表、字符串等)。这些是迄今为止我最喜欢的魔术方法,因为它们为您提供了一种方法,可以神奇地使类实例的整个全局函数数组完美地工作。

__len__(self)

返回容器的长度。一些协议同时支持可变和不可变容器

__getitem__(self, key)

使用 self[key] 表示法定义访问项目时的行为。这也是一个部分可变和不可变的容器协议。当 key 的类型错误,或者没有与 Key 对应的值时,这也会抛出相应的异常:TypeError。

__setitem__(self, key, value)

使用 self[key]=value 表示法定义为项目分配值时的行为。这也是可变和不可变容器协议的一部分。同样,您应该在适当的情况下抛出 KeyError 和 TypeError 异常。

__delitem__(self, key)

定义删除项目时的行为(例如 del self[key])。这只是可变容器协议的一部分。使用无效密钥后,您必须抛出适当的异常。

__iter__(self)

应将迭代器返回到容器。迭代器将返回几个内容,大部分由内置函数 iter() 表示。当容器使用 for x in container: 形式的循环时。迭代器本身就是它的对象,必须定义一个 __iter__ 方法来返回它自己。

__reversed__(self)

定义调用内置函数reverse() 时的行为。应返回列表的反向版本。

__contains__(self, item)

__contains__ 是一种成员关系,定义了使用 in 和 not in 进行测试时的行为。那么你可能会问为什么这不是序列协议的一部分?这是因为当 __contains__ 未定义时,Python 将迭代序列,如果遇到要查找的项,则返回 True。

__concat__(self, other)

最后,您可以通过 __concat__ 定义您的序列和另一个序列之间的连接。应该从 self 和 other 返回一个新构造的序列。 __concat__ 在调用 2 个序列时涉及运算符 +

在我们的示例中,让我们看一下列表实现的一些基本功能构建块。可能会让您想起您使用的其他语言(例如 Haskell)。

class FunctionalList:
    '''类覆盖了一个list的某些额外的功能性魔法,像head,
    tail,init,last,drop,and take'''

    def __init__(self, values=None):
        if values is None:
            self.values = []
        else:
            self.values = values

    def __len__(self):
        return len(self.values)

    def __getitem__(self, key):
        # 如果key是非法的类型和值,那么list valuse会抛出异常
        return self.values[key]

    def __setitem__(self, key, value):
        self.values[key] = value

    def __delitem__(self, key):
        del self.values[key]

    def __iter__(self):
        return iter(self.values)

    def __reversed__(self):
        return reversed(self.values)

    def append(self, value):
        self.values.append(value)
    def head(self):
        # 获得第一个元素
        return self.values[0]
    def tail(self):
        # 获得在第一个元素后的其他所有元素
        return self.values[1:]
    def init(self):
        # 获得除最后一个元素的序列
        return self.values[:-1]
    def last(last):
        # 获得最后一个元素
        return self.values[-1]
    def drop(self, n):
        # 获得除前n个元素的序列
        return self.values[n:]
    def take(self, n):
        # 获得前n个元素
        return self.values[:n]

反射

您还可以通过定义魔术方法,使用内置函数 isinstance() 和 issubclass() 来控制反射的行为方式。这些神奇的方法是:

__instancecheck__(self, instance)

检查实例是否是您定义的类的实例(例如,isinstance(instance, class))

__subclasscheck__(self, subclass)

检查某个类是否是您定义的类的子类(例如 issubclass(subclass, class))

可调用对象

这是 Python 中的一个特殊的魔术方法,它允许你的类实例像函数一样工作。所以你可以“调用”它们,将它们作为参数传递给函数等等。这是另一个强大而方便的功能,使Python编程变得更加可爱。

__call__(self, [args…])

允许像函数一样调用类实例。本质上,这意味着 x() 等同于 x.__call__()。请注意, __call__ 所需的参数数量是可变的,这意味着您可以为任何函数定义 __call__ 并使用任意数量的参数。

__call__ 对于频繁更改状态的实例非常有用。 “调用”实例是更改对象状态的直观且优雅的方式。以下示例是表示实体在平面上的位置的类:

class Entity:
    '''描述实体的类,被调用的时候更新实体的位置'''

    def __init__(self, size, x, y):
        self.x, self.y = x, y
        self.size = size

    def __call__(self, x, y):
        '''改变实体的位置'''
        self.x, self.y = x, y

    #省略...

上下文管理器

上下文管理允许对对象进行设置和清理操作,并使用 with 语句执行封装的操作。上下文操作的行为取决于 2 个魔术方法:

__enter__(self)

定义当使用 with 语句创建块时,上下文管理应在块的开头执行什么操作。

__exit__(self,  exception_type, exception_value, traceback)

定义块执行(或终止)后上下文管理应执行的操作。

您还可以使用这些方法来创建封装其他对象的上下文管理器。看下面的例子:

class Closer:
    '''用with声明一个上下文管理用一个close方法自动关闭一个对象'''

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

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

    def __exit__(self, exception_type, exception_val, trace):
        try:
            self.obj.close()
        except AttributeError: #obj不具备close
            print 'Not closable.'
            return True # 成功处理异常

以下是 Closer 的实际操作示例,使用 FTP 连接(可关闭套接字)进行演示:

>>> from magicmethods import Closer
>>> from ftplib import :;;
>>> with Closer(FTP('ftp.somsite.com')) as conn:
...     conn.dir()
...
# 省略的输出
>>> conn.dir()
# 一个很长的AttributeError消息, 不能关闭使用的一个连接
>>> with Closer(int(5)) as i:
...     i += 1
...
Not closeable.
>>> i
6

构建描述符对象

描述符可以更改其他对象,也可以访问类中的任何获取、设置或删除。

作为描述符,类必须至少实现 __get__、__set__ 和 __delete__ 之一。让我们快速浏览一下这些令人惊奇的方法:

__get__(self, instance, owner)

定义描述符在检索其值时的行为。 Instance是所有者对象的实例,所有者是所有类。

__set__(self, instance, value)

定义描述符的值更改时的行为。 instance是所有者对象的实例,value是设置描述符的值

__delete__(self, instance)

定义描述符的值被删除时的行为。实例是所有者对象的实例。

现在,这是一个有用的描述符应用程序的示例:单位转换策略

class Meter(object):
    '''米描述符'''

    def __init__(self, value=0.0):
        self.value = float(value)
    def __get__(self, instance, owner):
        return self.value
    def __set__(self, instance, value):
        self.value = float(value)

class Foot(object):
    '''英尺描述符'''

    def __get__(self, instance, owner):
        return instance.meter * 3.2808
    def __set__(self, instance, value):
        instance.meter = float(value) / 3.2808

class Distance(object):
    '''表示距离的类,控制2个描述符:feet和meters'''
    meter = Meter()
    foot = Foot()

总结

本指南的目标是让任何人都可以使用,无论他们是否有 Python 或面向对象编程的经验。如果您准备好学习 Python,那么您已经获得了有关编写功能丰富、优雅且易于使用的类的宝贵知识。如果您是一名中级 Python 程序员,您很可能已经掌握了一些概念、策略和一些减少编写代码量的好方法。如果你是Python专家,你可能已经复习了一些你可能已经忘记的知识点,或者你可能发现了一些新的东西。无论您的经验水平如何,我希望您在探索 Python 魔力的旅程中找到有用的东西!

原文链接:

#附录1

新智慧AI WORLD 2018大会【早鸟票】

发售中!

新智元将于9月20日在北京国家会议中心举办AI WORLD 2018大会,邀请机器学习教父、CMU教授Tom Mitchell、Max Tegmark、周志华、陶大成、陈怡然、等关注机器智能。和人类的命运。

会议官方网站:

即日起至8月19日,新智元将限量发售早鸟票,与全球人工智能领袖近距离交流,见证全球人工智能产业的跨越式发展。

你可能想看:
« 上一篇
下一篇 »

相关推荐

python爬虫教程PDF,Python爬虫书PDF

2026年09月02日

31阅读

外国教程python,python编程教学视频

2026年09月02日

25阅读

python简明教程沈洁元,python详细基础教程

2026年09月02日

21阅读

python库包教程,python 包

2026年09月02日

26阅读

python教程大全集,python教程600集

2026年09月02日

26阅读

python入门教程完整版,python零基础入门全套教程视频

2026年09月02日

22阅读