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.
¿Tiene problemas para encontrar la fuente? ¿Has visto http://svn.python.org/view/python/trunk/Python/ –
Las fuentes parecen un nivel un poco más bajo para mí, en cualquier caso, gracias por el interés. –