Heim  >  Artikel  >  Backend-Entwicklung  >  Einführung in magische Methoden in Python

Einführung in magische Methoden in Python

巴扎黑
巴扎黑Original
2017-03-18 11:38:121372Durchsuche

Vorwort

In Python werden alle Methoden, die durch doppelte Unterstriche „__“ eingeschlossen sind, gemeinsam als „Magische Methoden“ bezeichnet. Zum Beispiel __init__.

Einige magische Methoden, mit denen wir am häufigsten in Kontakt kommen, werden uns vielleicht nie wieder in unserem Leben begegnen, daher hier nur eine kurze Einführung

Und Einige magische Methoden sind genial. Sie können damit sehr schönen Code erstellen, z. B. die Kapselung komplexer Logik in einer einfachen API.

Die Bearbeitungsideen dieses Artikels sind dem Blog von Rafe Kettler entlehnt: A Guide to Python Magic Methods, und einige Codebeispiele wurden hinzugefügt.

Die Reihenfolge der Einführung ist ungefähr: Die häufigsten werden zuerst eingeführt und die selteneren werden später eingeführt.

Die in diesem Artikel verwendeten Codebeispiele können von meinem Github heruntergeladen werden.

Konstruktion und Initialisierung

__init__Es wird bei der Initialisierung des Objekts aufgerufen

Wenn wir x = SomeClass() aufrufen, ist tatsächlich nicht __init__ das erste, das ausgeführt wird, sondern __new__. Um genau zu sein, bilden __new__ und __init__ zusammen den „Konstruktor“.

__new__ wird verwendet, um eine Klasse zu erstellen und eine Instanz dieser Klasse zurückzugeben, während __init__ nur Parameter übergibt um die Instanz zu initialisieren.

__new__ wird definitiv während des Prozesses der Erstellung einer Instanz aufgerufen, aber __init__ wird nicht unbedingt aufgerufen, wenn eine Instanz über pickle.load __init__ deserialisiert wird wird nicht aufgerufen. Die Methode

__new__ muss immer eine Instanz der Klasse zurückgeben, während __init__ keinen Wert außer None zurückgeben kann. Zum Beispiel das folgende Beispiel:

class Foo(object):

    def __init__(self):
        print 'foo __init__'
        return None  # 必须返回None,否则抛TypeError

    def __del__(self):
        print 'foo __del__'

In der Praxis werden Sie selten __new__ verwenden, es sei denn, Sie möchten die Erstellung einer Klasse steuern können.
Wenn Sie __new__ erklären möchten, ist häufig die Einführung von metaclass (Metaklasse) erforderlich.
Wenn Sie tiefer gehen möchten, können Sie auf meinen anderen Blog verweisen: Pythons Metaklasse verstehen

Bezüglich der Überladung von __new__ gibt es auch eine ausführliche Einführung in der Python-Dokumentation.

Am Ende des Lebenszyklus des Objekts wird __del__ als „Destruktor“ bezeichnet. __del__
definiert, wann ein Objekt durch Müll gesammelt wird. __del__

wird leicht missverstanden. Tatsächlich ist

keine Implementierung von x.__del__(), aber del x wird oft aufgerufen, wenn del x ausgeführt wird Über diesen Satz? Verwenden Sie weiterhin den Code der Foo-Klasse als Beispiel: x.__del__()

Wenn

aufgerufen wird, existiert das Objekt selbst immer noch, aber nach dem Aufruf von
foo = Foo()
foo.__del__()
print foo
del foo
print foo  # NameError, foo is not defined
gibt es kein foo mehr.

foo.__del__() Bitte beachten Sie, dass es keine Garantie dafür gibt, dass del foo korrekt ausgeführt wird, wenn das Objekt beim Beenden des Interpreters noch vorhanden ist.

ist also kein Ersatz für gute Programmiergewohnheiten.

Schließen Sie beispielsweise bei der Verarbeitung eines Sockets die fertige Verbindung rechtzeitig. __del____del__Attributzugriffskontrolle

