web-dev-qa-db-ja.com

なぜPythonメソッドに「self」引数を明示的に必要とするのですか?

Pythonのクラスでメソッドを定義すると、次のようになります。

class MyClass(object):
    def __init__(self, x, y):
        self.x = x
        self.y = y

ただし、C#などの他の言語では、メソッドプロトタイプの引数として宣言せずに、「this」キーワードでメソッドがバインドされているオブジェクトへの参照があります。

これはPythonでの意図的な言語設計決定でしたか、または引数として「自己」を渡す必要がある実装の詳細がありますか?

184
Readonly

PetersのZen of Pythonを引用したいです。 「明示的は暗黙的よりも優れています。」

JavaおよびC++では、推定できない変数名がある場合を除き、「this.」を推定できます。そのため、必要な場合と必要ない場合があります。

Pythonは、ルールに基づくのではなく、このようなことを明示的にすることを選択します。

さらに、何も暗示または想定されていないため、実装の一部が公開されます。 self.__class__self.__dict__およびその他の「内部」構造は、明白な方法で利用可能です。

88
S.Lott

メソッドと関数の違いを最小限に抑えるためです。メタクラスでメソッドを簡単に生成したり、実行時に既存のクラスにメソッドを追加したりできます。

例えば.

>>> class C(object):
...     def foo(self):
...         print "Hi!"
...
>>>
>>> def bar(self):
...     print "Bork bork bork!"
...
>>>
>>> c = C()
>>> C.bar = bar
>>> c.bar()
Bork bork bork!
>>> c.foo()
Hi!
>>>

また、(私の知る限り)pythonランタイムの実装を容易にします。

58
Ryan

このトピックについては、 Guido van Rossumのブログ を読むことをお勧めします- なぜ明示的な自己がとどまる必要があるか

メソッド定義が装飾されると、自動的に「self」パラメータを与えるかどうかはわかりません。デコレータは、関数を静的メソッド(「self」を持たない)またはクラスメソッド(これはインスタンスの代わりにクラスを参照する面白い種類の自己を持っている)、またはまったく異なる何かをすることができます(純粋なPythonで「@classmethod」または「@staticmethod」を実装するデコレータを書くのは簡単です)。暗黙の「自己」引数で定義されているメソッドに与えるかどうかは、デコレーターが何をするのかを知らずにはできません。

特別なケース「@classmethod」や「@staticmethod」などのハッキングを拒否します。

52
bhadra

Pythonは「自己」の使用を強制しません。好きな名前を付けることができます。メソッド定義ヘッダーの最初の引数はオブジェクトへの参照であることを覚えておく必要があります。

16

