Python類と元類の深さ発掘I【経験】


前のページでは、Pythonエニュメレーションの種類の標準ライブラリを紹介しました。実際の使用性を考慮して、もう一つの重要な原因は、その実現過程が非常に良い学習であり、Python類と元類の例を理解しています。これを例にして、Pythonのクラスとクラスの背後にあるメカニズムを掘り下げます。
どのPython教程を開いても、次の二つの言葉が必ずどこかにあります。
Pythonの中では全てが対象です。
Pythonはオブジェクト指向プログラミング言語です。
上の二つの言葉の文脈では、オブジェクトの意味は少し違っているかもしれませんが、Pythonの対象は非常に重要な意味を持っています。これから検討するすべての内容の基礎です。相手は一体何ですか?
オブジェクト(Object)
オブジェクトはPythonでのデータに対する抽象的なもので、Pythonプログラムのすべてのデータはオブジェクトまたはオブジェクトの関係によって表されます。ref:Data Model]
香港と台湾はObjectを「物件」と訳して、データが入った箱と見なしてもいいです。純粋なデータ以外に他に有用な属性情報があります。Pythonでは、すべての対象はid、type、valueの3つの属性を持っています。

+---------------+
|  |
| Python Object |
|  |
+------+--------+
| ID | |
+---------------+
| Type | |
+---------------+
| Value| |
+---------------+
IDはメモリアドレスを表していますが、内蔵関数ID()で見ることができます。typeは対象のカテゴリを表しています。種類によっては、そのオブジェクトが持つ属性や方法などを意味しています。type()の方法で見ることができます。

 def who(obj):

  print(id(obj), type(obj))

  who(1)

  who(None)

  who(who)

  4515088368 

  4514812344 

  4542646064 

オブジェクトはPythonの基本単位として作成、名前、または削除できます。Pythonでは一般的に手で対象を削除する必要がなく、そのごみ回収メカニズムは使用しない対象を自動的に処理します。もちろん必要であれば、del文を使って変数を削除することもできます。ネーミングとは、オブジェクトに名前ラベルを付けて使いやすくすること、すなわち宣言や変数の割り当てです。次に、どのようにオブジェクトを作成するかに焦点を当てます。いくつかのPython内蔵タイプのオブジェクトについては、通常、特定の文法を使用して生成されます。たとえば、数字は直接アラビア数字の字面量を使用して、文字列は引用符を使用します。リストは[]を使います。辞書は{}を使います。関数はdef文法を使います。これらのオブジェクトのタイプはすべてPythonが内蔵しています。他のタイプのオブジェクトを作成できますか?
クラスとインスタンス
Pythonがオブジェクト指向プログラミング言語である以上、ユーザー自身がオブジェクトを作成することができ、通常はクラス文を使用し、他のオブジェクトとは異なり、クラス定義のオブジェクト(クラスと呼ぶ)は新しいオブジェクト(例と呼ぶ)を生成するために使用されることができる。
  

class A:

  pass

  a = A()

  who(A)

  who(a)

  140477703944616 

  4542635424 

上記の例ではAは私達が作成した新しいクラスです。A()を呼び出してAタイプのインスタンスオブジェクトを得ることができます。これをaとします。つまり、すべての内蔵オブジェクトタイプとは異なるオブジェクトaを作成しました。メーン.A!ここでPythonの中のすべての対象を二つに分けることができます。
新しいオブジェクトを生成するために使用できるクラスは、内蔵のint、strおよび自分で定義したAなどがあります。
クラスによって生成されたインスタンスオブジェクトは、内蔵タイプの数字、文字列、および自分で定義されたタイプがメーン.Aのaです。
単純に概念からこの2つのオブジェクトを理解するのは問題がないが、ここで議論するのは実践の中で考慮しなければならないいくつかの詳細な問題である。
オブジェクト指向プログラミングにおける継承、再負荷などの特性を実現するために、いくつかの便利な機構が必要である。
いくつかの固定的な流れが必要です。具体的なオブジェクトを生成する過程で特定の動作を実行できます。
この二つの問題は主にクラスの特殊な操作、つまりこの後の主要な内容についてです。冒頭の二つの文を振り返ってみると、クラス自体も対象である以上、それらはどのように生成されたのかということを思い出すかもしれません。これは後編で主に議論される問題です。クラスのオブジェクトを生成するためのクラス、すなわち元のクラスです。
super,mro()
0 x 00 Pythonの禅の中で最後の条に言及して、名前空間は絶妙な理念で、種類あるいは対象はPythonの中で一部の名前空間の作用を引き受けました。例えば、特定の方法や属性は特定のタイプのオブジェクトにしかありません。異なるタイプのオブジェクトの属性と方法は名前が同じでも、名前が異なる名前空間に属するため、その値は全く異なるかもしれません。クラスの継承と重負荷などの特性を実現するには、名前空間の問題を考慮する必要があります。エニュメレート・タイプの実現を例に挙げると、エニュメレート・オブジェクトの属性名が重複していないことを保証する必要があります。
 

 class _EnumDict(dict):

  def __init__(self):

  dict.__init__(self)

  self._member_names = []

  def keys(self):

  keys = dict.keys(self)

  return list(filter(lambda k: k.isupper(), keys))

  ed = _EnumDict()

  ed['RED'] = 1

  ed['red'] = 2

  print(ed, ed.keys())

  {'RED': 1, 'red': 2} ['RED']

