2010-07-12 11 views
24

Sto cercando di capire come ottenere i nomi di tutti i decoratori su un metodo. Posso già ottenere il nome del metodo e docstring, ma non riesco a capire come ottenere un elenco di decoratori.Introspection per ottenere nomi decoratori su un metodo?

+0

Questo sembra inutile. Hai la fonte. Cosa c'è di sbagliato leggendo la fonte? –

+15

@ S.Lott: non potreste rispondere allo stesso modo a qualsiasi domanda riguardante l'introspezione? Eppure l'introspezione è utile. –

+1

@ S.Lott: Non c'è niente di sbagliato nella lettura dell'origine, praticamente come se nulla fosse sbagliato leggendo i contenuti di un database direttamente invece di usare viste o script, a meno che non voglia l'automazione. Uso i decoratori per l'autenticazione e sto generando report con visualizzazioni diverse per mostrare quali gruppi di utenti hanno accesso a quali risorse. Quindi ho bisogno di un accesso programmatico alla sorgente, come se avessi bisogno di un accesso programmatico a un'origine dati. – Tony

risposta

23

Se è possibile cambiare il modo di chiamare i decoratori da

class Foo(object): 
    @many 
    @decorators 
    @here 
    def bar(self): 
     pass 

a

class Foo(object): 
    @register(many,decos,here) 
    def bar(self): 
     pass 

allora si potrebbe registrare i decoratori in questo modo:

def register(*decorators): 
    def register_wrapper(func): 
     for deco in decorators[::-1]: 
      func=deco(func) 
     func._decorators=decorators   
     return func 
    return register_wrapper 

Ad esempio:

def many(f): 
    def wrapper(*args,**kwds): 
     return f(*args,**kwds) 
    return wrapper 

decos = here = many 

class Foo(object): 
    @register(many,decos,here) 
    def bar(self): 
     pass 

foo=Foo() 

Qui accedere alla tupla di decoratori:

print(foo.bar._decorators) 
# (<function many at 0xb76d9d14>, <function decos at 0xb76d9d4c>, <function here at 0xb76d9d84>) 

Qui si stampa solo i nomi dei decoratori:

print([d.func_name for d in foo.bar._decorators]) 
# ['many', 'decos', 'here'] 
+1

Questa è un'ottima soluzione. : D Suppone che tu abbia accesso al codice che sta assegnando i decoratori, però ... – Faisal

+0

Ok questo potrebbe funzionare, ma perché non posso semplicemente aggiungere il codice func._whatever = 'qualcosa' nel mio decoratore esistente, e testare per il valore dell'attributo _whatever quando si esegue l'introspezione sul metodo? – Tony

+0

Puoi, ma poi dovrai sporcare ogni decoratore che scrivi con la preoccupazione trasversale di lasciare le sue tracce nella funzione che modifica. – Faisal

0

E 'impossibile fare in modo generale, perché

@foo 
def bar ... 

è esattamente lo stesso di

def bar ... 
bar = foo (bar) 

Si può fare in alcuni casi particolari, come probabilmente @staticmethod analizzando oggetti funzione , ma non meglio di così.

3

Questo perché i decoratori sono "zucchero sintattico". Diciamo che avete il seguente decoratore:

def MyDecorator(func): 
    def transformed(*args): 
     print "Calling func " + func.__name__ 
     func() 
    return transformed 

E lo si applica ad una funzione:

@MyDecorator 
def thisFunction(): 
    print "Hello!" 

Ciò equivale a:

thisFunction = MyDecorator(thisFunction) 

Si potrebbe incorporare una "storia" nella funzione oggetto, forse, se hai il controllo dei decoratori. Scommetto che c'è un altro modo intelligente per farlo (forse per incarico preponderante), ma sfortunatamente non sono molto esperto in Python. :(

1

Come Faisal notes, si potrebbe avere i decoratori si attaccano i metadati alla funzione, ma a mia conoscenza non è fatto automaticamente.

4

Non è possibile, per definizione.Decorator:

@dec 
def foo(): 
    return 1 

è solo scorciatoia per:

def foo_internal() 
    return 1 
foo = dec(foo_internal) 

Si noti che, decoratore dec è semplicemente un po 'di qualcosa di callable ritorno (funzione, forse qualche altro oggetto callable). Non so nemmeno se foo ha nulla a che fare con la definizione decorato, per esempio:

def dec(f): 
    def not_foo(): 
     return 0 
    return not_foo 

