web-dev-qa-db-ja.com

Pythonでのself vs super() "inconsistency"

Python documentation によると、super()は、クラス定義内で引数なしで使用できます。これは、コンパイラが暗黙的にコンテキスト引数を使用するためです。

_class C(B):
    def method(self, arg):
        super().method(arg)    # This does the same thing as:
                               # super(C, self).method(arg)
_

次に、super()のようなものではなく、selfを引数として渡すことの意味は何でしたか?self()?コンパイラーがトリックをプレイすることが許可されている場合、括弧なしのselfも許可できます。

これは悪いデザインですか、それとも正当な理由がありますか?引数なしのsuper()は問題ないのに、selfキーワードまたはself()組み込み関数は問題があるのはなぜですか? (仮説的なself()組み込み関数で括弧を入力するのを避けるためだけにではなく、self引数が渡されると思います。)

5
Alexey

関数とメソッドが同じシグネチャを持ち、互換的に使用できるようにします。

def multiply(a, b):
    return a * b

class Number(int):

    def add(self, value):
        return self + value

>>> number = Number(5)
>>> number.add(2)
7

>>> add = Number.add
>>> add(2, 5)
7

>>> Number.multiply = multiply
>>> number.multiply(2)
10
7
Jace Browning

言語設計は、さまざまな種類の複雑さのバランスをとることです。 Python一般に、より複雑なセマンティクスを犠牲にして、単純な構文を選択します。これらの選択肢の1つは、呼び出し可能オブジェクト(関数、インスタンスメソッド、クラスメソッド、静的メソッド)を定義する方法が1つだけあることです。 :defを使用します(技術的にはlambdaも使用しますが、ここでは無視します)。

staticmethodのような追加のキーワードを導入する代わりに、Pythonはdescriptor protocolを使用してこれらのケースを明確にします。記述子は___get___メソッド(場合によっては___set___および___del___も含む)を持つオブジェクトであり、クラスメンバーへのアクセス方法を制御するために使用できます。すべてのdefは関数オブジェクトを作成します、これも記述子プロトコルを満たします。

関数がオブジェクトに直接割り当てられている場合、そのフィールドにアクセスしても記述子プロトコルは呼び出されません。割り当てられている関数のように動作します。

_def f(a, b):  # no "self"
  return a + b

class C(object): pass
o = C()

c.f = f  # assign as instance field

o.f(2, 3)  #=> 5
_

関数をクラスに割り当てると、その関数にアクセスすると記述子プロトコルがトリガーされます。デフォルトの関数の場合、クラスからアクセスすると、関数自体が返されます。そのクラスのインスタンスを介してアクセスすると、呼び出されたインスタンスを関数の最初の引数にバインドしたメソッドオブジェクトが返されます。

_def f(self, a, b):
  return a + b

class C(object): pass
o = C()

C.f = f  # assign as class member

C.f is f  #=> True, the class member is returned unchanged
o.f is f  #=> False, this is a bound method

# in the absence of subclasses, these calls are the same:
o.f(2, 3)  #=> 5
C.f(o, 2, 3)  #=> 5
f(o, 2, 3)  #=> 5
instance_method = o.f
instance_method(2, 3)  #=> 5
_

classmethodデコレータは、関数を別の記述子でラップします。クラスまたはそのクラスのインスタンスを介してアクセスすると、最初の引数はクラスにバインドされます。

_def f(cls, a, b):
  return a + b

class C(object): pass
o = C()

C.f = classmethod(f)  # assign as class member

C.f is f  #=> False, we get a bound method
o.f is f  #=> False, also bound to the class

# in the absence of subclasses, these calls are the same:
C.f(2, 3)  #=> 5
f(C, 2, 3)  #=> 5
o.f(2, 3)  #=> 5
_

staticmethodデコレータは、いずれの場合も引数をバインドせずに関数を返すだけです。

_def f(a, b):
  return a + b

class C(object): pass
o = C()

C.f = staticmethod(f)  # assign as class member

C.f is f  #=> True
o.f is f  #=> True

# in the absence of subclasses, these calls are the same:
C.f(2, 3)
o.f(2, 3)
f(2, 3)
_

記述子は、プロパティの実装にも使用されます。ここでは、インスタンスを介してプロパティにアクセスすると、ラップされた関数をメソッドとして呼び出した結果が返されます。

_def f(self):
  return 42

p = property(f)

class C(object): pass
o = C()

C.p = p

C.p is p  #=> True
o.p is p  #=> False, this is the result of the function

# these calls are equivalent:
f(o)  #=> 42
o.p  #=> 42
_

したがって、この機能「explicit self argument」–「descriptors」–“ decorators”の組み合わせにより、言語に多くの柔軟性が与えられます。ほとんどの複雑な部分は記述子プロトコルに隠されており、通常のユーザーは心配する必要はありません。暗黙のselfキーワードがあった場合、クラスメソッドの実装はユーザーにとってより困難で混乱を招くでしょう。結局のところ、selfはクラスのインスタンスを参照すると理解されており、クラス自体を参照する場合はclsが優先されます。

もちろん、これが唯一の方法ではありません。 JavaScriptは、暗黙的なthisを備えた、ほぼ同様の言語です。関数にオブジェクトメンバーとしてアクセスすると、関数のthisパラメーターがそのオブジェクトにバインドされます。つまり関数メンバーfを持つオブジェクトoが与えられると、_o.f_はthisoにバインドされた関数を返します。これはPythonと同じです。インスタンスメソッドがオブジェクトにバインドされているため、o.f()m = o.f; m()は互換的に使用できます。

ただし、JavaScriptにはクラスに相当する概念がなく、オブジェクトからバインドされていないメソッドを取得する方法がありません。また、演算子のオーバーロードがないため、異なるバインディングを表す異なる関数型を持つことはできません。代わりに、すべての関数にf.call()およびf.apply()メソッドがあります。これらは、thisの値を明示的に指定する必要があるという点で、関数f()を直接呼び出すのとは異なります。

JavaScriptのアプローチは一般に直感的ですが、誤ってthisコンテキストを変更せずに関数をどこかに保存することは困難です。また、thisはほとんど暗黙的なパラメーターであるため、thisが参照するオブジェクトを追跡することは困難です。特に、ネストされた関数を扱う場合は困難です。 Pythonは、これらの問題に悩まされることはなく、少なくとも大幅に軽減されます。JSの場合よりも、関数とバインドされたメソッドとの間に強い概念上の違いがあります。明示的なselfパラメータを使用すると、メソッドのデータフローがより明確になります。

Pythonのsuper()がスタックトレースイントロスペクションを使用して、囲んでいる関数の最初の引数を取得することは、優れたデザインであるとは限りません。魔法は悪いです、魔法は予期しない方法で失敗する可能性があり、魔法はデバッグが困難です。ただし、ほとんどの場合、すべてのパラメーターを明示的に指定する必要があるよりもはるかに簡単です。これにより、間違いの可能性が減り、プログラマーがシンプルで正しいコードを書くことができます。だから私はこの魔法は素晴らしいとは思いませんが、それでも全体的には良いです。

4
amon