上の例で_EnumDictを重載して父の種類のdictのいくつか方法を同時に呼び出して、上の書き方は文法の上で間違いがないので、しかしもし私達は変えるならばEnumDictの親は、dictから継承するのではなく、すべての方法でdict.method(self)の呼び出しの形式を手動で修正しなければなりません。この問題を解決するために、Pythonは内蔵関数super():
  

print(super.__doc__)

  super() -> same as super(__class__, )

  super(type) -> unbound super object

  super(type, obj) -> bound super object; requires isinstance(obj, type)

  super(type, type2) -> bound super object; requires issubclass(type2, type)

  Typical use to call a cooperative superclass method:

  class C(B):

  def meth(self, arg):

  super().meth(arg)

  This works for class methods too:

  class C(B):

  @classmethod

  def cmeth(cls, arg):

  super().cmeth(arg)

最初はsuper()を親の対象に向けたポインタとしてしか考えられませんでしたが、実際には、オブジェクトとそのサブクラス(ここでは対象が少なくともクラスのオブジェクトであり、サブクラスはインスタンスオブジェクトでもいいです。)を与えて、オブジェクトの親の名前空間から対応する方法を検索します。
以下のコードを例に挙げます。

 class A(object):

  def method(self):

  who(self)

  print("A.method")

  class B(A):

  def method(self):

  who(self)

  print("B.method")

  class C(B):

  def method(self):

  who(self)

  print("C.method")

  class D(C):

  def __init__(self):

  super().method()

  super(__class__, self).method()

  super(C, self).method() # calling C's parent's method

  super(B, self).method() # calling B's parent's method

  super(B, C()).method() # calling B's parent's method with instance of C

  d = D()

  print("
Instance of D:")   who(d)   4542787992   C.method   4542787992   C.method   4542787992   B.method   4542787992   A.method   4542788048   A.method   Instance of D:   4542787992
もちろん外部でsuper()法を使うこともできますが、デフォルトパラメータの形は使えません。外部の名前空間には存在しないからです。クラス和self:

 super(D, d).method() # calling D's parent's method with instance d

  4542787992 

  C.method

上の例は下の図で説明できます。

+----------+
| A |
+----------+
| method() <---------------+ super(B,self)
+----------+  |
    |
+----------+  +----------+
| B |  | D |
+----------+ super(C,self) +----------+
| method() <---------------+ method() |
+----------+  +----------+
    |
+----------+  |
| C |  |
+----------+  | super(D,self)
| method() <---------------+
+----------+
super()方法は父の方向に遡って変数検索の起点を見つけたと考えられますが、この遡及の順序はどうやって決められますか?上記の例では、継承関係はobject->A->B->C->Dの順であり、比較的複雑な継承関係であれば?
 

 class A(object):

  pass

  class B(A):

  def method(self):

  print("B's method")

  class C(A):

  def method(self):

  print("C's method")

  class D(B, C):

  def __init__(self):

  super().method()

  class E(C, B):

  def __init__(self):

  super().method()

  d = D()

  e = E()

  B's method

  C's method

Pythonには、検索の順序を指定するためのクラスの方法mro()が提供されています。mroはMethod Resolution Orderの略語です。これは例示的な方法ではなく、mro()法を重載して継承中の方法の解析順序を変更することができますが、ここではその結果を見ます。

 D.mro()

  [__main__.D, __main__.B, __main__.C, __main__.A, object]

  E.mro()

  [__main__.E, __main__.C, __main__.B, __main__.A, object]

  super()        mro()             :

  super(D, d).method()

  super(E, e).method()

  B's method

  C's method

  super(C, e).method()

  super(B, d).method()

  B's method

  C's method