Se avete bisogno di mettere un po 'ulteriori informazioni sui metodi, classi ecc .--- come ad esempio attributi in .NET --- imposta semplicemente alcuni attributi su di essi.

def foo() 
    return 1 
foo.decorated = True 

Oppure implementare decoratori che impostano tali attributi, se è davvero utile per la leggibilità.

def dec(f): 
    f.decorated = True 
    return f 

Bene. Python è open-source. Puoi sempre estendere l'interprete Python per tracciare i decoratori applicati all'oggetto. Poiché (come mostrato sopra) l'oggetto restituito dal decoratore non deve essere correlato in alcun modo all'oggetto decorato, questa implementazione dovrebbe memorizzare le informazioni dall'oggetto decorato prima di applicare il decoratore, applicare il decoratore, sostituire le informazioni del decoratore sull'oggetto restituito e aggiungere informazioni sull'ultimo decoratore.

Può essere fatto. Ma non sono sicuro che sia davvero utile. E aggiunge un po 'di spese generali ad ogni decoratore. Quindi non mi aspetto un tale meccanismo nel mainstream Python, a meno che non offra altri vantaggi, come la semplificazione dell'implementazione di decoratori, ecc.

+2

"Non è possibile, per definizione" sembra una risposta errata al 100%, dato che Jaymon ha pubblicato come farlo con il pacchetto di riflessione standard ("import inspect") sopra. http://stackoverflow.com/a/31197273/1424877 Suggerisco di cancellare questa risposta ... a meno che non mi manchi qualcosa di sottile. – Quuxplusone

2

Non si può, ma peggio ancora, esistono librerie per nascondere il fatto che si ha decorato una funzione per cominciare. Vedere Functools o la libreria decoratore (@decorator se potessi trovarlo) per ulteriori informazioni.

21

Sono sorpreso che questa domanda è così vecchio e nessuno ha avuto il tempo di aggiungere il modo attuale introspettiva per fare questo, ecco che è:

Il codice che si desidera ispezionare ...

def template(func): 
    def wrapper(*args, **kwargs): 
     return func(*args, **kwargs) 
    return wrapper 

baz = template 
che = template 

class Foo(object): 
    @baz 
    @che 
    def bar(self): 
     pass 

Ora è possibile controllare quanto sopra Foo classe con qualcosa di simile ...

import ast 
import inspect 

def get_decorators(cls): 
    target = cls 
    decorators = {} 

    def visit_FunctionDef(node): 
     decorators[node.name] = [] 
     for n in node.decorator_list: 
      name = '' 
      if isinstance(n, ast.Call): 
       name = n.func.attr if isinstance(n.func, ast.Attribute) else n.func.id 
      else: 
       name = n.attr if isinstance(n, ast.Attribute) else n.id 

      decorators[node.name].append(name) 

    node_iter = ast.NodeVisitor() 
    node_iter.visit_FunctionDef = visit_FunctionDef 
    node_iter.visit(ast.parse(inspect.getsource(target))) 
    return decorators 

print get_decorators(Foo) 

questo dovrebbe stampare qualcosa di simile ...

{'bar': ['baz', 'che']} 

o almeno lo ha fatto quando ho provato questo con Python 2.7.9 reale veloce :)

+0

Ok, questo ha problemi in python 3: (almeno sembra, e sono sicuro di non essere * la persona migliore con la conoscenza di inspect/ast per commentare con quel livello di certezza); fondamentalmente ho un codice da quello che hai sopra, e 'inspect.getsource()' sembra tornare con gli spazi davanti al 'def wrapper', che poi dà un errore di rientro imprevisto sulla chiamata' ast.parse' . – Jmons

+0

* ANCHE * quando ho provato a dimostrarlo usando gli strumenti di run-script hte online (che credo piuttosto che eseguire script da un file), ho ottenuto "OSError: codice sorgente non disponibile" quindi sospetto che ci siano istanze (forse anche bin runs) dove questo processo non funzionerà. Forse non funzionerà quando esistono solo percorsi di python di python? – Jmons

+1

Quale versione di python3? L'ho appena testato in Python 2.7.13 e Python 3.6.4 (che sono le versioni che ho sul mio computer) ed entrambi hanno funzionato bene. Inoltre, non sono sicuro che funzionerà ovunque, ma ha funzionato ovunque ne avessi avuto bisogno. Potrei sicuramente vedere lo script di esecuzione online con protezioni per moduli come ast e inspect, e probabilmente altre cose come l'apertura di file, poiché è, per definizione, un ambiente più contenuto. – Jaymon