Es gibt immer Leute, die sich über die mangelnde Klassenkapselung in Python beschweren. Sie hoffen beispielsweise, dass Python private Attribute definieren und dann öffentlich zugängliche Getter und Setter bereitstellen kann. Python kann tatsächlich eine Kapselung durch magische Methoden erreichen.

Diese Methode definiert das Verhalten, wenn Sie versuchen, auf eine Eigenschaft zuzugreifen, die nicht existiert. Daher kann das Überladen dieser Methode Rechtschreibfehler erkennen und umleiten oder vor einigen veralteten Attributen warnen. __getattr__(self, name)

__setattr__(self, name, value) ist eine Lösung zur Implementierung der Kapselung, die das Verhalten definiert, wenn Sie Eigenschaften zuweisen und ändern.

Unabhängig davon, ob ein bestimmtes Attribut des Objekts vorhanden ist oder nicht, können Sie dem Attribut einen Wert zuweisen, sodass Sie benutzerdefinierte Operationen für den Wert des Attributs ausführen können. Zu beachten ist, dass bei der Implementierung von

der Fehler „unendliche Rekursion“ vermieden werden soll, der im folgenden Codebeispiel erwähnt wird. __setattr__
__setattr__

__delattr__(self, name) ist

sehr ähnlich, außer dass es das Verhalten definiert, wenn Sie das Attribut löschen. Durch die Implementierung von

soll gleichzeitig der Fehler der „unendlichen Rekursion“ vermieden werden. __delattr____setattr____delattr__

__getattribute__(self, name) definiert das Verhalten beim Zugriff auf Ihre Eigenschaft. Im Gegensatz dazu funktioniert

nur, wenn die Eigenschaft nicht vorhanden ist.

__getattribute__ Daher muss in Python-Versionen, die __getattr__ unterstützen,

vor dem Aufruf von

verwendet werden. __getattribute__Vermeiden Sie außerdem den Fehler „unendliche Rekursion“. __getattr____getattribute__Es sollte daran erinnert werden, dass es am besten ist, nicht zu versuchen, __getattribute__ zu implementieren, da dieser Ansatz selten gesehen wird und leicht Fehler verursachen kann.

Beispiel veranschaulicht den unendlichen Rekursionsfehler von __getattribute__:

__setattr__Die korrekte Schreibweise sollte also sein:

def __setattr__(self, name, value):
    self.__dict__[name] = value

__delattr__如果在其实现中出现del self.name 这样的代码也会出现”无限递归”错误,这是一样的原因。

下面的例子很好的说明了上面介绍的4个魔术方法的调用情况:

class Access(object):

    def __getattr__(self, name):
        print '__getattr__'
        return super(Access, self).__getattr__(name)

    def __setattr__(self, name, value):
        print '__setattr__'
        return super(Access, self).__setattr__(name, value)

    def __delattr__(self, name):
        print '__delattr__'
        return super(Access, self).__delattr__(name)

    def __getattribute__(self, name):
        print '__getattribute__'
        return super(Access, self).__getattribute__(name)

access = Access()
access.attr1 = True  # __setattr__调用
access.attr1  # 属性存在,只有__getattribute__调用
try:
    access.attr2  # 属性不存在, 先调用__getattribute__, 后调用__getattr__
except AttributeError:
    pass
del access.attr1  # __delattr__调用

描述器对象

我们从一个例子来入手,介绍什么是描述符,并介绍__get____set____delete__ 的使用。(放在这里介绍是为了跟上一小节介绍的魔术方法作对比)

我们知道,距离既可以用单位”米”表示,也可以用单位”英尺”表示。现在我们定义一个类来表示距离,它有两个属性: 米和英尺。

class Meter(object):
    '''Descriptor for a meter.'''
    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):
    '''Descriptor for a foot.'''
    def __get__(self, instance, owner):
        return instance.meter * 3.2808
    def __set__(self, instance, value):
        instance.meter = float(value) / 3.2808

