Pythonのマジックメソッドのガイド

これは、Rafe Kettlerによるマニュアルの 1.17バージョンの翻訳です。





内容



  1. エントリー
  2. 設計と初期化
  3. 任意のクラスの演算子をオーバーライドする
  4. クラスのプレゼンテーション
  5. 属性アクセス制御
  6. カスタムシーケンスを作成する
  7. 反射
  8. 呼び出されたオブジェクト
  9. コンテキストマネージャー
  10. 抽象基本クラス
  11. 記述子の構築
  12. コピー
  13. オブジェクトでpickleモジュールを使用する
  14. おわりに
  15. 付録1:マジックメソッドの呼び出し方法
  16. 付録2:Python 3の変更




エントリー



魔法の方法とは何ですか? それらはすべてオブジェクト指向Pythonです。 これらは、クラスに「魔法」を追加できる特別なメソッドです。 これらは常に2つのアンダースコアで囲まれています(たとえば、 __lt__



または__lt__



)。 また、私たちが望むほど文書化されていません。 すべてのマジックメソッドはドキュメントに記載されていますが、非常にランダムで、ほとんど組織化されていません。 したがって、Pythonのドキュメントがないと私が感じているものを修正するために、理解しやすい言語で記述され、例が豊富に提供されているマジックメソッドに関する詳細情報を提供します。 このガイドをお楽しみください。 トレーニング資料、メモ、または詳細な説明として使用します。 私は魔法の方法をできるだけ明確に説明しようとしました。





設計と初期化。



誰もが最も基本的な魔法のメソッド__init__



知っています。 これにより、オブジェクトを初期化できます。 ただし、 x = SomeClass()



と書くと、 __init__



最初に呼び出されるもので__init__



ません。 実際、オブジェクトのインスタンスが__new__



メソッドを作成し、引数が初期化子に渡されます。 オブジェクトのライフサイクルのもう一方の端は__del__



メソッドです。 これら3つの魔法の方法を詳しく見てみましょう。





それをすべてまとめると、ここに__init__



__del__



が動作する例があり__del__







 from os.path import join class FileObject: '''   ,     ,      .''' def __init__(self, filepath='~', filename='sample.txt'): #   filename  filepath      self.file = open(join(filepath, filename), 'r+') def __del__(self): self.file.close() del self.file
      
      







任意のクラスの演算子をオーバーライドする



Pythonでマジックメソッドを使用することの大きな利点の1つは、組み込みメソッドと同じようにオブジェクトを動作させる簡単な方法を提供することです。 これは、ベース演算子の退屈で非論理的な非標準の動作を回避できることを意味します。 一部の言語では、次のような記述が一般的です。



 if instance.equals(other_instance): # do something
      
      





もちろん、Pythonでも同じことができますが、これにより混乱と不必要な冗長性が追加されます。 異なるライブラリーは同じ操作を異なる方法で呼び出し、それらを使用するプログラマーに必要以上のアクションを実行させます。 マジックメソッドの力を使用して、目的のメソッド(この場合は__eq__



)を決定し、 念頭に置いたものを正確に表現できます



 if instance == other_instance: #do something
      
      





これは、魔法の方法の長所の1つです。 それらの大部分は、標準演算子が何をするかを決定できるため、組み込み型であるかのようにクラスで演算子を使用できます。





魔法の比較方法



Pythonには、不器用なメソッドではなく、演算子を使用してオブジェクト間の直感的な比較を定義するために設計された多数のマジックメソッドがあります。 さらに、Pythonのデフォルトの動作をオーバーライドしてオブジェクトを比較する方法を提供します(参照により)。 これらのメソッドとその機能のリストは次のとおりです。





たとえば、単語を説明するクラスを考えます。 単語を辞書順(アルファベット順)で比較できます。これは文字列を比較するときのデフォルトの動作ですが、比較するときは、音節の長さや数など、他の基準を使用することもできます。 この例では、長さを比較します。 実装は次のとおりです。



 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)
      
      





これで、2つのWord



Word('foo')



Word('bar')



)を作成し、長さを比較できます。 __eq__



__ne__



定義していないことに注意してください。これ__ne__



、奇妙な動作が発生します(たとえば、 Word('foo') == Word('bar')



はtrueと見なされます)。 長さに基づいて等価性をテストする場合、これは意味をなさないため、標準の等価性テストはstr



