Pythonのselfパラメータとは何ですか?

王林
リリース: 2023-05-08 17:49:08
転載
1144 人が閲覧しました

Python 的"self"参数是什么?

# すでに知っていることから始めましょう: self - メソッドの最初のパラメーター - はクラス インスタンスを参照します:

class MyClass:
┌─────────────────┐
▼ │
def do_stuff(self, some_arg): │
print(some_arg)▲│
 ││
 ││
 ││
 ││
instance = MyClass() ││
instance.do_stuff("whatever") │
│ │
└───────────────────────────────┘
ログイン後にコピー

また、この引数は実際には self と呼ばれる必要はありません。これは単なる慣例です。たとえば、他の言語で一般的に使用されるように使用できます。

上記のコードは、実際に使ったことがあるので自然で明白かもしれませんが、 .do_stuff() に 1 つの引数 (some_arg) を与えただけですが、メソッドは 2 つの引数 (self と some_arg) を宣言しています。意味がわからないようです。スニペット内の矢印は、self がインスタンスに変換されることを示していますが、実際にはどのように渡されるのでしょうか?

instance = MyClass()
MyClass.do_stuff(instance, "whatever")
ログイン後にコピー

Python が内部的に行うことは、instance.do_stuff("whatever") を MyClass.do_stuff(instance, "whatever") に変換することです。ここではそれを「Python マジック」と呼ぶこともできますが、舞台裏で何が起こっているのかを本当に理解したい場合は、Python メソッドとは何か、そしてそれらが関数にどのように関連しているかを理解する必要があります。

クラスの属性/メソッド

Python には、「メソッド」オブジェクトのようなものはありません。実際、メソッドは単なる通常の関数です。関数とメソッドの違いは、メソッドがクラスの名前空間で定義され、そのクラスのプロパティになることです。

これらの属性はクラス ディクショナリ __dict__ に保存されており、直接アクセスすることも、組み込み関数 vars を使用してアクセスすることもできます:

MyClass.__dict__["do_stuff"]
# <function MyClass.do_stuff at 0x7f132b73d550>
vars(MyClass)["do_stuff"]
# <function MyClass.do_stuff at 0x7f132b73d550>
ログイン後にコピー

これらの属性にアクセスする最も一般的な方法は「クラスメソッド」です 方法:

print(MyClass.do_stuff)
# <function MyClass.do_stuff at 0x7f132b73d550>
ログイン後にコピー

ここでは、class 属性を使用して関数にアクセスします。予想通り、print do_stuff は MyClass の関数です。ただし、インスタンス プロパティを使用してアクセスすることもできます。

print(instance.do_stuff)
# <bound method MyClass.do_stuff of <__main__.MyClass object at 0x7ff80c78de50>
ログイン後にコピー

ただし、この場合、元の関数の代わりに「バインドされたメソッド」が取得されます。ここで Python が行うことは、クラス属性をインスタンスにバインドし、いわゆる「バインディング メソッド」を作成することです。この「バインドされたメソッド」は、基礎となる関数のラッパーであり、インスタンスが最初の引数 (self) としてすでに挿入されています。

したがって、メソッドは、他のパラメーターにクラス インスタンス (self) が追加された通常の関数です。

これがどのように起こるかを理解するには、記述子プロトコルを調べる必要があります。

ディスクリプタ プロトコル

ディスクリプタはメソッドの背後にあるメカニズムであり、__get__()、__set__()、または __delete__() メソッドを定義するオブジェクト (クラス) です。 self がどのように機能するかを理解するために、次のようなシグネチャを持つ __get__() を考えてみましょう。

descr.__get__(self, instance, type=None) -> value
ログイン後にコピー

しかし、__get__() メソッドは実際に何をするのでしょうか?これにより、クラス内のプロパティ検索をカスタマイズできます。つまり、ドット表記を使用してクラス プロパティにアクセスしたときに何が起こるかをカスタマイズできます。メソッドが実際にはクラスの単なるプロパティであることを考えると、これは非常に便利です。これは、__get__ メソッドを使用してクラスの「バインドされたメソッド」を作成できることを意味します。

理解を容易にするために、記述子を使用して「メソッド」を実装することでこれを実証してみましょう。まず、関数オブジェクトの純粋な Python 実装を作成します。

import types
class Function:
def __get__(self, instance, objtype=None):
if instance is None:
return self
return types.MethodType(self, instance)
def __call__(self):
return
ログイン後にコピー

上記の Function クラスは、記述子となる __get__ を実装します。この特別なメソッドは、インスタンス パラメータでクラス インスタンスを受け取ります。このパラメータが None の場合、__get__ メソッドがクラス (例: MyClass.do_stuff) から直接呼び出されたことがわかるため、self を返すだけです。ただし、instance.do_stuff などのクラス インスタンスから呼び出された場合は、types.MethodType が返されます。これは、「バインドされたメソッド」を手動で作成する方法です。

さらに、__call__ という特別なメソッドも提供しています。 __init__ は、インスタンスを初期化するためにクラスが呼び出されるとき (たとえば、instance = MyClass()) に呼び出されます。一方、 __call__ は、インスタンスが呼び出されるとき (たとえば、instance()) に呼び出されます。 types.MethodType(self,instance) の self は呼び出し可能でなければならないため、これを使用する必要があります。

独自の関数実装ができたので、それを使用してメソッドをクラスにバインドできます。

class MyClass:
do_stuff = Function()
print(MyClass.__dict__["do_stuff"])# __get__ not invoked
# <__main__.Function object at 0x7f229b046e50>
print(MyClass.do_stuff)# __get__ invoked, but "instance" is None, "self" is returned
print(MyClass.do_stuff.__get__(None, MyClass))
# <__main__.Function object at 0x7f229b046e50>
instance = MyClass()
print(instance.do_stuff)#__get__ invoked and "instance" is not None, "MethodType" is returned
print(instance.do_stuff.__get__(instance, MyClass))
# <bound method ? of <__main__.MyClass object at 0x7fd526a33d30>
ログイン後にコピー

MyClass に Function 型の属性 do_stuff を与えることで、クラスの名前空間でメソッドを定義するときに Python が行うことを大まかにエミュレートします。

要約すると、instance.do_stuff などの属性にアクセスすると、do_stuff はインスタンスの属性辞書 (__dict__) 内で検索されます。 do_stuff が __get__ メソッドを定義している場合、 do_stuff.__get__ が呼び出され、最終的に次の呼び出しが行われます:

# For class invocation:
print(MyClass.__dict__['do_stuff'].__get__(None, MyClass))
# <__main__.Function object at 0x7f229b046e50>
# For instance invocation:
print(MyClass.__dict__['do_stuff'].__get__(instance, MyClass))
# Alternatively:
print(type(instance).__dict__['do_stuff'].__get__(instance, type(instance)))
# <bound method ? of <__main__.MyClass object at 0x7fd526a33d30>
ログイン後にコピー

今わかっているように、バインドされたメソッドが返されます。呼び出し可能オブジェクトは元の関数 Wrapper をラップします。パラメータの前には self が付きます!

これをさらに詳しく調べたい場合は、静的メソッドとクラス メソッドを同様に実装できます (https://docs.python.org/3.7/howto/descriptor.html#static-methods-and-class-methods) )

メソッド定義に self が含まれているのはなぜですか?

これがどのように機能するかはわかりましたが、さらに哲学的な質問があります。「なぜメソッド定義にそれを含める必要があるのですか?」

明示的な self メソッド パラメーターは、物議を醸す設​​計上の選択です。しかし、それはシンプルさを好むものです。

Python は、ここで説明されている「悪いほど良い」設計哲学を自ら体現しています。この設計コンセプトの優先事項は「シンプルさ」であり、次のように定義されます。

設計は、実装とインターフェイスを含めてシンプルでなければなりません。インターフェイスよりも実装が単純であることが重要です...

これはまさに self の場合に当てはまります。インターフェイスを犠牲にした単純な実装であり、メソッドのシグネチャがその呼び出しと一致しません。

もちろん、self を明示的に記述する必要がある理由、または保持する必要がある理由は他にもあります。その一部は、Guido van Rossum によるブログ投稿 (http://neopythonic.blogspot.com/) で説明されています。 2008/10/why-explicit-self-has-to-stay.html)、この記事は削除要求に応えました。

Python は多くの複雑さを抽象化しますが、私の意見では、低レベルの詳細と複雑さを掘り下げることは、言語がどのように機能するか、問題が発生した場合、および十分ではない場合の高度なトラブルシューティング/デバッグをよりよく理解するために非常に価値があります。 、便利です。

また、記述子にはいくつかの使用例があるため、記述子を理解することは実際には非常に実用的です。ほとんどの場合、実際に必要なのは @property 記述子のみですが、SLQAlchemy やカスタム バリデーターなど、カスタム記述子が意味をなす場合もあります。

以上がPythonのselfパラメータとは何ですか?の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

関連ラベル:
ソース:51cto.com
このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
最新の問題
人気のチュートリアル
詳細>
最新のダウンロード
詳細>
ウェブエフェクト
公式サイト
サイト素材
フロントエンドテンプレート