class Distance(object):
    meter = Meter()
    foot = Foot()

d = Distance()
print d.meter, d.foot  # 0.0, 0.0
d.meter = 1
print d.meter, d.foot  # 1.0 3.2808
d.meter = 2
print d.meter, d.foot  # 2.0 6.5616

在上面例子中,在还没有对Distance的实例赋值前, 我们认为meter和foot应该是各自类的实例对象, 但是输出却是数值。这是因为__get__发挥了作用.

我们只是修改了meter,并且将其赋值成为int,但foot也修改了。这是__set__发挥了作用.

描述器对象(Meter、Foot)不能独立存在, 它需要被另一个所有者类(Distance)所持有。

描述器对象可以访问到其拥有者实例的属性,比如例子中Foot的instance.meter

在面向对象编程时,如果一个类的属性有相互依赖的关系时,使用描述器来编写代码可以很巧妙的组织逻辑。在Django的ORM中, models.Model中的InterField等字段, 就是通过描述器来实现功能的。

一个类要成为描述器,必须实现__get____set____delete__ 中的至少一个方法。下面简单介绍下:

__get__(self, instance, owner)

参数instance是拥有者类的实例。参数owner是拥有者类本身。__get__在其拥有者对其读值的时候调用。

__set__(self, instance, value)

__set__在其拥有者对其进行修改值的时候调用。

__delete__(self, instance)

__delete__在其拥有者对其进行删除的时候调用。

构造自定义容器(Container)

在Python中,常见的容器类型有: dict, tuple, list, string。

其中tuple, string是不可变容器,dict, list是可变容器。

可变容器和不可变容器的区别在于,不可变容器一旦赋值后,不可对其中的某个元素进行修改。

比如定义了l = [1, 2, 3]t = (1, 2, 3)后, 执行l[0] = 0是可以的,但执行t[0] = 0则会报错。

如果我们要自定义一些数据结构,使之能够跟以上的容器类型表现一样,那就需要去实现某些协议。

这里的协议跟其他语言中所谓的”接口”概念很像,一样的需要你去实现才行,只不过没那么正式而已。

如果要自定义不可变容器类型,只需要定义__len__ 和 __getitem__方法;

如果要自定义可变容器类型,还需要在不可变容器类型的基础上增加定义__setitem__ 和 __delitem__

如果你希望你的自定义数据结构还支持”可迭代”, 那就还需要定义__iter__

__len__(self)

需要返回数值类型,以表示容器的长度。该方法在可变容器和不可变容器中必须实现。

__getitem__(self, key)

当你执行self[key]的时候,调用的就是该方法。该方法在可变容器和不可变容器中也都必须实现。
调用的时候,如果key的类型错误,该方法应该抛出TypeError;
如果没法返回key对应的数值时,该方法应该抛出ValueError。

__setitem__(self, key, value)

当你执行self[key] = value时,调用的是该方法。

__delitem__(self, key)

当你执行del self[key]的时候,调用的是该方法。

__iter__(self)

该方法需要返回一个迭代器(iterator)。当你执行for x in container: 或者使用iter(container)时,该方法被调用。

__reversed__(self)

如果想要该数据结构被內建函数reversed()支持,就还需要实现该方法。

__contains__(self, item)

如果定义了该方法,那么在执行item in container 或者 item not in container时该方法就会被调用。
如果没有定义,那么Python会迭代容器中的元素来一个一个比较,从而决定返回True或者False。

__missing__(self, key)

dict字典类型会有该方法,它定义了key如果在容器中找不到时触发的行为。
比如d = {'a': 1}, 当你执行d[notexist]时,d.__missing__['notexist']就会被调用。

下面举例,使用上面讲的魔术方法来实现Haskell语言中的一个数据结构。

