2009-05-12 10 views
5

¿Cómo funciona la invocación de métodos en Python? Quiero decir, cómo la máquina virtual de Python lo interpreta.¿Cómo funciona la resolución del método y la invocación internamente en Python?

Es cierto que la resolución del método python podría ser más lenta en Python que en Java. ¿Qué es el atascamiento tardío?

¿Cuáles son las diferencias en el mecanismo de reflexión en estos dos idiomas? ¿Dónde encontrar buenos recursos explicando estos aspectos?

+0

¿Tiene problemas para encontrar la fuente? ¿Has visto http://svn.python.org/view/python/trunk/Python/ –

+1

Las fuentes parecen un nivel un poco más bajo para mí, en cualquier caso, gracias por el interés. –

Respuesta

4

Los nombres (métodos, funciones, variables) se resuelven al mirar el espacio de nombres. Los espacios de nombres se implementan en CPython como dict s (mapas hash).

Cuando no se encuentra un nombre en el espacio de nombres de la instancia (dict), python aplica la clase y luego las clases base, siguiendo el orden de resolución del método (MRO).

Todas las soluciones se realizan en tiempo de ejecución.

Puede jugar con el módulo dis para ver cómo ocurre esto en bytecode.

ejemplo simple:

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) 

que imprime:

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   

Todo LOAD s son operaciones de búsqueda de espacios de nombres.

1

Es cierto que la resolución de pitón método podría ser más lento en Python que en Java. ¿Qué es el enlace tardío?

Late binding describe la estrategia de cómo un intérprete o compilador de un idioma determinado decide cómo asignar un identificador a un fragmento de código. Por ejemplo, considere escribir obj.Foo() en C#. Cuando compila esto, el compilador intenta encontrar el objeto al que se hace referencia e inserta una referencia a la ubicación del método Foo que se invocará en el tiempo de ejecución. Todo esto resolución de método ocurre en tiempo de compilación; decimos que los nombres están vinculados "temprano".

Por el contrario, Python vincula los nombres "tarde". La resolución del método ocurre en , tiempo de ejecución: el intérprete simplemente intenta encontrar el método referenciado Foo con la firma correcta, y si no está allí, se produce un error de tiempo de ejecución.

¿Cuáles son las diferencias en el mecanismo reflejo en estos dos idiomas ?

Los lenguajes dinámicos tienden a tener mejores facilidades de reflexión que los lenguajes estáticos, y Python es muy poderoso en este aspecto. Aún así, Java tiene formas muy amplias de acceder a las partes internas de las clases y los métodos. Sin embargo, no puedes evitar la verbosidad de Java; escribirá mucho más código para hacer lo mismo en Java de lo que haría en Python. Consulte la API java.lang.reflect.

8

La invocación del método en Python consta de dos pasos separables distintos. Primero se realiza una búsqueda de atributos, luego se invoca el resultado de esa búsqueda. Esto significa que las siguientes dos fragmentos tienen la misma semántica:

foo.bar() 

method = foo.bar 
method() 

búsqueda de atributos en Python es un proceso bastante complejo. Digamos que estamos buscando hasta atributo llamado attr en el objeto obj , lo que significa la expresión siguiente en el código Python: obj.attr

diccionario Primera obj 's instancia se busca 'attr', entonces el diccionario de instancia de la clase obj y los diccionarios de sus clases principales se buscan en el orden de resolución de método para "attr".

Normalmente, si se encuentra un valor en la instancia, se devuelve. Pero si la búsqueda en la clase da como resultado un valor que tiene los métodos __get__ y __set__ (para ser exactos, si una búsqueda de diccionario en la clase de valores y las clases principales tienen valores para ambas claves), entonces el atributo de clase se considera algo llamado un "descriptor de datos". Esto significa que se llama al método __get__ en ese valor, pasando el objeto en el que se produjo la búsqueda y se devuelve el resultado de ese valor. Si el atributo de clase no se encuentra o no es un descriptor de datos, se devuelve el valor del diccionario de instancias.

Si no hay ningún valor en el diccionario de instancia, se devuelve el valor de la búsqueda de clase. A menos que resulte ser un "descriptor no de datos", es decir, tiene el método __get__. Luego se invoca el método __get__ y se devuelve el valor resultante.

Hay otro caso especial, si el obj pasa a ser una clase, (una instancia del tipo tipo), entonces el valor de la instancia se comprueba también si se trata de un descriptor y se invoca en consecuencia.

Si no se encuentra ningún valor en la instancia ni en su jerarquía de clases, y la clase obj tiene un método __getattr__, se llama a ese método.

A continuación, se muestra el algoritmo codificado en Python, haciendo efectivamente lo que haría la función getattr(). (Excluyendo los errores que se han deslizado en)

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) 

¿Qué significa todo este absurdo descriptor tiene que con la invocación de métodos que usted pide? Bueno, la cuestión es que las funciones también son objetos y que implementan el protocolo descriptor. Si la búsqueda de atributos encuentra un objeto de función en la clase, se llama a los métodos __get__ y devuelve un objeto de "método vinculado". Un método vinculado es simplemente un pequeño contenedor alrededor del objeto de función que almacena el objeto en el que se buscó la función, y cuando se invoca, lo antepone a la lista de argumentos (donde generalmente se usan los métodos para el argumento self es).

Aquí hay un código ilustrativo:

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) 

Para la orden resolución método (que en el caso de Python en realidad significa orden resolución atributo) Python utiliza el algoritmo C3 de Dylan. Es muy complicado de explicar aquí, así que si está interesado, vea this article.A menos que esté haciendo jerarquías de herencia realmente funky (y no debería), es suficiente con saber que el orden de búsqueda es de izquierda a derecha, profundidad primero, y todas las subclases de una clase se buscan antes de buscar en esa clase.

+0

Gran respuesta y +1 para el código de ejemplo. ¿Puedes hablar sobre los usos de is_descriptor y is_data_descriptor? – Hernan

Cuestiones relacionadas