から残しstr





さて、すべての比較を完全にカバーするために、それぞれの比較の魔法の方法を定義する必要はないことを言及する良い機会のようです。 標準ライブラリはfunctools



モジュールにデコレータークラスを提供します。これにより、すべての比較メソッドが決定されます__eq__



1つ( __gt__



__lt__



など)のみを定義すれば十分です。この機能はPythonのバージョン2.7以降で使用可能です。あなたは満足しています、あなたは多くの時間と労力を節約します。 有効にするには、クラス定義に@total_ordering



します。





数値魔法法



比較演算子によってオブジェクトの比較方法を決定できるように、数値演算子の動作を定義できます。 準備をしなさい、友人、それらの多くがあります。 より良い構成のために、数値マジックメソッドを5つのカテゴリに分類しました。単項演算子、通常の算術演算子、反射算術演算子(後で詳しく説明します)、複合代入、および型変換です。





単項演算子と関数



単項演算子および関数には、否定、絶対値などのオペランドが1つだけあります。





一般的な算術演算子



ここで、通常の2項演算子(およびさらに2、3の関数)、+、-、*、および同様のものを検討します。 彼らは、ほとんどの場合、自分自身を完全に説明しています。





反映された算術演算子



私がより詳細に反映された算術に専念するつもりだと言ったことを覚えていますか? これはある種の大きくて恐ろしくて理解できない概念だと思うかもしれません。 実際、すべてが非常に簡単です。 以下に例を示します。



 some_object + other
      
      





これは「通常の」追加です。 同等の反射式の唯一の違いは、用語の順序です:



 other + some_object
      
      





したがって、これらの魔法のメソッドはすべて、通常のバージョンと同じように動作しますが、第1オペランドとしてother



し、第2オペランドとしてself



を使用して操作を実行します。 ほとんどの場合、反映された操作の結果は通常の同等の結果と同じであるため、 __radd__



を定義するときに__radd__



呼び出しに制限することができます。 演算子の左側のオブジェクト(この例ではother



)には、このメソッドの通常の無反射バージョンを使用しないでください。 この例では、 some_object.__radd__



__add__



定義されていない場合にのみsome_object.__radd__



が呼び出されます。



複合割り当て



Pythonでは、複合代入のマジックメソッドも広く表現されています。 ほとんどの場合、すでに複合割り当てに精通している可能性が高く、これは「通常の」演算子と割り当ての組み合わせです。 それでも不明な場合は、次の例をご覧ください。



 x = 5 x += 1 #   x = x + 1
      
      





これらの各メソッドは、左側の変数に割り当てられる値を返す必要があります(たとえば、 a += b



場合、 __iadd__



a + b



に割り当てられるa + b



を返す必要があります)。 リストは次のとおりです。



型変換マジックメソッド



さらに、Pythonには、 float()



などの組み込み型変換関数の動作を定義するための多くの魔法のメソッドがあります。 ここにあります:





クラスのプレゼンテーション



多くの場合、クラスを文字列として表現すると便利です。 Pythonには、クラスを表すときにインライン関数の動作をカスタマイズするために定義できるメソッドがいくつかあります。





魔法の方法に関するマニュアルの退屈な部分(例のない部分)はほぼ完了しました。 最も基本的な魔法の方法について説明したので、今度はより高度な素材に移りましょう。





属性アクセス制御



他の言語からPythonに来た多くの人々は、クラスの実際のカプセル化の欠如について不満を述べています(例えば、パブリックアクセスメソッドでプライベート属性を定義する方法はありません)。 これは完全に真実ではありません。カプセル化に関連する多くのこと、Pythonは「マジック」を通じて実装し、メソッドとフィールドの明示的な修飾子ではありません。 参照:





属性へのアクセスを制御するメソッドを定義すると、問題が発生しやすくなります。 例を考えてみましょう:



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





繰り返しになりますが、Pythonの魔法の手法の力は信じられないほど素晴らしいものであり、大きな力には大きな責任が伴います。何も壊さずにマジックメソッドを正しく使用する方法を知ることが重要です。



それでは、属性アクセス制御について何を学びましたか?軽く使用する必要はありません。実際、それらは過度に強力で非論理的である傾向があります。それらが存在する理由は、特定の欲求を満たすためです。Pythonは悪いことを完全に禁止する傾向はなく、その使用を複雑にするだけです。自由は最優先事項ですので、あなたは実際に何でもやりたいことができます。アクセス制御メソッドの使用例を次に示します(super



すべてのクラスに属性があるわけではないためを使用していることに注意してください__dict__



)。



 class AccessCounter(object): ''',   value      .    ,   value.''' 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)]
      
      







カスタムシーケンスを作成する



Pythonには、クラスをインラインシーケンス(辞書、タプル、リスト、文字列など)のように動作させる多くの方法があります。もちろん、これらは高度な制御の不合理さと、グローバル関数のホスト全体がクラスのインスタンスと完全に機能し始める魔法のために、私のお気に入りの魔法の方法です。しかし、あらゆる種類の良いことを始める前に、プロトコルについて知る必要があります。





プロトコル



Pythonで独自のシーケンスを作成することができたので、次はプロトコルについて説明します。プロトコルは、実装する必要がある一連のメソッドを提供するという点で、他の言語のインターフェイスに少し似ています。ただし、Pythonでは、プロトコルは絶対に何の義務も負わず、必ずしもアナウンスメントの実装を必要としません。それらはおそらくガイドラインのように見えます。



プロトコルについて話しているのはなぜですか? Pythonでの任意のコンテナタイプの実装には、それらのいくつかの使用が伴うためです。最初に、不変コンテナを定義するためのプロトコル:不変コンテナを作成するには、定義__len__



__getitem__



(さらにそれらについての詳細)。可変コンテナプロトコルには、不変コンテナと同じものに加えて__setitem__



、とが必要__delitem__



です。最後に、オブジェクトを反復可能にする場合は、__iter__



どの反復子が返されるか決定する必要があります。この反復子は、メソッド__iter__



(それ自体を返す)およびを必要とする反復子プロトコルに準拠する必要がありnext



ます。





コンテナマジック



さらに遅延することなく、コンテナで使用される魔法のメソッドは次のとおりです。







例として、他の言語(Haskellなど)で遭遇する可能性のあるいくつかの機能的構成要素を実装するリストを見てみましょう。



 class FunctionalList: '''-       : 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): #      , list   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]
      
      





() . , , , ( , ?), Counter



, OrderedDict



, NamedTuple



.







, isinstance()



issubclass()



, . ここにあります:





これらの魔法の方法を有益に使用するためのオプションはほとんどないように思えるかもしれませんが、実際はそうでしょう。私はリフレクションの魔法の手法にあまり時間をかけたくありません。それらはあまり重要ではありませんが、Pythonのオブジェクト指向プログラミングとPython全般について重要なことを反映しています。この「何でも」では非常にまれにしか起こりません。これらの魔法のメソッドは役に立たないように見えるかもしれませんが、それらが必要になった場合、それらが存在することを覚えて喜んでいるでしょう(そして、このマニュアルを読んでいます!)。





呼び出されたオブジェクト



おそらく既にご存知のように、Pythonでは、関数はファーストクラスのオブジェクトです。これは、他のオブジェクトと同様に、それらを関数またはメソッドに渡すことができることを意味します。これは非常に強力な機能です。



特殊なマジックメソッドを使用すると、クラスのインスタンスを関数のように動作させることができます。つまり、それらを「呼び出し」、関数を引数として受け取る関数に渡すことができます。これは、Pythonプログラミングをとても楽しくするもう1つの便利な機能です。





__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 # ...
      
      







コンテキストマネージャー



Python 2.5では、新しいキーワードと、コードを再利用する新しい方法であるkeywordが導入されましたwith



コンテキストマネージャの概念はPythonにとって新しいものではありませんでした(以前ライブラリの一部として実装されていました)が、PEP 343では言語構造のステータスを達成しました。あなたはすでに式を見ることができましたwith







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





コンテキストマネージャを使用すると、オブジェクトの作成がステートメントにラップされている場合に、構成またはクリーンアップするアクションを実行できますwith



コンテキストマネージャの動作は、2つの魔法の方法によって決定されます。





__enter__



そして、__exit__



共通し、それらの設定やリソースの精製のための行動を説明して、特定のクラスにも有用であり得ます。これらのメソッドを使用して、さまざまなオブジェクトに共通のコンテキストマネージャーを作成できます。以下に例を示します。



 class Closer: '''        close  with-.''' def __init__(self, obj): self.obj = obj def __enter__(self): return self.obj #     with- def __exit__(self, exception_type, exception_val, trace): try: self.obj.close() except AttributeError: #     close print 'Not closable.' return True #  
      
      





Closer



FTP接続(closeメソッドを持つソケット)での使用例



 >>> from magicmethods import Closer >>> from ftplib import FTP >>> with Closer(FTP('ftp.somesite.com')) as conn: ... conn.dir() ... # output omitted for brevity >>> conn.dir() # long AttributeError message, can't use a connection that's closed >>> with Closer(int(5)) as i: ... i += 1 ... Not closable. >>> i 6
      
      





ラッパーがどのように正しいオブジェクトと間違ったオブジェクトの両方を適切に処理するかをご覧ください。これは、コンテキストマネージャと魔法のメソッドの力です。標準のPythonライブラリにはcontextlibモジュールが含まれていることに注意してください。このモジュールにはcontextlib.closing()



、ほぼ同じことを行うコンテキストマネージャが含まれています(オブジェクトにメソッドがない場合の処理​​はありませんclose()



)。





抽象基本クラス



http://docs.python.org/2/library/abc.htmlを参照してください





記述子の構築



記述子は、他のオブジェクトの属性にイベントにアクセスする(取得、変更、削除する)ロジックを追加できるクラスです。記述子は、単独で使用するためのものではありません。むしろ、それらはそれらに関連付けられたいくつかのクラスによって所有されると想定されています。記述子は、属性が互いに依存するオブジェクト指向のデータベースまたはクラスを構築するのに役立ちます。特に、記述子は、いくつかの計算システムの属性または計算された属性(開始点からグリッド上の属性によって表される点までの距離)を表すときに役立ちます。



クラスが記述子になるには__get__



__set__



またはから少なくとも1つのメソッドを実装する必要があります__delete__



。これらの魔法のメソッドを見てみましょう:





記述子の便利な使用例:単位変換。



 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): ''',  ,       .''' meter = Meter() foot = Foot()
      
      







コピー



Pythonでは、代入演算子はオブジェクトをコピーせず、別のリンクを追加するだけです。ただし、可変要素を含むコレクションの場合、あるシーケンスの要素を別のシーケンスに影響を与えずに変更できるように、完全なコピーが必要になる場合があります。ここに登場しcopy



ます。幸いなことに、Pythonのモジュールには心がありません。そのため、突然制御不能に自分自身をコピーし始め、すぐにLinuxロボットが惑星全体を埋め尽くすことを心配する必要はありません。





これらの魔法の方法をいつ使用するか?いつものように-いずれの場合でも、標準的な動作以上のものが必要な場合。たとえば、キャッシュをディクショナリ(おそらく非常に大きなディクショナリ)として含むオブジェクトをコピーしようとする場合、キャッシュ全体をコピーする必要はなく、オブジェクトの共有メモリに1つだけコピーする必要があります。





オブジェクトでpickleモジュールを使用する



PickleはPythonデータ構造をシリアル化するためのモジュールであり、オブジェクトの状態を保存して後で復元する必要がある場合(通常はキャッシュ目的)に非常に役立ちます。さらに、経験と混乱の優れた源でもあります。



シリアル化は非常に重要なので、モジュール(pickle



)に加えて、独自のプロトコルと独自のマジックメソッドを持っていますしかし、最初に、pickleを使用して既存のデータ型をシリアル化する方法(既に知っている場合はスキップしてください)。





シリアライズについて簡単に



シリアル化に飛び込みましょう。後で保存して復元したい辞書があるとします。その内容をファイルに書き込み、正しい構文で記述していることを確認してから、ファイルを実行exec()



または読み取ることにより、復元する必要があります。しかし、これはせいぜい危険です:重要なデータをテキストで保存すると、プログラムをクラッシュさせたり、一般的にはコンピューターで危険なコードを実行したりするために、さまざまな方法でデータを破損または修正できます。pickleを使用する方が良い:



 import pickle data = {'foo': [1, 2, 3], 'bar': ('Hello', 'world!'), 'baz': True} jar = open('data.pkl', 'wb') pickle.dump(data, jar) #     jar jar.close()
      
      





そして今、数時間後、再び辞書が必要になります。



 import pickle pkl_file = open('data.pkl', 'rb') #  data = pickle.load(pkl_file) #    print data pkl_file.close()
      
      





どうしたまさに期待されたもの。data



いつもそこにあるかのように。



さて、注意について少し:ピクルスは完璧ではありません。そのファイルは、偶然または意図的に簡単に破損します。Pickleはテキストファイルよりも安全かもしれませんが、悪意のあるコードを実行するために使用できます。さらに、Pythonの異なるバージョン間では互換性がないため、pickleを使用してオブジェクトを配布する場合、すべての人がそれらを使用できるとは考えないでください。ただし、モジュールは、キャッシュやその他の一般的なシリアル化タスクのための強力なツールになる可能性があります。





独自のオブジェクトのシリアル化。



pickleモジュールはビルトイン型専用ではありません。プロトコルを実装するすべてのクラスで使用できます。このプロトコルには、pickleがそれらを処理する方法を設定できるオプションのメソッドが4つ含まれています(C拡張機能にはいくつかの違いがありますが、これはガイドの範囲外です)。







たとえば、スレートボード(Slate



について説明します。このボードには、何がいつ書かれたかが記憶されています。ただし、具体的には、このボードはシリアル化されるたびにクリーンになります。現在の値は保存されません。



 import time class Slate: ''',     .      .''' def __init__(self, value): self.value = value self.last_change = time.asctime() self.history = {} def change(self, new_value): #  .     . self.history[self.last_change] = self.value self.value = new_value self.last_change = time.asctime() def print_changes(self): print 'Changelog for Slate object:' for k, v in self.history.items(): print '%s\t %s' % (k, v) def __getstate__(self): #    self.value or self.last_change. #   " "  . return self.history def __setstate__(self, state): self.history = state self.value, self.last_change = None, None
      
      







おわりに



このガイドの目的は、Pythonやオブジェクト指向プログラミングの経験に関係なく、読んだすべての人に何かを伝えることです。Pythonを初めて使用する場合は、機能的でエレガントで使いやすいクラスを作成するための基本についての貴重な知識を身につけています。あなたが中級レベルのプログラマーであるなら、あなたとあなたのクライアントによって書かれたコードの量を減らすいくつかの良い新しいアイデアと戦略、いくつかの良い方法を見つけたかもしれません。あなたが専門のピトニストであるならば、あなたはあなたのおそらく忘れられた知識のいくつかを更新したか、あるいはいくつかの新しいトリックを見つけたかもしれません。あなたのレベルに関係なく、私は特別なPythonメソッドのこの旅が本当に魔法であったことを望みます(抵抗できませんでした)。





付録1:マジックメソッドの呼び出し方法



マジックメソッドの一部は、組み込み関数に直接関連しています。この場合、それらを呼び出す方法は非常に明白です。ただし、これは常にそうではありません。このアドオンは、マジックメソッドの呼び出しにつながる明白でない構文を明らかにすることを目的としています。

マジックメソッド 呼び出されたとき(例) 説明
__new__(cls [,...])



instance = MyClass(arg1, arg2)



__new__



インスタンス化時に呼び出される
__init__(self [,...])



instance = MyClass(arg1, arg2)



__init__



インスタンス化時に呼び出される
__cmp__(self, other)



self == other



self > other



、等
比較のために呼び出されます。
__pos__(self)



+self



単項プラス記号
__neg__(self)



-self



単項マイナス記号
__invert__(self)



~self



ビット単位の反転
__index__(self)



x[self]



オブジェクトがインデックスとして使用されるときに変換する
__nonzero__(self)



bool(self)



if self:



オブジェクトのブール値
__getattr__(self, name)



self.name # name



存在しない属性を取得しようとしています
__setattr__(self, name, val)



self.name = val



任意の属性への割り当て
__delattr__(self, name)



del self.name



属性を削除
__getattribute__(self, name)



self.name



属性を取得する
__getitem__(self, key)



self[key]



インデックスを介してアイテムを取得する
__setitem__(self, key, val)



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)



連載
__setstate__(self)



data = pickle.load(pkl_file)



連載




この表が、魔法のメソッドを呼び出すための構文とは何かについての疑問を解決することを願っています。





補足2:Python 3の変更



Python 3がオブジェクトモデルの点で2.xと異なるいくつかの主なケースについて説明します。










All Articles