# -*- coding: utf-8 -*-
class FunctionalList:
    ''' 实现了内置类型list的功能,并丰富了一些其他方法: head, tail, init, last, drop, 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):
        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 FunctionalList(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(self):
        # 获取最后一个元素
        return self.values[-1]
    def drop(self, n):
        # 获取所有元素,除了前N个
        return self.values[n:]
    def take(self, n):
        # 获取前N个元素
        return self.values[:n]

我们再举个例子,实现Perl语言的autovivification,它会在你每次引用一个值未定义的属性时为你自动创建数组或者字典。

class AutoVivification(dict):
    """Implementation of perl's autovivification feature."""
    def __missing__(self, key):
        value = self[key] = type(self)()
        return value

weather = AutoVivification()
weather['china']['guangdong']['shenzhen'] = 'sunny'
weather['china']['hubei']['wuhan'] = 'windy'
weather['USA']['California']['Los Angeles'] = 'sunny'
print weather

结果输出:{'china': {'hubei': {'wuhan': 'windy'}, 'guangdong': {'shenzhen': 'sunny'}}, 'USA':    {'California': {'Los Angeles': 'sunny'}}}

在Python中,关于自定义容器的实现还有更多实用的例子,但只有很少一部分能够集成在Python标准库中,比如Counter, OrderedDict等

上下文管理

with声明是从Python2.5开始引进的关键词。你应该遇过这样子的代码:

with open('foo.txt') as bar:
    # do something with bar

在with声明的代码段中,我们可以做一些对象的开始操作和清除操作,还能对异常进行处理。

这需要实现两个魔术方法: __enter__ 和 __exit__

__enter__(self)

__enter__会返回一个值,并赋值给as关键词之后的变量。在这里,你可以定义代码段开始的一些操作。

__exit__(self, exception_type, exception_value, traceback)

__exit__定义了代码段结束后的一些操作,可以这里执行一些清除操作,或者做一些代码段结束后需要立即执行的命令,比如文件的关闭,socket断开等。如果代码段成功结束,那么exception_type, exception_value, traceback 三个参数传进来时都将为None。如果代码段抛出异常,那么传进来的三个参数将分别为: 异常的类型,异常的值,异常的追踪栈。

如果__exit__返回True, 那么with声明下的代码段的一切异常将会被屏蔽。

如果__exit__返回None, 那么如果有异常,异常将正常抛出,这时候with的作用将不会显现出来。

举例说明:

这该示例中,IndexError始终会被隐藏,而TypeError始终会抛出。

class DemoManager(object):

    def __enter__(self):
        pass

    def __exit__(self, ex_type, ex_value, ex_tb):
        if ex_type is IndexError:
            print ex_value.__class__
            return True
        if ex_type is TypeError:
            print ex_value.__class__
            return  # return None

with DemoManager() as nothing:
    data = [1, 2, 3]
    data[4]  # raise IndexError, 该异常被__exit__处理了

with DemoManager() as nothing:
    data = [1, 2, 3]
    data['a']  # raise TypeError, 该异常没有被__exit__处理

输出:
<type 'exceptions.IndexError'>
<type 'exceptions.TypeError'>
Traceback (most recent call last):
  ...

对象的序列化

Python对象的序列化操作是pickling进行的。pickling非常的重要,以至于Python对此有单独的模块pickle,还有一些相关的魔术方法。使用pickling, 你可以将数据存储在文件中,之后又从文件中进行恢复。

下面举例来描述pickle的操作。从该例子中也可以看出,如果通过pickle.load 初始化一个对象, 并不会调用__init__方法。

# -*- coding: utf-8 -*-
from datetime import datetime
import pickle

class Distance(object):

    def __init__(self, meter):
        print 'distance __init__'
        self.meter = meter

data = {
    'foo': [1, 2, 3],
    'bar': ('Hello', 'world!'),
    'baz': True,
    'dt': datetime(2016, 10, 01),
    'distance': Distance(1.78),
}
print 'before dump:', data
with open('data.pkl', 'wb') as jar:
    pickle.dump(data, jar)  # 将数据存储在文件中

del data
print 'data is deleted!'

with open('data.pkl', 'rb') as jar:
    data = pickle.load(jar)  # 从文件中恢复数据
print 'after load:', data

值得一提,从其他文件进行pickle.load操作时,需要注意有恶意代码的可能性。另外,Python的各个版本之间,pickle文件可能是互不兼容的。

pickling并不是Python的內建类型,它支持所有实现pickle协议(可理解为接口)的类。pickle协议有以下几个可选方法来自定义Python对象的行为。

__getinitargs__(self)

如果你希望unpickle时,__init__方法能够调用,那么就需要定义__getinitargs__, 该方法需要返回一系列参数的元组,这些参数就是传给__init__的参数。

该方法只对old-style class有效。所谓old-style class,指的是不继承自任何对象的类,往往定义时这样表示: class A:, 而非class A(object):

__getnewargs__(self)

__getinitargs__很类似,只不过返回的参数元组将传值给__new__

__getstate__(self)

在调用pickle.dump时,默认是对象的__dict__属性被存储,如果你要修改这种行为,可以在__getstate__方法中返回一个state。state将在调用pickle.load时传值给__setstate__

__setstate__(self, state)

一般来说,定义了__getstate__,就需要相应地定义__setstate__来对__getstate__返回的state进行处理。

__reduce__(self)

如果pickle的数据包含了自定义的扩展类(比如使用C语言实现的Python扩展类)时,就需要通过实现__reduce__方法来控制行为了。由于使用过于生僻,这里就不展开继续讲解了。

令人容易混淆的是,我们知道, reduce()是Python的一个內建函数, 需要指出__reduce__并非定义了reduce()的行为,二者没有关系。

__reduce_ex__(self)

__reduce_ex__ 是为了兼容性而存在的, 如果定义了__reduce_ex__, 它将代替__reduce__ 执行。

下面的代码示例很有意思,我们定义了一个类Slate(中文是板岩的意思)。这个类能够记录历史上每次写入给它的值,但每次pickle.dump时当前值就会被清空,仅保留了历史。

# -*- coding: utf-8 -*-
import pickle
import time

class Slate:
    '''Class to store a string and a changelog, and forget its value when pickled.'''
    def __init__(self, value):
        self.value = value
        self.last_change = time.time()
        self.history = []

    def change(self, new_value):
        # 修改value, 将上次的valeu记录在history
        self.history.append((self.last_change, self.value))
        self.value = new_value
        self.last_change = time.time()

    def print_changes(self):
        print 'Changelog for Slate object:'
        for k, v in self.history:
            print '%s    %s' % (k, v)

    def __getstate__(self):
        # 故意不返回self.value和self.last_change,
        # 以便每次unpickle时清空当前的状态,仅仅保留history
        return self.history

    def __setstate__(self, state):
        self.history = state
        self.value, self.last_change = None, None

slate = Slate(0)
time.sleep(0.5)
slate.change(100)
time.sleep(0.5)
slate.change(200)
slate.change(300)
slate.print_changes()  # 与下面的输出历史对比
with open('slate.pkl', 'wb') as jar:
    pickle.dump(slate, jar)
del slate  # delete it
with open('slate.pkl', 'rb') as jar:
    slate = pickle.load(jar)
print 'current value:', slate.value  # None
print slate.print_changes()  # 输出历史记录与上面一致

运算符相关的魔术方法

运算符相关的魔术方法实在太多了,也很好理解,不打算多讲。在其他语言里,也有重载运算符的操作,所以我们对这些魔术方法已经很了解了。

比较运算符

__cmp__(self, other)

如果该方法返回负数,说明self 042dce579c75a4e6bf6232131b927758 other; 返回0说明self == other

强烈不推荐来定义__cmp__, 取而代之, 最好分别定义__lt__等方法从而实现比较功能。
__cmp__在Python3中被废弃了。

__eq__(self, other)

定义了比较操作符==的行为.

__ne__(self, other)

定义了比较操作符!=的行为.

__lt__(self, other)

定义了比较操作符ccf8deccb5da98fff4b9bc74a8c5ab3f的行为.

__le__(self, other)

定义了比较操作符64a319a9972386dadb9b47d23b11326e=的行为.

下面我们定义一种类型Word, 它会使用单词的长度来进行大小的比较, 而不是采用str的比较方式。
但是为了避免 Word('bar') == Word('foo') 这种违背直觉的情况出现,并没有定义__eq__, 因此Word会使用它的父类(str)中的__eq__来进行比较。

下面的例子中也可以看出: 在编程语言中, 如果a >=b and a 9999d93a92f2957b76a21b768be88604>.

__and__(self, other)

实现了位操作&.

__or__(self, other)

实现了位操作|

__xor__(self, other)

实现了位操作^

反算术运算符

这里只需要解释一下概念即可。假设针对some_object这个对象:

some_object + other

上面的代码非常正常地实现了some_object的__add__方法。那么如果遇到相反的情况呢?

other + some_object

这时候,如果other没有定义__add__方法,但是some_object定义了__radd__, 那么上面的代码照样可以运行。
这里的__radd__(self, other)就是__add__(self, other)的反算术运算符。

所以,类比的,我们就知道了更多的反算术运算符, 就不一一展开了:

  • __rsub__(self, other)

  • __rmul__(self, other)

  • __rmul__(self, other)

  • __rfloorp__(self, other)

  • __rp__(self, other)

  • __rtruep__(self, other)

  • __rmod__(self, other)

  • __rpmod__(self, other)

  • __rpow__(self, other)

  • __rlshift__(self, other)

  • __rrshift__(self, other)

  • __rand__(self, other)

  • __ror__(self, other)

  • __rxor__(self, other)

增量赋值

这也是只要理解了概念就容易掌握的运算。举个例子:

x = 5
x += 1  # 这里的+=就是增量赋值,将x+1赋值给了x

因此对于a += b__iadd__ 将返回a + b, 并赋值给a。
所以很容易理解下面的魔术方法了:

  • __iadd__(self, other)

  • __isub__(self, other)

  • __imul__(self, other)

  • __ifloorp__(self, other)

  • __ip__(self, other)

  • __itruep__(self, other)

  • __imod__(self, other)

  • __ipow__(self, other)

  • __ilshift__(self, other)

  • __irshift__(self, other)

  • __iand__(self, other)

  • __ior__(self, other)

  • __ixor__(self, other)

类型转化

__int__(self)

实现了类型转化为int的行为.

__long__(self)

实现了类型转化为long的行为.

__float__(self)

实现了类型转化为float的行为.

__complex__(self)

实现了类型转化为complex(复数, 也即1+2j这样的虚数)的行为.

__oct__(self)

实现了类型转化为八进制数的行为.

__hex__(self)

实现了类型转化为十六进制数的行为.

__index__(self)

在切片运算中将对象转化为int, 因此该方法的返回值必须是int。用一个例子来解释这个用法。

class Thing(object):
    def __index__(self):
        return 1

thing = Thing()
list_ = ['a', 'b', 'c']
print list_[thing]  # 'b'
print list_[thing:thing]  # []

上面例子中, list_[thing]的表现跟list_[1]一致,正是因为Thing实现了__index__方法。

可能有的人会想,list_[thing]为什么不是相当于list_[int(thing)]呢? 通过实现Thing的__int__方法能否达到这个目的呢?

显然不能。如果真的是这样的话,那么list_[1.1:2.2]这样的写法也应该是通过的。
而实际上,该写法会抛出TypeError: slice indices must be integers or None or have an __index__ method

下面我们再做个例子,如果对一个dict对象执行dict_[thing]会怎么样呢?

dict_ = {1: 'apple', 2: 'banana', 3: 'cat'}
print dict_[thing]  # raise KeyError

这个时候就不是调用__index__了。虽然listdict都实现了__getitem__方法, 但是它们的实现方式是不一样的。
如果希望上面例子能够正常执行, 需要实现Thing的__hash__ 和 __eq__方法.

class Thing(object):
    def __hash__(self):
        return 1
    def __eq__(self, other):
        return hash(self) == hash(other)

dict_ = {1: 'apple', 2: 'banana', 3: 'cat'}
print dict_[thing]  # apple

__coerce__(self, other)

实现了混合模式运算。

要了解这个方法,需要先了解coerce()内建函数: 官方文档上的解释是, coerce(x, y)返回一组数字类型的参数, 它们被转化为同一种类型,以便它们可以使用相同的算术运算符进行操作。如果过程中转化失败,抛出TypeError。

比如对于coerce(10, 10.1), 因为10和10.1在进行算术运算时,会先将10转为10.0再来运算。因此coerce(10, 10.1)返回值是(10.0, 10.1).

__coerce__在Python3中废弃了。

其他魔术方法

还没讲到的魔术方法还有很多,但有些我觉得很简单,或者很少见,就不再累赘展开说明了。

__str__(self)

对实例使用str()时调用。

__repr__(self)

对实例使用repr()时调用。str()repr()都是返回一个代表该实例的字符串,
主要区别在于: str()的返回值要方便人来看,而repr()的返回值要方便计算机看。

__unicode__(self)

对实例使用unicode()时调用。unicode()str()的区别在于: 前者返回值是unicode, 后者返回值是str。unicode和str都是basestring的子类。

当你对一个类只定义了__str__但没定义__unicode__时,__unicode__会根据__str__的返回值自动实现,即return unicode(self.__str__());
但返回来则不成立。

class StrDemo2:
    def __str__(self):
        return 'StrDemo2'

class StrDemo3:
    def __unicode__(self):
        return u'StrDemo3'

demo2 = StrDemo2()
print str(demo2)  # StrDemo2
print unicode(demo2)  # StrDemo2

demo3 = StrDemo3()
print str(demo3)  # <__main__.StrDemo3 instance>
print unicode(demo3)  # StrDemo3

__format__(self, formatstr)

"Hello, {0:abc}".format(a)等价于format(a, "abc"), 等价于a.__format__("abc")

这在需要格式化展示对象的时候非常有用,比如格式化时间对象。

__hash__(self)

对实例使用hash()时调用, 返回值是数值类型。

__nonzero__(self)

对实例使用bool()时调用, 返回True或者False。
你可能会问, 为什么不是命名为__bool__? 我也不知道。
我只知道该方法在Python3中改名为__bool__了。

__dir__(self)

对实例使用dir()时调用。通常实现该方法是没必要的。

__sizeof__(self)

对实例使用sys.getsizeof()时调用。返回对象的大小,单位是bytes。

__instancecheck__(self, instance)

对实例调用isinstance(instance, class)时调用。 返回值是布尔值。它会判断instance是否是该类的实例。

__subclasscheck__(self, subclass)

对实例使用issubclass(subclass, class)时调用。返回值是布尔值。它会判断subclass否是该类的子类。

__copy__(self)

对实例使用copy.copy()时调用。返回”浅复制”的对象。

__deepcopy__(self, memodict={})

对实例使用copy.deepcopy()时调用。返回”深复制”的对象。

__call__(self, [args...])

该方法允许类的实例跟函数一样表现:

class XClass:
    def __call__(self, a, b):
        return a + b

def add(a, b):
    return a + b

x = XClass()
print 'x(1, 2)', x(1, 2)
print 'callable(x)', callable(x)  # True
print 'add(1, 2)', add(1, 2)
print 'callable(add)', callable(add)  # True

Python3中的差异

  • Python3中,str与unicode的区别被废除了,因而__unicode__没有了,取而代之地出现了__bytes__.

  • Python3中,pision默认就是true pision, 因而__p__废弃.

  • __coerce__因存在冗余而废弃.

  • __cmp__因存在冗余而废弃.

  • __nonzero__改名为__bool__.

Das obige ist der detaillierte Inhalt vonEinführung in magische Methoden in Python. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn