2009-05-12 10 views
5

メソッドの呼び出しはどのようにPythonで動作しますか? 私は、python仮想マシンがそれをどのように解釈するのかを意味します。Pythonでメソッドの解決と呼び出しが内部的にどのように機能しますか?

Pythonのメソッドの解像度は、JavaのPythonでは遅くなる可能性があります。 レイトバインディングとは何ですか?

これら2つの言語の反射メカニズムの違いは何ですか? これらの側面を説明する良いリソースはどこにありますか?

+0

ソースを見つけるのに問題がありますか?あなたはhttp://svn.python.org/view/python/trunk/Python/ –

+1

を見ましたか?私の場合、ソースは少し下のようですが、いずれにしても関心があります。 –

答えて

4

名前(メソッド、関数、変数)はすべて名前空間を調べることで解決されます。名前空間はCPythonでdict(ハッシュマップ)として実装されています。

名前がインスタンスネームスペース(dict)に見つからない場合、pythonはクラスのために行き、次にメソッド解決順序(MRO)に従って基本クラスのために行きます。

すべての解決は実行時に行われます。

disモジュールで遊んで、バイトコードでどうなるか確認できます。

簡単な例:

import dis 
a = 1 

class X(object): 
    def method1(self): 
     return 15 

def test_namespace(b=None): 
    x = X() 
    x.method1() 
    print a 
    print b 

dis.dis(test_namespace) 

出力します

9   0 LOAD_GLOBAL    0 (X) 
       3 CALL_FUNCTION   0 
       6 STORE_FAST    1 (x) 

10   9 LOAD_FAST    1 (x) 
      12 LOAD_ATTR    1 (method1) 
      15 CALL_FUNCTION   0 
      18 POP_TOP    

11   19 LOAD_GLOBAL    2 (a) 
      22 PRINT_ITEM   
      23 PRINT_NEWLINE  

12   24 LOAD_FAST    0 (b) 
      27 PRINT_ITEM   
      28 PRINT_NEWLINE  
      29 LOAD_CONST    0 (None) 
      32 RETURN_VALUE   

をすべてLOAD Sは、名前空間の検索です。

1

Pythonの の解像度は、JavaのPython では遅くなる可能性があります。レイトバインディングとは何ですか?

レイトバインディングは、特定の言語のインタープリタまたはコンパイラがどのようにして識別子をコードにマップするかを決定する戦略を記述します。たとえば、C#でobj.Foo()と記述することを検討してください。これをコンパイルすると、コンパイラは参照されたオブジェクトを見つけようとし、実行時に呼び出されるFooメソッドの場所への参照を挿入しようとします。このすべてのメソッドの解決はコンパイル時に発生します。名前は「早い」と結びついていると言います。

対照的に、Pythonは名前を「late」にバインドします。メソッドの解決はの実行時に行われます:正しいシグネチャで参照されたFooメソッドをインタープリタが単に探しようとしますが、存在しない場合は実行時エラーが発生します。

これら2つの の言語で 反射機構上の違いは何ですか?

動的言語は静的言語よりも優れたリフレクション機能を持つ傾向にあり、Pythonはこの点で非常に強力です。それでも、Javaは、クラスとメソッドの内部を取得するためのかなり広範な方法を持っています。それにもかかわらず、あなたはJavaの冗長性を取り戻すことはできません。あなたはPythonと同じようにJavaでより多くのコードを書くでしょう。 java.lang.reflect APIを参照してください。

8

Pythonでのメソッド呼び出しは、2つの別個の分離可能なステップで構成されています。最初に属性の検索が行われ、次にその検索の結果が呼び出されます。 Pythonで

foo.bar() 

method = foo.bar 
method() 

属性検索はかなり複雑なプロセスである:これは、次の2つのスニペットが同じ意味を持っていることを意味します。私たちはPythonコードに次の式を意味し、オブジェクトOBJに属性の名前のattrを探していると言う。そして、まずOBJのインスタンス辞書が 『attrの』検索されobj.attr

objのクラスのインスタンス辞書であり、その親クラスの辞書は "attr"のメソッド解決順序で検索されます。

通常、インスタンスに値が見つかった場合は、その値が返されます。しかし、クラスのルックアップが__get__と__set__の両方のメソッドを持つ値になった場合(正確には、valuesクラスとparentクラスの辞書ルックアップが両方のキーの値を持っている場合)、class属性は何かとみなされます「データ記述子」と呼ばれます。これは、その値の__get__メソッドが呼び出され、参照が発生したオブジェクトを渡し、その値の結果が返されることを意味します。 class属性が見つからないか、データ記述子でない場合は、インスタンス辞書の値が返されます。

インスタンスディクショナリに値がない場合は、クラスルックアップの値が返されます。それが "データ以外の記述子"でない限り、すなわち、それは__get__メソッドを持っています。 __get__メソッドが呼び出され、結果の値が返されます。もう一つの特別なケースがあり

OBJは、クラス、(タイプタイプのインスタンス)であることを起こる場合、それは記述子だならば、インスタンス値もチェックされ、それに応じて呼び出されます。

インスタンスやクラス階層に値がなく、objのクラスに__getattr__メソッドがある場合、そのメソッドが呼び出されます。

以下は、getattr()関数が行うことを効果的に行う、Pythonでコード化されたアルゴリズムを示しています。 (で滑ってきたバグを除く)

NotFound = object() # A singleton to signify not found values 

def lookup_attribute(obj, attr): 
    class_attr_value = lookup_attr_on_class(obj, attr) 

    if is_data_descriptor(class_attr_value): 
     return invoke_descriptor(class_attr_value, obj, obj.__class__) 

    if attr in obj.__dict__: 
     instance_attr_value = obj.__dict__[attr] 
     if isinstance(obj, type) and is_descriptor(instance_attr_value): 
      return invoke_descriptor(instance_attr_value, None, obj) 
     return instance_attr_value 

    if class_attr_value is NotFound: 
     getattr_method = lookup_attr_on_class(obj, '__getattr__') 
     if getattr_method is NotFound: 
      raise AttributeError() 
     return getattr_method(obj, attr) 

    if is_descriptor(class_attr_value): 
     return invoke_descriptor(class_attr_value, obj, obj.__class__) 

    return class_attr_value 

def lookup_attr_on_class(obj, attr): 
    for parent_class in obj.__class__.__mro__: 
     if attr in parent_class.__dict__: 
      return parent_class.__dict__[attr] 
    return NotFound 

def is_descriptor(obj): 
    if lookup_attr_on_class(obj, '__get__') is NotFound: 
     return False 
    return True 

def is_data_descriptor(obj): 
    if not is_descriptor(obj) or lookup_attr_on_class(obj, '__set__') is NotFound : 
     return False 
    return True 

def invoke_descriptor(descriptor, obj, cls): 
    descriptormethod = lookup_attr_on_class(descriptor, '__get__') 
    return descriptormethod(descriptor, obj, cls) 

すべてこの記述子のナンセンスはあなたが求めるメソッド呼び出しでに何を持っていますか?まあ、事は、その関数もオブジェクトであり、彼らは記述子プロトコルを実装することになります。属性ルックアップがクラス上の関数オブジェクトを見つけた場合、それは__get__メソッドが呼び出され、 "バウンドメソッド"オブジェクトを返します。バインドされたメソッドは、関数オブジェクトの周りの小さなラッパーで、関数が検索されたオブジェクトを格納し、呼び出されると、そのオブジェクトを引数リストの前に追加します(通常、メソッドの自己引数)です。

class Function(object): 
    def __get__(self, obj, cls): 
     return BoundMethod(obj, cls, self.func) 
    # Init and call added so that it would work as a function 
    # decorator if you'd like to experiment with it yourself 
    def __init__(self, the_actual_implementation): 
     self.func = the_actual_implementation 
    def __call__(self, *args, **kwargs): 
     return self.func(*args, **kwargs) 

class BoundMethod(object): 
    def __init__(self, obj, cls, func): 
     self.obj, self.cls, self.func = obj, cls, func 
    def __call__(self, *args, **kwargs): 
     if self.obj is not None: 
      return self.func(self.obj, *args, **kwargs) 
     elif isinstance(args[0], self.cls): 
      return self.func(*args, **kwargs) 
     raise TypeError("Unbound method expects an instance of %s as first arg" % self.cls) 

(Pythonの場合には、実際の属性解決順序を意味する)メソッド解決順序PythonはディランからC3アルゴリズムを使用する場合:

ここにいくつかの例示的なコードです。ここで説明するのは複雑すぎるので、興味があればthis articleを参照してください。本当にファンキーな継承階層を作っていない限り(そしてそうすべきではありません)、ルックアップの順序は右から左に、クラスのすべてのサブクラスはそのクラスが検索される前に検索されることが分かります。

+0

偉大な答えと+1の例のコードです。 is_descriptorとis_data_descriptorの使い方について議論できますか? – Hernan

関連する問題