> 백엔드 개발 > 파이썬 튜토리얼 > Python의 매직 메소드 소개

Python의 매직 메소드 소개

巴扎黑
풀어 주다: 2017-03-18 11:38:12
원래의
1389명이 탐색했습니다.
<h2>머리말</h2> <p>파이썬에서는 "__" 이중 밑줄로 묶인 모든 메소드를 통칭하여 "<span class="wp_keywordlink">매직 메소드</span>"라고 합니다. 예를 들어 <code>__init__</code>.</p> <p>우리가 가장 많이 접하게 되는 몇 가지 마술 방법은 평생 다시는 접하지 못할 수도 있으므로 간단히 소개하겠습니다. </p> <p>그리고 일부 마법의 방법은 독창적입니다. 이를 사용하여 복잡한 논리를 간단한 API로 캡슐화하는 등 매우 아름다운 코드를 구성할 수 있습니다. </p> <p>이 글의 편집 아이디어는 Rafe Kettler의 블로그: A Guide to Python Magic Methods에서 차용했으며, 일부 코드 예제가 추가되었습니다. </p> <p>소개 순서는 대략적으로 일반적인 것이 먼저 소개되고, 희귀한 것이 나중에 소개됩니다. </p> <p>이 기사에 사용된 코드 예제는 내 github에서 다운로드할 수 있습니다. </p> <h2>생성 및 초기화</h2> <p><code>__init__</code>객체가 초기화될 때 호출되는 것을 일반적으로 "생성자"라고 이해합니다. 실제로 </p>을 호출하면 <p>가 가장 먼저 실행되는 것이 아니고 <code>x = SomeClass()</code>이 실행됩니다. 정확하게 말하면 <code>__init__</code>과 <code>__new__</code>는 함께 "생성자"를 구성합니다. <code>__new__</code><code>__init__</code></p>은 클래스를 만들고 이 클래스의 인스턴스를 반환하는 데 사용되며 <p>는 매개변수만 전달합니다. 인스턴스를 초기화하려면 <code>__new__</code><code>__init__</code></p>이 인스턴스 생성 과정에서 호출되지만, 예를 들어 <p> <code>__new__</code>을 통해 인스턴스를 역직렬화하는 경우에는 <code>__init__</code>이 호출되지 않습니다. 호출되지 않습니다. <code>pickle.load</code><code>__init__</code></p> 메서드는 항상 클래스의 인스턴스를 반환해야 하지만 <p>는 None을 제외한 어떤 값도 반환할 수 없습니다. 예를 들어, 다음 예는 <code>__new__</code><div class="code" style="position:relative; padding:0px; margin:0px;"><pre class="brush: python; gutter: true; first-line: 1">class Foo(object): def __init__(self): print 'foo __init__' return None # 必须返回None,否则抛TypeError def __del__(self): print 'foo __del__'</pre><div class="contentsignin">로그인 후 복사</div></div><code>__init__</code>실제로 클래스 생성을 제어하려는 경우가 아니면 </p>를 거의 사용하지 않습니다. <p><code>__new__</code>을 설명하려면 <br/>(메타클래스)를 소개하는 경우가 많습니다. <code>__new__</code>더 깊이 알고 싶다면 내 다른 블로그인 Python의 메타클래스 이해를 참조하세요.<code>metaclass</code><br/></p>의 오버로딩에 대해서는 Python 문서에 자세한 소개도 있습니다. <p><code>__new__</code>객체 수명 주기가 끝나면 </p>이 호출됩니다. <p>는 "소멸자"로 이해될 수 있습니다. <code>__del__</code><code>__del__</code>은 객체가 가비지 수집 동작인 경우를 정의합니다. <br/><code>__del__</code>은 좀 오해하기 쉽습니다. 사실 </p>은 <p>의 구현이 아니지만, <code>x.__del__()</code>을 실행할 때 <code>del x</code>이 자주 호출되는데요. 위의 Foo 클래스 코드를 예시로 계속 사용하세요. <code>del x</code><div class="code" style="position:relative; padding:0px; margin:0px;"><pre class="brush: python; gutter: true; first-line: 1">foo = Foo() foo.__del__() print foo del foo print foo # NameError, foo is not defined</pre><div class="contentsignin">로그인 후 복사</div></div><code>x.__del__()</code></p>가 호출되면 객체 자체는 여전히 존재합니다. 그러나 <p>가 호출되면 객체가 없습니다. foo 더 이상 .</p><p>인터프리터가 종료될 때 객체가 여전히 존재하는 경우 <code>foo.__del__()</code>가 정확하게 실행된다는 보장은 없습니다. 따라서 <code>del foo</code>은 좋은 프로그래밍 습관을 대체할 수 없습니다. </p>예를 들어 소켓을 처리할 때 적시에 완료된 연결을 닫습니다. <p><code>__del__</code>속성 액세스 제어<code>__del__</code><br/>Python의 클래스 캡슐화가 부족하다고 불평하는 사람들이 항상 있습니다. 예를 들어 Python이 비공개 속성을 정의한 다음 공개적으로 액세스 가능한 getter 및 setter를 제공할 수 있기를 바랍니다. Python은 실제로 매직 메소드를 통해 캡슐화를 달성할 수 있습니다. </p><h2></h2><p></p>이 메서드는 존재하지 않는 속성에 액세스하려고 할 때의 동작을 정의합니다. 따라서 이 메서드를 오버로드하면 맞춤법 오류를 찾아 리디렉션하거나 더 이상 사용되지 않는 일부 속성에 대해 경고할 수 있습니다. <h4><code>__getattr__(self, name)</code></h4><p></p><h4>은 속성을 할당하고 수정할 때의 동작을 정의하는 캡슐화를 구현하는 솔루션입니다. <code>__setattr__(self, name, value)</code>객체의 특정 속성이 존재하는지 여부에 관계없이 속성에 값을 할당할 수 있으므로 속성 값에 대해 사용자 정의 작업을 수행할 수 있습니다. 한 가지 주의할 점은 </h4> 구현 시 "무한 재귀" 오류를 방지하는 것입니다. 이에 대해서는 아래 코드 예제에서 언급하겠습니다. <p><code>__setattr__</code><br/><code>__setattr__</code></p><h4>은 속성을 삭제할 때의 동작을 정의한다는 점을 제외하면 <code>__delattr__(self, name)</code>과 매우 유사합니다. </h4>을 구현하는 것은 "무한 재귀" 오류를 동시에 방지하기 위한 것입니다. <p><code>__delattr__</code><code>__setattr__</code><code>__delattr__</code></p><h4>은 속성에 액세스할 때의 동작을 정의합니다. 반면 <code>__getattribute__(self, name)</code>은 속성이 존재하지 않는 경우에만 작동합니다. </h4><p> 따라서 <code>__getattribute__</code>을 지원하는 Python 버전에서는 <code>__getattr__</code>을 호출하기 전에 </p>을 사용해야 합니다. <p>'무한 재귀' 오류도 방지하세요. <code>__getattribute__</code><code>__getattr__</code><code>__getattribute__</code>을 구현하려고 하지 않는 것이 가장 좋다는 점을 기억해야 합니다. 이 접근 방식은 거의 볼 수 없고 버그를 일으키기 쉽기 때문입니다. <code>__getattribute__</code></p>예제는 <p>:<code>__getattribute__</code><div class="code" style="position:relative; padding:0px; margin:0px;"><pre class="brush: python; gutter: true; first-line: 1">def __setattr__(self, name, value): self.name = value # 每一次属性赋值时, __setattr__都会被调用,因此不断调用自身导致无限递归了。</pre><div class="contentsignin">로그인 후 복사</div></div></p>의 무한 재귀 오류를 보여줍니다. 따라서 올바른 작성 방법은 다음과 같습니다.<p><div class="code" style="position:relative; padding:0px; margin:0px;"><pre class="brush: python; gutter: true; first-line: 1">def __setattr__(self, name, value): self.__dict__[name] = value</pre><div class="contentsignin">로그인 후 복사</div></div><p><code>__delattr__</code>如果在其实现中出现<code>del self.name</code> 这样的代码也会出现”无限递归”错误,这是一样的原因。</p><p>下面的例子很好的说明了上面介绍的4个魔术方法的调用情况:</p><div class="code" style="position:relative; padding:0px; margin:0px;"><pre class="brush: python; gutter: true; first-line: 1">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__调用</pre><div class="contentsignin">로그인 후 복사</div></div><h2>描述器对象</h2><p>我们从一个例子来入手,介绍什么是描述符,并介绍<code>__get__</code>, <code>__set__</code>, <code>__delete__</code> 的使用。(放在这里介绍是为了跟上一小节介绍的魔术方法作对比)</p><p>我们知道,距离既可以用单位”米”表示,也可以用单位”英尺”表示。现在我们定义一个类来表示距离,它有两个属性: 米和英尺。</p><div class="code" style="position:relative; padding:0px; margin:0px;"><pre class="brush: python; gutter: true; first-line: 1">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</pre><div class="contentsignin">로그인 후 복사</div></div><p>在上面例子中,在还没有对Distance的实例赋值前, 我们认为meter和foot应该是各自类的实例对象, 但是输出却是数值。这是因为<code>__get__</code>发挥了作用.</p><p>我们只是修改了meter,并且将其赋值成为int,但foot也修改了。这是<code>__set__</code>发挥了作用.</p><p>描述器对象(Meter、Foot)不能独立存在, 它需要被另一个所有者类(Distance)所持有。</p><p>描述器对象可以访问到其拥有者实例的属性,比如例子中Foot的<code>instance.meter</code>。</p><p>在面向对象编程时,如果一个类的属性有相互依赖的关系时,使用描述器来编写代码可以很巧妙的组织逻辑。在Django的ORM中, models.Model中的InterField等字段, 就是通过描述器来实现功能的。</p><p>一个类要成为描述器,必须实现<code>__get__</code>, <code>__set__</code>, <code>__delete__</code> 中的至少一个方法。下面简单介绍下:</p><h4><code>__get__(self, instance, owner)</code></h4><p>参数instance是拥有者类的实例。参数owner是拥有者类本身。<code>__get__</code>在其拥有者对其读值的时候调用。</p><h4><code>__set__(self, instance, value)</code></h4><p><code>__set__</code>在其拥有者对其进行修改值的时候调用。</p><h4><code>__delete__(self, instance)</code></h4><p><code>__delete__</code>在其拥有者对其进行删除的时候调用。</p><h2>构造自定义容器(Container)</h2><p>在Python中,常见的容器类型有: dict, tuple, list, string。</p><p>其中tuple, string是不可变容器,dict, list是可变容器。</p><p>可变容器和不可变容器的区别在于,不可变容器一旦赋值后,不可对其中的某个元素进行修改。</p><p>比如定义了<code>l = [1, 2, 3]</code>和<code>t = (1, 2, 3)</code>后, 执行<code>l[0] = 0</code>是可以的,但执行<code>t[0] = 0</code>则会报错。</p><p>如果我们要自定义一些数据结构,使之能够跟以上的容器类型表现一样,那就需要去实现某些协议。</p><p>这里的协议跟其他语言中所谓的”接口”概念很像,一样的需要你去实现才行,只不过没那么正式而已。</p><p>如果要自定义不可变容器类型,只需要定义<code>__len__</code> 和 <code>__getitem__</code>方法;</p><p>如果要自定义可变容器类型,还需要在不可变容器类型的基础上增加定义<code>__setitem__</code> 和 <code>__delitem__</code>。</p><p>如果你希望你的自定义数据结构还支持”可迭代”, 那就还需要定义<code>__iter__</code>。</p><h4><code>__len__(self)</code></h4><p>需要返回数值类型,以表示容器的长度。该方法在可变容器和不可变容器中必须实现。</p><h4><code>__getitem__(self, key)</code></h4><p>当你执行<code>self[key]</code>的时候,调用的就是该方法。该方法在可变容器和不可变容器中也都必须实现。<br/>调用的时候,如果key的类型错误,该方法应该抛出TypeError;<br/>如果没法返回key对应的数值时,该方法应该抛出ValueError。</p><h4><code>__setitem__(self, key, value)</code></h4><p>当你执行<code>self[key] = value</code>时,调用的是该方法。</p><h4><code>__delitem__(self, key)</code></h4><p>当你执行<code>del self[key]</code>的时候,调用的是该方法。</p><h4><code>__iter__(self)</code></h4><p>该方法需要返回一个迭代器(iterator)。当你执行<code>for x in container:</code> 或者使用<code>iter(container)</code>时,该方法被调用。</p><h4><code>__reversed__(self)</code></h4><p>如果想要该数据结构被內建函数<code>reversed()</code>支持,就还需要实现该方法。</p><h4><code>__contains__(self, item)</code></h4><p>如果定义了该方法,那么在执行<code>item in container</code> 或者 <code>item not in container</code>时该方法就会被调用。<br/>如果没有定义,那么Python会迭代容器中的元素来一个一个比较,从而决定返回True或者False。</p><h4><code>__missing__(self, key)</code></h4><p><code>dict</code>字典类型会有该方法,它定义了key如果在容器中找不到时触发的行为。<br/>比如<code>d = {'a': 1}</code>, 当你执行<code>d[notexist]</code>时,<code>d.__missing__['notexist']</code>就会被调用。</p><p>下面举例,使用上面讲的魔术方法来实现Haskell语言中的一个数据结构。</p><div class="code" style="position:relative; padding:0px; margin:0px;"><pre class="brush: python; gutter: true; first-line: 1"># -*- 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]</pre><div class="contentsignin">로그인 후 복사</div></div><p>我们再举个例子,实现Perl语言的autovivification,它会在你每次引用一个值未定义的属性时为你自动创建数组或者字典。</p><div class="code" style="position:relative; padding:0px; margin:0px;"><pre class="brush: python; gutter: true; first-line: 1">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'}}}</pre><div class="contentsignin">로그인 후 복사</div></div><p>在Python中,关于自定义容器的实现还有更多实用的例子,但只有很少一部分能够集成在Python标准库中,比如Counter, OrderedDict等</p><h2>上下文管理</h2><p><code>with</code>声明是从Python2.5开始引进的关键词。你应该遇过这样子的代码:</p><div class="code" style="position:relative; padding:0px; margin:0px;"><pre class="brush: python; gutter: true; first-line: 1">with open('foo.txt') as bar: # do something with bar</pre><div class="contentsignin">로그인 후 복사</div></div><p>在with声明的代码段中,我们可以做一些对象的开始操作和清除操作,还能对异常进行处理。</p><p>这需要实现两个魔术方法: <code>__enter__</code> 和 <code>__exit__</code>。</p><h4><code>__enter__(self)</code></h4><p><code>__enter__</code>会返回一个值,并赋值给<code>as</code>关键词之后的变量。在这里,你可以定义代码段开始的一些操作。</p><h4><code>__exit__(self, exception_type, exception_value, traceback)</code></h4><p><code>__exit__</code>定义了代码段结束后的一些操作,可以这里执行一些清除操作,或者做一些代码段结束后需要立即执行的命令,比如文件的关闭,socket断开等。如果代码段成功结束,那么exception_type, exception_value, traceback 三个参数传进来时都将为None。如果代码段抛出异常,那么传进来的三个参数将分别为: 异常的类型,异常的值,异常的追踪栈。</p><p>如果<code>__exit__</code>返回True, 那么with声明下的代码段的一切异常将会被屏蔽。</p><p>如果<code>__exit__</code>返回None, 那么如果有异常,异常将正常抛出,这时候with的作用将不会显现出来。</p><p>举例说明:</p><p>这该示例中,IndexError始终会被隐藏,而TypeError始终会抛出。</p><div class="code" style="position:relative; padding:0px; margin:0px;"><pre class="brush: python; gutter: true; first-line: 1">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): ...</pre><div class="contentsignin">로그인 후 복사</div></div><h2>对象的序列化</h2><p>Python对象的序列化操作是pickling进行的。pickling非常的重要,以至于Python对此有单独的模块<code>pickle</code>,还有一些相关的魔术方法。使用pickling, 你可以将数据存储在文件中,之后又从文件中进行恢复。</p><p>下面举例来描述pickle的操作。从该例子中也可以看出,如果通过pickle.load 初始化一个对象, 并不会调用<code>__init__</code>方法。</p><div class="code" style="position:relative; padding:0px; margin:0px;"><pre class="brush: python; gutter: true; first-line: 1"># -*- 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</pre><div class="contentsignin">로그인 후 복사</div></div><p>值得一提,从其他文件进行pickle.load操作时,需要注意有恶意代码的可能性。另外,Python的各个版本之间,pickle文件可能是互不兼容的。</p><p>pickling并不是Python的內建类型,它支持所有实现pickle协议(可理解为接口)的类。pickle协议有以下几个可选方法来自定义Python对象的行为。</p><h4><code>__getinitargs__(self)</code></h4><p>如果你希望unpickle时,<code>__init__</code>方法能够调用,那么就需要定义<code>__getinitargs__</code>, 该方法需要返回一系列参数的元组,这些参数就是传给<code>__init__</code>的参数。</p><p>该方法只对<code>old-style class</code>有效。所谓<code>old-style class</code>,指的是不继承自任何对象的类,往往定义时这样表示: <code>class A:</code>, 而非<code>class A(object):</code></p><h4><code>__getnewargs__(self)</code></h4><p>跟<code>__getinitargs__</code>很类似,只不过返回的参数元组将传值给<code>__new__</code></p><h4><code>__getstate__(self)</code></h4><p>在调用<code>pickle.dump</code>时,默认是对象的<code>__dict__</code>属性被存储,如果你要修改这种行为,可以在<code>__getstate__</code>方法中返回一个state。state将在调用<code>pickle.load</code>时传值给<code>__setstate__</code></p><h4><code>__setstate__(self, state)</code></h4><p>一般来说,定义了<code>__getstate__</code>,就需要相应地定义<code>__setstate__</code>来对<code>__getstate__</code>返回的state进行处理。</p><h4><code>__reduce__(self)</code></h4><p>如果pickle的数据包含了自定义的扩展类(比如使用C语言实现的Python扩展类)时,就需要通过实现<code>__reduce__</code>方法来控制行为了。由于使用过于生僻,这里就不展开继续讲解了。</p><p>令人容易混淆的是,我们知道, <code>reduce()</code>是Python的一个內建函数, 需要指出<code>__reduce__</code>并非定义了<code>reduce()</code>的行为,二者没有关系。</p><h4><code>__reduce_ex__(self)</code></h4><p><code>__reduce_ex__</code> 是为了兼容性而存在的, 如果定义了<code>__reduce_ex__</code>, 它将代替<code>__reduce__</code> 执行。</p><p>下面的代码示例很有意思,我们定义了一个类Slate(中文是板岩的意思)。这个类能够记录历史上每次写入给它的值,但每次<code>pickle.dump</code>时当前值就会被清空,仅保留了历史。</p><div class="code" style="position:relative; padding:0px; margin:0px;"><pre class="brush: python; gutter: true; first-line: 1"># -*- 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() # 输出历史记录与上面一致</pre><div class="contentsignin">로그인 후 복사</div></div><h2>运算符相关的魔术方法</h2><p>运算符相关的魔术方法实在太多了,也很好理解,不打算多讲。在其他语言里,也有重载运算符的操作,所以我们对这些魔术方法已经很了解了。</p><h3>比较运算符</h3><h4><code>__cmp__(self, other)</code></h4><p>如果该方法返回负数,说明<code>self < other</code>; 返回正数,说明<code>self > other</code>; 返回0说明<code>self == other</code>。</p><p>强烈不推荐来定义<code>__cmp__</code>, 取而代之, 最好分别定义<code>__lt__</code>等方法从而实现比较功能。<br/><code>__cmp__</code>在Python3中被废弃了。</p><h4><code>__eq__(self, other)</code></h4><p>定义了比较操作符<code>==</code>的行为.</p><h4><code>__ne__(self, other)</code></h4><p>定义了比较操作符<code>!=</code>的行为.</p><h4><code>__lt__(self, other)</code></h4><p>定义了比较操作符<code><</code>的行为.</p><h4><code>__gt__(self, other)</code></h4><p>定义了比较操作符<code>></code>的行为.</p><h4><code>__le__(self, other)</code></h4><p>定义了比较操作符<code><=</code>的行为.</p><h4><code>__ge__(self, other)</code></h4><p>定义了比较操作符<code>>=</code>的行为.</p><p>下面我们定义一种类型Word, 它会使用单词的长度来进行大小的比较, 而不是采用str的比较方式。<br/>但是为了避免 <code>Word('bar') == Word('foo')</code> 这种违背直觉的情况出现,并没有定义<code>__eq__</code>, 因此Word会使用它的父类(str)中的<code>__eq__</code>来进行比较。</p><p>下面的例子中也可以看出: 在编程语言中, 如果<code>a >=b and a <= b</code>, 并不能推导出<code>a == b</code>这样的结论。</p><div class="code" style="position:relative; padding:0px; margin:0px;"><pre class="brush: python; gutter: true; first-line: 1"># -*- coding: utf-8 -*- class Word(str): '''存储单词的类,定义比较单词的几种方法''' def __new__(cls, word): # 注意我们必须要用到__new__方法,因为str是不可变类型 # 所以我们必须在创建的时候将它初始化 if ' ' in word: print "Value contains spaces. Truncating to first space." 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) print 'foo < fool:', Word('foo') < Word('fool') # True print 'foolish > fool:', Word('foolish') > Word('fool') # True print 'bar >= foo:', Word('bar') >= Word('foo') # True print 'bar <= foo:', Word('bar') <= Word('foo') # True print 'bar == foo:', Word('bar') == Word('foo') # False, 用了str内置的比较方法来进行比较 print 'bar != foo:', Word('bar') != Word('foo') # True</pre><div class="contentsignin">로그인 후 복사</div></div><h3>一元运算符和函数</h3><h4><code>__pos__(self)</code></h4><p>实现了’+'号一元运算符(比如<code>+some_object</code>)</p><h4><code>__neg__(self)</code></h4><p>实现了’-'号一元运算符(比如<code>-some_object</code>)</p><h4><code>__invert__(self)</code></h4><p>实现了<code>~</code>号一元运算符(比如<code>~some_object</code>)</p><h4><code>__abs__(self)</code></h4><p>实现了<code>abs()</code>內建函数.</p><h4><code>__round__(self, n)</code></h4><p>实现了<code>round()</code>内建函数. 参数n表示四舍五进的精度.</p><h4><code>__floor__(self)</code></h4><p>实现了<code>math.round()</code>, 向下取整.</p><h4><code>__ceil__(self)</code></h4><p>实现了<code>math.ceil()</code>, 向上取整.</p><h4><code>__trunc__(self)</code></h4><p>实现了<code>math.trunc()</code>, 向0取整.</p><h3>算术运算符</h3><h4><code>__add__(self, other)</code></h4><p>实现了加号运算.</p><h4><code>__sub__(self, other)</code></h4><p>实现了减号运算.</p><h4><code>__mul__(self, other)</code></h4><p>实现了乘法运算.</p><h4><code>__floorp__(self, other)</code></h4><p>实现了<code>//</code>运算符.</p><h4><code>__p__(self, other)</code></h4><p>实现了<code>/</code>运算符. 该方法在Python3中废弃. 原因是Python3中,pision默认就是true pision.</p><h4><code>__truep__</code>(self, other)</h4><p>实现了true pision. 只有你声明了<code>from __future__ import pision</code>该方法才会生效.</p><h4><code>__mod__(self, other)</code></h4><p>实现了<code>%</code>运算符, 取余运算.</p><h4><code>__pmod__(self, other)</code></h4><p>实现了<code>pmod()</code>內建函数.</p><h4><code>__pow__(self, other)</code></h4><p>实现了<code>**</code>操作. N次方操作.</p><h4><code>__lshift__(self, other)</code></h4><p>实现了位操作<code><<</code>.</p><h4><code>__rshift__(self, other)</code></h4><p>实现了位操作<code>>></code>.</p><h4><code>__and__(self, other)</code></h4><p>实现了位操作<code>&</code>.</p><h4><code>__or__(self, other)</code></h4><p>实现了位操作<code>|</code></p><h4><code>__xor__(self, other)</code></h4><p>实现了位操作<code>^</code></p><h3>反算术运算符</h3><p>这里只需要解释一下概念即可。假设针对some_object这个对象:</p><div class="code" style="position:relative; padding:0px; margin:0px;"><pre class="brush: python; gutter: true; first-line: 1">some_object + other</pre><div class="contentsignin">로그인 후 복사</div></div><p>上面的代码非常正常地实现了some_object的<code>__add__</code>方法。那么如果遇到相反的情况呢?</p><div class="code" style="position:relative; padding:0px; margin:0px;"><pre class="brush: python; gutter: true; first-line: 1">other + some_object</pre><div class="contentsignin">로그인 후 복사</div></div><p>这时候,如果other没有定义<code>__add__</code>方法,但是some_object定义了<code>__radd__</code>, 那么上面的代码照样可以运行。<br/>这里的<code>__radd__(self, other)</code>就是<code>__add__(self, other)</code>的反算术运算符。</p><p>所以,类比的,我们就知道了更多的反算术运算符, 就不一一展开了:</p><ul class=" list-paddingleft-2"><li><p><code>__rsub__(self, other)</code></p></li><li><p><code>__rmul__(self, other)</code></p></li><li><p><code>__rmul__(self, other)</code></p></li><li><p><code>__rfloorp__(self, other)</code></p></li><li><p><code>__rp__(self, other)</code></p></li><li><p><code>__rtruep__(self, other)</code></p></li><li><p><code>__rmod__(self, other)</code></p></li><li><p><code>__rpmod__(self, other)</code></p></li><li><p><code>__rpow__(self, other)</code></p></li><li><p><code>__rlshift__(self, other)</code></p></li><li><p><code>__rrshift__(self, other)</code></p></li><li><p><code>__rand__(self, other)</code></p></li><li><p><code>__ror__(self, other)</code></p></li><li><p><code>__rxor__(self, other)</code></p></li></ul><h3>增量赋值</h3><p>这也是只要理解了概念就容易掌握的运算。举个例子:</p><div class="code" style="position:relative; padding:0px; margin:0px;"><pre class="brush: python; gutter: true; first-line: 1">x = 5 x += 1 # 这里的+=就是增量赋值,将x+1赋值给了x</pre><div class="contentsignin">로그인 후 복사</div></div><p>因此对于<code>a += b</code>, <code>__iadd__</code> 将返回<code>a + b</code>, 并赋值给a。<br/>所以很容易理解下面的魔术方法了:</p><ul class=" list-paddingleft-2"><li><p><code>__iadd__(self, other)</code></p></li><li><p><code>__isub__(self, other)</code></p></li><li><p><code>__imul__(self, other)</code></p></li><li><p><code>__ifloorp__(self, other)</code></p></li><li><p><code>__ip__(self, other)</code></p></li><li><p><code>__itruep__(self, other)</code></p></li><li><p><code>__imod__(self, other)</code></p></li><li><p><code>__ipow__(self, other)</code></p></li><li><p><code>__ilshift__(self, other)</code></p></li><li><p><code>__irshift__(self, other)</code></p></li><li><p><code>__iand__(self, other)</code></p></li><li><p><code>__ior__(self, other)</code></p></li><li><p><code>__ixor__(self, other)</code></p></li></ul><h3>类型转化</h3><h4><code>__int__(self)</code></h4><p>实现了类型转化为int的行为.</p><h4><code>__long__(self)</code></h4><p>实现了类型转化为long的行为.</p><h4><code>__float__(self)</code></h4><p>实现了类型转化为float的行为.</p><h4><code>__complex__(self)</code></h4><p>实现了类型转化为complex(复数, 也即1+2j这样的虚数)的行为.</p><h4><code>__oct__(self)</code></h4><p>实现了类型转化为八进制数的行为.</p><h4><code>__hex__(self)</code></h4><p>实现了类型转化为十六进制数的行为.</p><h4><code>__index__(self)</code></h4><p>在切片运算中将对象转化为int, 因此该方法的返回值必须是int。用一个例子来解释这个用法。</p><div class="code" style="position:relative; padding:0px; margin:0px;"><pre class="brush: python; gutter: true; first-line: 1">class Thing(object): def __index__(self): return 1 thing = Thing() list_ = ['a', 'b', 'c'] print list_[thing] # 'b' print list_[thing:thing] # []</pre><div class="contentsignin">로그인 후 복사</div></div><p>上面例子中, <code>list_[thing]</code>的表现跟<code>list_[1]</code>一致,正是因为Thing实现了<code>__index__</code>方法。</p><p>可能有的人会想,<code>list_[thing]</code>为什么不是相当于<code>list_[int(thing)]</code>呢? 通过实现Thing的<code>__int__</code>方法能否达到这个目的呢?</p><p>显然不能。如果真的是这样的话,那么<code>list_[1.1:2.2]</code>这样的写法也应该是通过的。<br/>而实际上,该写法会抛出TypeError: <code>slice indices must be integers or None or have an __index__ method</code></p><p>下面我们再做个例子,如果对一个dict对象执行<code>dict_[thing]</code>会怎么样呢?</p><div class="code" style="position:relative; padding:0px; margin:0px;"><pre class="brush: python; gutter: true; first-line: 1">dict_ = {1: 'apple', 2: 'banana', 3: 'cat'} print dict_[thing] # raise KeyError</pre><div class="contentsignin">로그인 후 복사</div></div><p>这个时候就不是调用<code>__index__</code>了。虽然<code>list</code>和<code>dict</code>都实现了<code>__getitem__</code>方法, 但是它们的实现方式是不一样的。<br/>如果希望上面例子能够正常执行, 需要实现Thing的<code>__hash__</code> 和 <code>__eq__</code>方法.</p><div class="code" style="position:relative; padding:0px; margin:0px;"><pre class="brush: python; gutter: true; first-line: 1">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</pre><div class="contentsignin">로그인 후 복사</div></div><h4><code>__coerce__(self, other)</code></h4><p>实现了混合模式运算。</p><p>要了解这个方法,需要先了解<code>coerce()</code>内建函数: 官方文档上的解释是, coerce(x, y)返回一组数字类型的参数, 它们被转化为同一种类型,以便它们可以使用相同的算术运算符进行操作。如果过程中转化失败,抛出TypeError。</p><p>比如对于<code>coerce(10, 10.1)</code>, 因为10和10.1在进行算术运算时,会先将10转为10.0再来运算。因此<code>coerce(10, 10.1)</code>返回值是(10.0, 10.1).</p><p><code>__coerce__</code>在Python3中废弃了。</p><h2>其他魔术方法</h2><p>还没讲到的魔术方法还有很多,但有些我觉得很简单,或者很少见,就不再累赘展开说明了。</p><h4><code>__str__(self)</code></h4><p>对实例使用<code>str()</code>时调用。</p><h4><code>__repr__(self)</code></h4><p>对实例使用<code>repr()</code>时调用。<code>str()</code>和<code>repr()</code>都是返回一个代表该实例的字符串,<br/>主要区别在于: str()的返回值要方便人来看,而repr()的返回值要方便计算机看。</p><h4><code>__unicode__(self)</code></h4><p>对实例使用<code>unicode()</code>时调用。<code>unicode()</code>与<code>str()</code>的区别在于: 前者返回值是unicode, 后者返回值是str。unicode和str都是<code>basestring</code>的子类。</p><p>当你对一个类只定义了<code>__str__</code>但没定义<code>__unicode__</code>时,<code>__unicode__</code>会根据<code>__str__</code>的返回值自动实现,即<code>return unicode(self.__str__())</code>;<br/>但返回来则不成立。</p><div class="code" style="position:relative; padding:0px; margin:0px;"><pre class="brush: python; gutter: true; first-line: 1">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</pre><div class="contentsignin">로그인 후 복사</div></div><h4><code>__format__(self, formatstr)</code></h4><p><code>"Hello, {0:abc}".format(a)</code>等价于<code>format(a, "abc")</code>, 等价于<code>a.__format__("abc")</code>。</p><p>这在需要格式化展示对象的时候非常有用,比如格式化时间对象。</p><h4><code>__hash__(self)</code></h4><p>对实例使用<code>hash()</code>时调用, 返回值是数值类型。</p><h4><code>__nonzero__(self)</code></h4><p>对实例使用<code>bool()</code>时调用, 返回True或者False。<br/>你可能会问, 为什么不是命名为<code>__bool__</code>? 我也不知道。<br/>我只知道该方法在Python3中改名为<code>__bool__</code>了。</p><h4><code>__dir__(self)</code></h4><p>对实例使用<code>dir()</code>时调用。通常实现该方法是没必要的。</p><h4><code>__sizeof__(self)</code></h4><p>对实例使用<code>sys.getsizeof()</code>时调用。返回对象的大小,单位是bytes。</p><h4><code>__instancecheck__(self, instance)</code></h4><p>对实例调用<code>isinstance(instance, class)</code>时调用。 返回值是布尔值。它会判断instance是否是该类的实例。</p><h4><code>__subclasscheck__(self, subclass)</code></h4><p>对实例使用<code>issubclass(subclass, class)</code>时调用。返回值是布尔值。它会判断subclass否是该类的子类。</p><h4><code>__copy__(self)</code></h4><p>对实例使用<code>copy.copy()</code>时调用。返回”浅复制”的对象。</p><h4><code>__deepcopy__(self, memodict={})</code></h4><p>对实例使用<code>copy.deepcopy()</code>时调用。返回”深复制”的对象。</p><h4><code>__call__(self, [args...])</code></h4><p>该方法允许类的实例跟函数一样表现:</p><div class="code" style="position:relative; padding:0px; margin:0px;"><pre class="brush: python; gutter: true; first-line: 1">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</pre><div class="contentsignin">로그인 후 복사</div></div></p> <h2>Python3中的差异</h2> <ul class=" list-paddingleft-2"> <li><p>Python3中,str与unicode的区别被废除了,因而<code>__unicode__</code>没有了,取而代之地出现了<code>__bytes__</code>.</p></li> <li><p>Python3中,pision默认就是true pision, 因而<code>__p__</code>废弃.</p></li> <li><p><code>__coerce__</code>因存在冗余而废弃.</p></li> <li><p><code>__cmp__</code>因存在冗余而废弃.</p></li> <li><p><code>__nonzero__</code>改名为<code>__bool__</code>.</p></li> </ul> <div></div>

위 내용은 Python의 매직 메소드 소개의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

관련 라벨:
원천:php.cn
본 웹사이트의 성명
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.
인기 튜토리얼
더>
최신 다운로드
더>
웹 효과
웹사이트 소스 코드
웹사이트 자료
프론트엔드 템플릿