2012-04-06 13 views
13

Si consideri il seguente codice Python:Python introspezione: il nome della funzione di accesso e docstring all'interno definizione di funzione

def function(): 
    "Docstring" 

    name = ??? 
    doc = ??? 

    return name, doc 

>>> function() 
"function", "Docstring" 

cosa ho bisogno di sostituire i punti interrogativi con così che ho il nome e la docstring della funzione da all'interno della stessa funzione?

MODIFICA: La maggior parte delle risposte finora ha esplicitamente codificato il nome della funzione all'interno della sua definizione. È possibile fare qualcosa di simile qui sotto dove una nuova funzione get_name_doc dovrebbe accedere alla funzione dal frame esterno da cui è chiamata, e restituire il suo nome e doc?

def get_name_doc(): 
    ??? 

def function(): 
    "Docstring" 

    name, doc = get_name_doc() 

    return name, doc 

>>> function() 
"function", "Docstring" 

risposta

13

Questo non è possibile fare in modo pulito in modo coerente perché i nomi possono essere modificati e riassegnati.

Tuttavia, è possibile utilizzare questo fino a quando la funzione non viene rinominato o decorato.

>>> def test(): 
...  """test""" 
...  doc = test.__doc__ 
...  name = test.__name__ 
...  return doc, name 
... 
>>> test() 
('test', 'test') 
>>> 

Non è affatto affidabile. Ecco un esempio di come sta andando male.

>>> def dec(f): 
...  def wrap(): 
...   """wrap""" 
...   return f() 
...  return wrap 
... 
>>> @dec 
... def test(): 
...  """test""" 
...  return test.__name__, test.__doc__ 
... 
>>> test() 
('wrap', 'wrap') 
>>> 

Ciò è perché il nome test non è definita nel momento in cui la funzione è effettivamente creato ed è un riferimento globale nella funzione. Viene quindi esaminato nell'ambito globale ad ogni esecuzione. Quindi le modifiche al nome nell'ambito globale (come i decoratori) interromperanno il tuo codice.

+1

In questo caso, c'è un modo per ottenere la stringa doc 'correct'? Qualche soluzione per risolvere il problema? – George

+0

@George, vedi il fondo del mio post per avere un'idea. Ho giocato con cose del genere semplicemente hackerando ma non sono mai riuscito a farlo funzionare a un livello che considero pronto per la produzione. – aaronasterling

+0

Informazioni su 'inspect.stack()' su questo link: http://stackoverflow.com/questions/900392/getting-the-caller-function-name-inside-another-function-in-python, come fa a sapere quale indice accedere allo stack? Suppongo che non sia una supposizione, giusto? Sono interessato se riesci a capire in modo dinamico quale 'stack [x] [y]' accedere? – George

1

Dovete usare il nome della funzione per farlo:

def function(): 
    "Docstring" 

    name = function.__name__ 
    doc = function.__doc__ 

    return name, doc 

C'è anche un modulo chiamato ispezionare: http://docs.python.org/library/inspect.html. Questo è utile per ottenere ulteriori informazioni sulla funzione (o qualsiasi oggetto python).

+0

E 'possibile farlo senza usare il nome in modo esplicito? – dzhelil

+2

Python è pensato per essere esplicito. – jamylak

+1

Questa risposta è sbagliata e inaffidabile. Prova ad applicare un decoratore. – aaronasterling

1
def function(): 
    "Docstring" 

    name = function.__name__ 
    doc = function.__doc__ 

    return name, doc  

Questo dovrebbe farlo, utilizzare il nome della funzione, nel tuo caso, function.

Ecco un bel tutorial che ne parla: http://epydoc.sourceforge.net/docstrings.html

E naturalmente: http://docs.python.org/tutorial/controlflow.html#documentation-strings

Edit:, consultare la versione modificata della questione, penso che si potrebbe avere a pasticciare con inspect.stack()from this SO question. La risposta di Ayman Hourieh fornisce un piccolo esempio.

1
>>> def function(): 
     "Docstring" 

     name = function.__name__ 
     doc = function.__doc__ 

     return name, doc 

>>> function() 
('function', 'Docstring') 
1

Questo troverà il nome e il doc di una funzione che chiama get_doc. Nel mio senso, get_doc dovrebbe avere la funzione come argomento (che avrebbe reso davvero facile, ma molto meno divertente di raggiungere;))

import inspect 

def get_doc(): 
    """ other doc 
    """ 
    frame = inspect.currentframe() 

    caller_frame = inspect.getouterframes(frame)[1][0] 
    caller_name = inspect.getframeinfo(caller_frame).function 
    caller_func = eval(caller_name) 

    return caller_name, caller_func.__doc__ 


def func(): 
    """ doc string """ 
    print get_doc() 
    pass 


def foo(): 
    """ doc string v2 """ 
    func() 

def bar(): 
    """ new caller """ 
    print get_doc() 

func() 
foo() 
bar() 
+0

è necessario verificare se la funzione che chiama è decorata, ma non dovrebbe essere un problema in quanto il decoratore dovrebbe essere il fotogramma successivo, e non il precedente –

+0

Se non si esegue il comando 'del' frame, viene creato un ciclo. – aaronasterling

+0

@anche, fallisce con i decoratori perché si sta usando 'eval' per valutare il nome nell'ambito globale. Quindi questa è la stessa risposta che hanno dato tutti gli altri eccetto che tu usi 'inspect' per ottenere un nome in' eval' nello scope globale e assumere che la funzione sia presente e non, ad esempio in un modulo. – aaronasterling

6

Il codice di seguito risolve il problema per il nome della funzione. Tuttavia, non riesce a rilevare la docstring corretta per l'esempio fornito da aaronasterling. Mi chiedo se c'è un modo per tornare alla struttura sintattica astratta associata ad un oggetto bytecode. Quindi sarebbe abbastanza facile leggere la docstring.

import inspect 

def get_name_doc(): 
    outerframe = inspect.currentframe().f_back 
    name = outerframe.f_code.co_name 
    doc = outerframe.f_back.f_globals[name].__doc__  
    return name, doc 

if __name__ == "__main__": 

    def function(): 
     "Docstring" 

     name, doc = get_name_doc() 

     return name, doc 

    def dec(f): 
     def wrap(): 
      """wrap""" 
      return f() 
     return wrap 

    @dec 
    def test(): 
     """test""" 
     return get_name_doc() 

    assert function() == ('function', "Docstring") 
    #The assertion below fails:. It gives: ('test', 'wrap') 
    #assert test() == ('test', 'test') 
1

ne dite di questo:

import functools 

def giveme(func): 
    @functools.wraps(func) 
    def decor(*args, **kwargs): 
     return func(decor, *args, **kwargs) 
    return decor 

@giveme 
def myfunc(me): 
    "docstr" 
    return (me.__name__, me.__doc__) 

# prints ('myfunc', 'docstr') 
print myfunc() 

Poco, il decoratore giveme aggiunge la (decorato) oggetto funzione come primo argomento. In questo modo la funzione può accedere al proprio nome e docstring quando viene chiamata.

A causa della decorazione, la funzione myfunc originale è stata sostituita da decor. Per fare in modo che il primo argomento sia esattamente uguale a myfunc, ciò che viene passato nella funzione è decor e non func.

Il decoratore functools.wraps viene utilizzato per fornire a decor le proprietà (nome, docstring, ecc.) Della funzione originale myfunc.

+0

Stai passando la funzione wrapped al decoratore come primo argomento e non alla funzione ... – aaronasterling

+0

Sì, ho spiegato perché. – yak

+0

Nella descrizione, si dichiara di passare l'oggetto funzione. – aaronasterling

2
>>> import inspect 
>>> def f(): 
...  """doc""" 
...  name = inspect.getframeinfo(inspect.currentframe()).function 
...  doc = eval(name + '.__doc__') 
...  return name, doc 
... 
>>> f() 
('f', 'doc') 
>>> class C: 
...  def f(self): 
...   """doc""" 
...   name = inspect.getframeinfo(inspect.currentframe()).function 
...   doc = eval(name + '.__doc__') 
...   return name, doc 
... 
>>> C().f() 
('f', 'doc') 
1

per una versione hardcoded che funziona decentemente con decoratori "ben educati". Deve essere dichiarato dopo la funzione. se la funzione viene rimbalzata in un secondo momento, le modifiche vengono aggiornate qui.

def get_name_doc(): 
    # global function # this is optional but makes your intent a bit more clear. 
    return function.__name__, function.__doc__ 

Questo è un trucco piuttosto brutto, in quanto abusa del modo in cui gli argomenti di default funzionano. Utilizzerà qualsiasi funzione associata al momento in cui questa funzione è 'inizializzata' e la ricorderà anche se la funzione viene rimbalzata. Chiamarlo con args porterà a risultati interessanti.

def get_name_doc(fn=function): 
    return fn.__name__, fn.__doc__ 

e uno dinamico che è ancora codificato, ma si aggiorna sulla funzione ricevendo una chiamata con un argomento di True. Fondamentalmente questa versione verrà aggiornata solo quando gli verrà richiesto di farlo.

def get_name_doc(update=False): 
    global fn 
    if update: 
     fn = function 
    return fn.__name__, fn.__doc__ 

Ora ovviamente ci sono anche esempi di decoratori.

@decorator # applying the decorator decorator to make it well behaved 
def print_name_doc(fn, *args, **kwargs): 
    def inner(*args, **kwargs): 
     print(fn.__doc__, fn.__name__) # im assuming you just want to print in this case 
     return fn(*args, **kwargs) 
return inner 

si dovrebbe leggere sul decoratore decoratore (almeno). Cerca l'origine NamedTuple (dal modulo delle collezioni) poiché implica l'essere codificato. Purtroppo il codice tupla è piuttosto strano. È un formato di stringa utilizzato con eval piuttosto che con codice tradizionale, ma funziona davvero bene. Questa sembra essere la variante più promettente. Potresti riuscire a farlo anche con metaclasess, che porta a un codice accurato, ma a cose piuttosto brutte nascoste dietro le quinte, che devi codificare. Questo id sconsiglia di

Im sospetto che ci sia probabilmente un modo più semplice di andare in ispezione/riflessione/modelli/metaclasess semplicemente aggiungendo la seguente riga alla fine del modulo.

help(<module>) 

dove si trova il nome del modulo su cui si sta lavorando (una stringa). O anche la variabile __name__. Questo potrebbe essere fatto anche nel file __init__.py se si lavora con più moduli o su singole classi penso che sia.

1

Come notato più volte, l'uso del nome della funzione all'interno della funzione è in realtà una ricerca dinamica nei globali() del modulo corrente. L'utilizzo di qualsiasi tipo di eval() è solo una variazione da esso poiché la risoluzione del suo nome funzionerà nuovamente con il dizionario globals().La maggior parte degli esempi non funzionerà con una funzione membro: è necessario prima cercare il nome della classe da globals() e successivamente accedere alla funzione membro. Quindi, in realtà

def function(): 
    """ foo """ 
    doc = function.__doc__ 

class Class: 
    def function(): 
     """ bar """ 
     doc = Class.function.__doc__ 

è equivalente a

def function(): 
    """ foo """ 
    doc = globals()["function"].__doc__ 

class Class: 
    def function(): 
     """ bar """ 
     doc = globals()["Class"].function.__doc__ 

In molti casi questa ricerca dinamica sarà sufficiente. Ma in realtà devi ridigitare il nome della funzione all'interno della funzione. Tuttavia, se si scrive una funzione di supporto per scoprire la stringa doc del chiamante, si dovrà affrontare il fatto che la funzione helper potrebbe vivere in un modulo diverso con un dizionario globals() diverso. Quindi l'unico modo corretto sarebbe quello di usare le informazioni sul frame corrente per trovare la funzione - ma l'oggetto frame di Python non ha un riferimento all'oggetto function, porta solo un riferimento al codice "f_code" che usa. Ha bisogno di cercare attraverso i "f_globals" fa riferimento dizionario per trovare la mappatura dal f_code all'oggetto funzione, ad esempio come questo:

import inspect 

def get_caller_doc(): 
    frame = inspect.currentframe().f_back.f_back 
    for objref in frame.f_globals.values(): 
     if inspect.isfunction(objref): 
      if objref.func_code == frame.f_code: 
       return objref.__doc__ 
     elif inspect.isclass(objref): 
      for name, member in inspect.getmembers(objref): 
       if inspect.ismethod(member): 
        if member.im_func.func_code == frame.f_code: 
         return member.__doc__ 

Prende il nome get_caller_doc() al posto di get_my_doc(), perché nella stragrande maggioranza dei casi che si desidera avere la stringa doc per consegnarla come argomento ad una qualche funzione di supporto. Ma la funzione helper può facilmente ottenere la stringa doc dal suo chiamante - sto usando questo nei miei script di unittest dove una funzione helper può usare la doc string del test per postarla in qualche registro o usarla come dati di test effettivi. Ecco perché l'helper presentato cerca solo le stringhe doc delle funzioni di test e le funzioni dei membri del test.

class MyTest: 
    def test_101(self): 
     """ some example test """ 
     self.createProject("A") 
    def createProject(self, name): 
     description = get_caller_doc() 
     self.server.createProject(name, description) 

E 'lasciato al lettore espandere l'esempio per altri casi d'uso.

2

Per i miei progetti personali, ho sviluppato il nome della funzione e le tecniche di ripristino doc per le funzioni e i metodi di classe. Questi sono implementati in un modulo importabile (SelfDoc.py) che ha il suo autotest nel suo principale. È incluso sotto. Questo codice viene eseguito come in Python 2.7.8 su Linux e MacOS. È in uso attivo.

#!/usr/bin/env python 

from inspect import (getframeinfo, currentframe, getouterframes) 

class classSelfDoc(object): 

    @property 
    def frameName(self): 
     frame = getframeinfo(currentframe().f_back) 
     return str(frame.function) 

    @property 
    def frameDoc(self): 
     frame = getframeinfo(currentframe().f_back) 
     doc = eval('self.'+str(frame.function)+'.__doc__') 
     return doc if doc else 'undocumented' 

def frameName(): 
    return str(getframeinfo(currentframe().f_back).function) 

def frameDoc(): 
    doc = eval(getframeinfo(currentframe().f_back).function).__doc__ 
    return doc if doc else 'undocumented' 

if __name__ == "__main__": 

    class aClass(classSelfDoc): 
     "class documentation" 

     def __init__(self): 
      "ctor documentation" 
      print self.frameName, self.frameDoc 

     def __call__(self): 
      "ftor documentation" 
      print self.frameName, self.frameDoc 

     def undocumented(self): 
      print self.frameName, self.frameDoc 

    def aDocumentedFunction(): 
     "function documentation" 
     print frameName(), frameDoc() 

    def anUndocumentedFunction(): 
     print frameName(), frameDoc() 

    anInstance = aClass() 
    anInstance() 
    anInstance.undocumented() 

    aDocumentedFunction() 
    anUndocumentedFunction() 
1

Riferimento http://stefaanlippens.net/python_inspect

import inspect 
# functions 
def whoami(): 
    return inspect.stack()[1][3] 
def whocalledme(): 
    return inspect.stack()[2][3] 
def foo(): 
    print "hello, I'm %s, daddy is %s" % (whoami(), whocalledme()) 
    bar() 
def bar(): 
    print "hello, I'm %s, daddy is %s" % (whoami(), whocalledme()) 
johny = bar 
# call them! 
foo() 
bar() 
johny() 

uscita:

hello, I'm foo, daddy is ? 
hello, I'm bar, daddy is foo 
hello, I'm bar, daddy is ? 
hello, I'm bar, daddy is ? 
Problemi correlati