また、これを行うことができます:(要するに、Outer(3).create_inner_class(4)().weird_sum_with_closure_scope(5)を呼び出すと12が返されますが、最もクレイジーな方法で呼び出されます。

class Outer(object):
    def __init__(self, outer_num):
        self.outer_num = outer_num

    def create_inner_class(outer_self, inner_arg):
        class Inner(object):
            inner_arg = inner_arg
            def weird_sum_with_closure_scope(inner_self, num)
                return num + outer_self.outer_num + inner_arg
        return Inner

もちろん、これはJavaやC#のような言語では想像しにくいです。自己参照を明示的にすることにより、その自己参照によって任意のオブジェクトを自由に参照できます。また、実行時にクラスを操作するこのような方法は、より静的な言語では実行するのが難しくなります。必ずしも良いことでも悪いことでもありません。それは、明示的な自己がこの狂気のすべての存在を許すということです。

さらに、これを想像してください:メソッドの動作をカスタマイズしたい(プロファイリング、またはいくつかのクレイジーブラックマジック)。これにより、考えることができます:動作をオーバーライドまたは制御できるクラスMethodがある場合はどうでしょうか。

さて、ここにあります:

from functools import partial

class MagicMethod(object):
    """Does black magic when called"""
    def __get__(self, obj, obj_type):
        # This binds the <other> class instance to the <innocent_self> parameter
        # of the method MagicMethod.invoke
        return partial(self.invoke, obj)


    def invoke(magic_self, innocent_self, *args, **kwargs):
        # do black magic here
        ...
        print magic_self, innocent_self, args, kwargs

class InnocentClass(object):
    magic_method = MagicMethod()

そして今:InnocentClass().magic_method()は期待どおりに動作します。メソッドは、innocent_selfパラメーターでInnocentClassにバインドされ、magic_selfでMagicMethodインスタンスにバインドされます。変だよね? JavaやC#のような言語で2つのキーワードthis1this2を持っているようなものです。このようなマジックにより、フレームワークは、それ以外の場合ははるかに冗長な処理を実行できます。

繰り返しますが、私はこのようなものの倫理についてコメントしたくありません。私は、明示的な自己参照なしで行うのが難しいことを示したかっただけです。

6
vlad-ardelean

「Pythonの禅」以外の本当の理由は、関数がPythonの第一級市民だからだと思います。

これは本質的にそれらをオブジェクトにします。今、基本的な問題は、関数がオブジェクトでもある場合、オブジェクト指向のパラダイムでは、メッセージ自体がオブジェクトである場合、どのようにオブジェクトにメッセージを送信しますか?

鶏の卵の問題のように見えますが、このパラドックスを減らすための唯一の方法は、実行のコンテキストをメソッドに渡すか、それを検出することです。しかし、pythonはネストされた関数を持つことができるため、内部関数の実行コンテキストが変わるため、ネストすることはできません。

つまり、唯一可能な解決策は、明示的に「自己」(実行のコンテキスト)を渡すことです。

だから、Zenがずっと後になってきた実装上の問題だと思います。

2
pankajdoharey

私はそれがPEP 227に関係していると思う:

クラススコープ内の名前にはアクセスできません。名前は最も内側の関数スコープで解決されます。ネストされたスコープのチェーンでクラス定義が発生する場合、解決プロセスはクラス定義をスキップします。このルールは、クラス属性とローカル変数アクセスの間の奇妙な相互作用を防ぎます。クラス定義で名前バインディング操作が発生すると、結果のクラスオブジェクトに属性が作成されます。メソッドまたはメソッド内にネストされた関数でこの変数にアクセスするには、selfまたはクラス名を介して属性参照を使用する必要があります。

2
daole

Pythonの selfで説明されているように、Demystified

obj.meth(args)のようなものはすべてClass.meth(obj、args)になります。呼び出しプロセスは自動ですが、受信プロセスは自動ではありません(明示的)。これが、クラス内の関数の最初のパラメーターがオブジェクトそのものでなければならない理由です。

class Point(object):
    def __init__(self,x = 0,y = 0):
        self.x = x
        self.y = y

    def distance(self):
        """Find distance from Origin"""
        return (self.x**2 + self.y**2) ** 0.5

呼び出し:

>>> p1 = Point(6,8)
>>> p1.distance()
10.0

init ()は3つのパラメーターを定義しますが、2つ(6と8)を渡しました。同様に、distance()には1つの引数が必要ですが、渡された引数はゼロです。

Pythonがこの引数番号の不一致について文句を言っていないのはなぜですか?

一般に、いくつかの引数を指定してメソッドを呼び出すと、メソッドのオブジェクトを最初の引数の前に配置して、対応するクラス関数が呼び出されます。したがって、obj.meth(args)のようなものはすべてClass.meth(obj、args)になります。 呼び出しプロセスは自動ですが、受信プロセスは(明示的ではありません).

これが、クラス内の関数の最初のパラメーターがオブジェクトそのものでなければならない理由です。このパラメーターをselfとして記述することは、単なる規則です。これはキーワードではなく、Pythonでは特別な意味を持ちません。他の名前(このような)を使用することもできますが、使用しないことを強くお勧めします。 self以外の名前を使用すると、ほとんどの開発者が顔をしかめ、コードの可読性を低下させます(「可読性のカウント」)。
...
最初の例のself.xはインスタンス属性で、xはローカル変数です。それらは同じではなく、異なる名前空間にあります。

自己はここにいる

多くの人が、C++やJavaのように、Pythonでキーワードをにすることを提案しています。これにより、メソッドの正式なパラメーターリストから明示的なselfの冗長な使用が排除されます。少なくとも近い将来ではありません。 主な理由は、下位互換性です。ここに、Pythonの作成者自身によるブログがあります。このブログでは、明示的な自己を維持しなければならない理由を説明しています。

0
mon