2009-05-12 9 views
5

Come funziona l'invocazione dei metodi in Python? Voglio dire, come la macchina virtuale Python lo interpreta.In che modo la risoluzione e l'invocazione del metodo funzionano internamente in Python?

È vero che la risoluzione del metodo Python potrebbe essere più lenta in Python che in Java. Che cos'è l'associazione tardiva?

Quali sono le differenze sul meccanismo di riflessione in queste due lingue? Dove trovare buone risorse per spiegare questi aspetti?

+0

Hai difficoltà a trovare la fonte? Hai guardato su http://svn.python.org/view/python/trunk/Python/ –

+1

Le fonti mi sembrano un po 'più basse, in ogni caso grazie per l'interesse. –

risposta

4

I nomi (metodi, funzioni, variabili) vengono risolti osservando lo spazio dei nomi. I namespace sono implementati in CPython come dict s (mappe hash).

Quando un nome non viene trovato nello spazio dei nomi dell'istanza (dict), python va per la classe e quindi per le classi base, seguendo l'ordine di risoluzione dei metodi (MRO).

Tutte le risoluzioni vengono eseguite in fase di esecuzione.

È possibile giocare con il modulo dis per vedere come accade nel bytecode.

semplice esempio:

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) 

che stampa:

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   

Tutti LOAD s sono le ricerche dello spazio dei nomi.

1

E 'vero che la risoluzione pitone metodo potrebbe essere più lento in Python che in Java. Cos'è l'associazione tardiva?

L'associazione tardiva descrive una strategia su come un interprete o un compilatore di una determinata lingua decide come mappare un identificatore su un pezzo di codice. Ad esempio, considera la possibilità di scrivere obj.Foo() in C#. Quando si compila questo, il compilatore prova a trovare l'oggetto di riferimento e inserisce un riferimento alla posizione del metodo che verrà richiamato in fase di esecuzione. Tutta questa risoluzione del metodo avviene in fase di compilazione; diciamo che i nomi sono legati "presto".

Al contrario, Python lega i nomi "in ritardo". La risoluzione del metodo avviene a tempo di esecuzione: l'interprete cerca semplicemente di trovare il metodo di riferimento Foo con la firma corretta e, se non è presente, si verifica un errore di runtime.

Quali sono le differenze sul meccanismo riflessione in questi due lingue?

I linguaggi dinamici tendono ad avere strutture di riflessione migliori rispetto ai linguaggi statici e Python è molto potente in questo senso. Ancora, Java ha dei modi abbastanza estesi per ottenere l'interno di classi e metodi. Tuttavia, non è possibile aggirare la verbosità di Java; scriverai molto più codice per fare la stessa cosa in Java di quanto faresti in Python. Vedi l'API java.lang.reflect.

8

L'invocazione del metodo in Python consiste di due distinti passaggi separabili. Prima viene eseguita una ricerca degli attributi, quindi viene richiamato il risultato di tale ricerca. Ciò significa che i due seguenti frammenti hanno la stessa semantica:

foo.bar() 

method = foo.bar 
method() 

Abilità ricerca in Python è un processo piuttosto complesso. Diciamo che stiamo cercando fino attributo denominato attr sull'oggetto obj, il che significa l'espressione seguente codice Python: obj.attr

primo dizionario esempio 's obj viene cercato per 'attr', poi il il dizionario di istanza della classe obj ei dizionari delle sue classi genitore vengono ricercati nell'ordine di risoluzione dei metodi per "attr".

Normalmente se viene trovato un valore nell'istanza, viene restituito. Ma se la ricerca sulla classe produce un valore che ha entrambi i metodi __get__ e __set__ (per essere precisi, se una ricerca di dizionario sulla classe values ​​e le classi parent hanno valori per entrambe le chiavi) allora l'attributo class è considerato come qualcosa chiamato "descrittore di dati". Ciò significa che il metodo __get__ su quel valore viene chiamato, passando l'oggetto su cui si è verificata la ricerca e il risultato di tale valore viene restituito. Se l'attributo class non viene trovato o non è un descrittore di dati, viene restituito il valore dal dizionario delle istanze.

Se non c'è alcun valore nel dizionario di istanza, viene restituito il valore dalla ricerca di classe. A meno che non si tratti di un "descrittore non dati", cioè ha il metodo __get__. Quindi viene richiamato il metodo __get__ e restituito il valore risultante.

C'è un'altra caso particolare, se il obj sembra essere una classe, (un'istanza del tipo tipo), allora il valore di istanza viene anche controllato se è un descrittore e invocato conseguenza.

Se non viene trovato alcun valore nell'istanza o nella sua gerarchia di classi e la classe obj ha un metodo __getattr__, tale metodo viene chiamato.

Quanto segue mostra l'algoritmo come codificato in Python, facendo effettivamente ciò che farebbe la funzione getattr(). (Escluse eventuali bug che sono avanza)

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) 

Che cosa significa tutto questo descrittore sciocchezze devono con chiamata di metodo si chiede? Beh, il fatto è che anche le funzioni sono oggetti, e capita di implementare il protocollo descrittore. Se la ricerca dell'attributo trova un oggetto funzione sulla classe, vengono chiamati i metodi __get__ e restituisce un oggetto "metodo associato". Un metodo associato è solo un piccolo wrapper attorno all'oggetto funzione che memorizza l'oggetto su cui è stata cercata la funzione, e quando viene invocato antepone l'oggetto alla lista degli argomenti (dove in genere per le funzioni che si intendono metodi l'argomento auto è).

Ecco alcuni codice illustrativi:

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) 

Per ordine di risoluzione dei metodi (che nel caso di pitone significa in realtà ordine risoluzione attributo) Python utilizza l'algoritmo C3 da Dylan. È troppo complicato da spiegare qui, quindi se sei interessato vedi this article.A meno che tu non stia facendo delle gerarchie di ereditarietà davvero funky (e non dovresti), è sufficiente sapere che l'ordine di ricerca è lasciato a destra, prima la profondità e tutte le sottoclassi di una classe vengono cercate prima che la classe venga cercata.

+0

Ottima risposta e +1 per il codice di esempio. Puoi discutere degli usi di is_descriptor e is_data_descriptor? – Hernan

Problemi correlati