Come è possibile che una funzione di accesso i suoi attributi?

è possibile accedere alla funzione python attributi degli oggetti da dentro la funzione scope?

ad esempio, diamo

def f():
    return SOMETHING

f._x = "foo"
f()           # -> "foo"

ora, che QUALCOSA deve essere, se vogliamo avere la _x il contenuto di un attributo “pippo” ha restituito? se è ancora possibile (semplicemente)

grazie

AGGIORNAMENTO:

vorrei i seguenti lavori anche:

g = f
del f
g()          # -> "foo"

AGGIORNA 2:

Dichiarazione che essa non è possibile (se è il caso), e perché, è più soddisfacente di fornire un modo come falso ad esempio con un oggetto diverso rispetto a una funzione

  • Che cosa vi impedisce di essere semplicemente una funzione con un parametro?
  • Space_C0wb0y: i parametri della funzione sono off topic, questa è una domanda di teoria, non la vita reale soluzioni pragmatiche
  • +1 per me esplorare (e imparare) che angolo di python interne 😉
InformationsquelleAutor mykhal | 2010-06-24



15 Replies
  1. 47

    Soluzione

    Fare una funzione di default di argomenti essere un riferimento alla funzione stessa.

    def f(self):
        return self.x
    f.func_defaults = (f,)

    Esempio di utilizzo:

    >>> f.x = 17
    >>> b = f
    >>> del f
    >>> b()
    17

    Spiegazione

    Il poster originale voluto una soluzione che non richiede un nome globale di ricerca. La soluzione più semplice

    def f():
        return f.x

    esegue una ricerca della variabile globale f su ogni chiamata, che non soddisfa i requisiti. Se f viene eliminato, quindi la funzione ha esito negativo. Il più complicato inspect proposta non nello stesso modo.

    Quello che vogliamo è quello di eseguire associazione anticipata e conservare il legato di riferimento all’interno dell’oggetto stesso. Il seguente è concettualmente quello che stiamo facendo:

    def f(self=f):
        return self.x

    Sopra, self è una variabile locale, in modo che nessun globale viene eseguita la ricerca. Tuttavia, non siamo in grado di scrivere il codice così com’è, perché f non è ancora definito quando si tenta di associare il valore di default di self ad esso. Invece, abbiamo impostato il valore di default dopo f è definito.

    Decoratore

    Ecco un semplice decoratore per fare questo per voi. Nota che il self argomento deve venire ultimo, a differenza di metodi, in cui self viene prima di tutto. Questo significa anche che si deve dare un valore di default se e per gli altri argomenti e prendere un valore di default.

    def self_reference(f):
        f.func_defaults = f.func_defaults[:-1] + (f,)
        return f
    
    @self_reference
    def foo(verb, adverb='swiftly', self=None):
        return '%s %s %s' % (self.subject, verb, adverb)

    Esempio:

    >>> foo.subject = 'Fred'
    >>> bar = foo
    >>> del foo
    >>> bar('runs')
    'Fred runs swiftly'
    • complimenti miglior risposta!!!
    • Questo si sente come una chiusura decoratore. Mi piace.
    • Io non sono un grande fan di questo approccio, ma non riesce a spiegare perché. Per altri approcci vedere questa risposta “variabile Statica in python?”.
    • Possiamo aggiungere che f.func_defaults ha rinominato f.__defaults__ in Python 3 per coerenza? Anche il i dubbi sono condivisi da altri
    • Non mi piace il decoratore, perché nel mio piccolo cervello non ricordo che questo decoratore non deve essere applicato a funzioni arbitrarie, ma solo a quelli che hanno una predisposizione ad accettare self come ultimo argomento. Immagino che dovrò fare il solito ritorno a capo per ottenere un’altra chiusura
  2. 24

    Si potrebbe utilizzare una classe per fare questo

    >>> class F(object):
    ...     def __call__(self, *args, **kw):
    ...         return self._x
    ... 
    >>> f=F()
    >>> f._x = "foo"
    >>> f()
    'foo'
    >>> g=f
    >>> del f
    >>> g()
    'foo'
    • ben lavoro di simulazione 🙂
    • +1, cosa più sensata da fare, se questo è per un progetto reale.
    • questo è quello che mi piacerebbe utilizzare in scenario reale
  3. 15

    Bene, diamo un’occhiata a ciò che la funzione è:

    >>> def foo():
    ...     return x
    ... 
    >>> foo.x = 777
    >>> foo.x
    777
    >>> foo()
    Traceback (most recent call last):
      File "<interactive input>", line 1, in <module>
      File "<interactive input>", line 2, in foo
    NameError: global name 'x' is not defined
    >>> dir(foo)
    ['__call__', '__class__', '__delattr__', '__dict__', '__doc__', '__get__', 
    '__getattribute__', '__hash__', '__init__', '__module__', '__name__', '__new__', 
    '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__str__', 
    'func_closure', 'func_code', 'func_defaults', 'func_dict', 'func_doc', 
    'func_globals', 'func_name', 'x']
    >>> getattr(foo, 'x')
    777

    Aha! Così l’attributo è stato aggiunto alla funzione dell’oggetto, ma non vedo perché si sta cercando globale x invece.

    Possiamo cercare di afferrare il telaio della funzione di esecuzione e prova a guardare che cosa c’è (essenzialmente ciò che Anthony Kong suggerito, ma w/o inspect modulo):

    >>> def foo():
    ...     import sys
    ...     return sys._getframe()
    ... 
    >>> fr = foo()
    >>> dir(fr)
    ['__class__', '__delattr__', '__doc__', '__getattribute__', '__hash__', '__init__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__str__', 'f_back', 'f_builtins', 'f_code', 'f_exc_traceback', 'f_exc_type', 'f_exc_value', 'f_globals', 'f_lasti', 'f_lineno', 'f_locals', 'f_restricted', 'f_trace']
    >>> fr.f_locals
    {'sys': <module 'sys' (built-in)>}
    >>> fr.f_code
    <code object foo at 01753020, file "<interactive input>", line 1>
    >>> fr.f_code.co_code
    'd\x01\x00d\x00\x00k\x00\x00}\x00\x00|\x00\x00i\x01\x00\x83\x00\x00S'
    >>> fr.f_code.co_name
    'foo'

    Aha! Così forse si può ottenere il nome della funzione da il nome del blocco di codice, e poi cerca in giro-su strada per l’attributo? Certo, basta:

    >>> getattr(fr.f_globals[fr.f_code.co_name], 'x')
    777
    >>> fr.f_globals[fr.f_code.co_name].x
    777
    >>> def foo():
    ...     import sys
    ...     frm = sys._getframe()
    ...     return frm.f_globals[frm.f_code.co_name].x
    ... 
    >>> foo.x=777
    >>> foo()
    777

    Che è grande! Ma vorresti stare in piedi la ridenominazione ed eliminazione della funzione originale?

    >>> g = foo
    >>> g.func_name
    'foo'
    >>> g.func_code.co_name
    'foo'

    Ah, molto dubbia. La funzione di un oggetto e il suo codice oggetto ancora insistono che sono chiamati foo. Certo, basta, qui è dove si rompe:

    >>> g.x
    777
    >>> g.x=888
    >>> foo.x
    888
    >>> g()
    888
    >>> del foo
    >>> g()
    Traceback (most recent call last):
      File "<interactive input>", line 1, in <module>
      File "<interactive input>", line 4, in foo
    KeyError: 'foo'

    Dang! Così, in generale, non può essere fatto attraverso l’introspezione, attraverso l’esecuzione di cornici. I problemi sembra che c’è una differenza tra funzione di oggetto e codice oggetto – codice di oggetti sono ciò che viene eseguito e si è solo attributo func_code della funzione-oggetto e come tale non ha accesso a func_dict attributo, dove il nostro attributo x è:

    >>> g
    <function foo at 0x0173AE30>
    >>> type(g)
    <type 'function'>
    >>> g.func_code
    <code object foo at 017532F0, file "<interactive input>", line 1>
    >>> type(g.func_code)
    <type 'code'>
    >>> g.func_dict
    {'x': 888}

    C’è, naturalmente, gli altri cavillo si può fare in modo che sembra come funzione – in particolare il trucco con la definizione della classe… ma che non è una funzione di per sé. Tutto dipende da cosa ti serve veramente a che fare con questo.

    • Inutile come una risposta immediata, ma incredibilmente illuminante in tutti gli altri modi. Mi ha davvero insegnato molto circa il perché di ambito. +1
  4. 8

    Come soluzione alternativa si potrebbe utilizzare una fabbrica funzione di riparare il vostro campo di applicazione:

    def factory():
        def inner():
            print inner.x
        return inner
    
    
    >>> foo=factory()
    >>> foo.x=11
    >>> foo()
    11
    >>> bar = foo
    >>> del foo
    >>> bar()
    11
  5. 3

    Dubito che questo è il migliori modo per ottenere questo, ma si può accedere agli attributi utilizzando il metodo del nome all’interno del metodo:

    >>> def foo():
    ...   print foo.x
    ... 
    >>> foo()
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
      File "<stdin>", line 2, in foo
    AttributeError: 'function' object has no attribute 'x'
    >>> foo.x = 5
    >>> foo()
    5
    • hmm. ma che cosa se posso avere di lavoro per “rinominato” la funzione, come: g = f; del f; print(g())? 🙂
    • si conserva x valore di corso, dal momento g è solo un altro riferimento a qualcosa di inizialmente fatto riferimento solo da f
    • nailxx: ehm, sì, ma poiché non facciamo riferimento a questo con esplicito il nome della funzione, che è ora cancellato, NameError è cresciuto
  6. 3

    Ecco un decoratore che inietta current_fun nelle funzioni globali prima di eseguire la funzione. E ‘ veramente un hack, ma anche molto efficace.

    from functools import wraps
    
    
    def introspective(f):
        @wraps(f)
        def wrapper(*args, **kwargs):
            exists = 'current_fun' in f.func_globals
            old = f.func_globals.get('current_fun',None)
            f.func_globals['current_fun'] = wrapper
            try:
                return f(*args, **kwargs)
            finally:
                if exists:
                    f.func_globals['current_fun'] = old
                else:
                    del f.func_globals['current_fun']
        return wrapper
    
    @introspective
    def f():
        print 'func_dict is ',current_fun.func_dict
        print '__dict__ is ',current_fun.__dict__
        print 'x is ',current_fun.x

    Ecco un esempio di utilizzo

    In [41]: f.x = 'x'
    
    In [42]: f()
    func_dict is  {'x': 'x'}
    __dict__ is  {'x': 'x'}
    x is  x
    
    In [43]: g = f
    
    In [44]: del f
    
    In [45]: g()
    func_dict is  {'x': 'x'}
    __dict__ is  {'x': 'x'}
    x is  x
    • Bello! 🙂 Per Python 3 func_globals deve essere sostituito con __globals__. Ho esteso il decoratore per accettare il nome dell’attributo: def introspective(f, attribute_name='self'): e in realtà rinominato per @add_self. Tutti 'current_fun' occorrenze deve essere sostituito con attribute_name. Per impostazione predefinita, il decoratore avvolge l’attributo self in funzione. Ora posso fare self.x all’interno di funzioni e classi. Io non la uso mai ma per self.logger.info() che ora possono essere ben aggiunta a tutte le funzioni, i metodi e le classi con decoratori.
    • Molto, molto bello. +1
  7. 2

    La risposta è piuttosto semplice. Basta usare il fatto del nome da cercare in fase di esecuzione, non in fase di compilazione:

    def f():
        return f._x
    
    f._x = "foo"
    f()           # -> "foo"
    • non funziona, quando la funciton viene rinominato
    • Vero, ma funziona se la funzione ha un alias…
  8. 1

    Se si vuole essere totalmente indipendente dal nome della funzione, avete bisogno di qualche frame magic. Per esempio:

    def f2():
        import inspect
        frame = inspect.currentframe()
        fname = frame.f_code.co_name
        fobj = frame.f_globals[fname]
        print fobj._x
    
    
    f2._x = 2
    f2() 
    • interessante. sembrava promettente, ma purtroppo non funziona bene.. prova a f3=f2; del f2; f3()
    • ho pensato inspect richiede il file di origine per essere disponibile?
    • Questo non offre alcun miglioramento rispetto al semplice return f2._x. Il codice co_name è il nome della funzione, quando è stato definito, quindi, se la funzione viene rinominato, la ricerca nel dizionario globale avrà esito negativo.
  9. 1

    Questo utilizza un po ‘ di un hackish approccio, ma è forse più corretto finora, dato che funziona con il g() chiamata così. Funziona perché fare affidamento su qualsiasi bytecode ispezione è eseguita dal dis modulo, come scorciatoia.

    Sembra più hackish di quello che realmente è, in parte, perché dis.disassemble() chiamata stampa su stdout, così ho redirect che in un StringIO. Io uso disassemble() per la sua caratteristica di evidenziare l’ultima istruzione (aggiungere un print text linea in là per vedere come sembra) e che rende più facile per afferrare il precedente LOAD_NAME e la variabile sia utilizzato.

    Sarebbe possibile utilizzare un detergente bytecode ispezione biblioteca di farlo senza utilizzare il dis modulo, ma questo dimostra che è possibile. Questo potrebbe non essere il più robusto approccio, ma poi di nuovo forse funzionerà nella maggior parte dei casi. Non ho passato abbastanza tempo frugando in Python interni o bytecode per sapere se la maggior parte CALL_FUNCTION di bytecode sono immediatamente preceduto da istruzioni che la regex trick scegliere.

    import inspect
    import dis
    import re
    import sys
    import StringIO
    
    def f():
        caller = inspect.stack()[1][0]
        sys.stdout = StringIO.StringIO()
        dis.disassemble(caller.f_code, caller.f_lasti)
        text = sys.stdout.getvalue()
        sys.stdout = sys.__stdout__
        match = re.search(r'LOAD_NAME.*\((.*?)\)\s+-->', text)
        name = match.group(1)
        try:
            func = caller.f_locals[name]
        except KeyError:
            func = caller.f_globals[name]
        return func._x
    
    f._x = 'foo'
    print 'call f():', f()
    g = f
    del f
    print 'call g():', g()

    Questo genera il seguente output:

    call f(): foo
    call g(): foo
    • ho sperato per la soluzione più semplice, ma solo tuo soddisfa la domanda di condizioni, in modo che io sto dando il bounty.
    • Grazie @mykhal. Si potrebbe probabilmente essere semplificato con una delle librerie pypi.python.org/… ma mi sembra che il disegno di Python rende praticamente irraggiungibile. La funzione in oggetto contiene contesto, per richiamare il codice oggetto, ma una volta dentro si ha solo quel contesto e diretto la registrazione di ciò che l’oggetto della funzione è venuto, oltre a tracciare a ritroso attraverso i fotogrammi e il bytecode di questo hack è che fa così crudamente. E ‘ una limitazione interessante, ma sembra che siamo bloccati con esso.
  10. 0

    Come sull’utilizzo di una classe invece di una funzione e abusando del __new__ metodo per rendere la classe callable come una funzione? Dal momento che il __new__ metodo ottiene il nome della classe come primo parametro, si può accedere a tutti gli attributi della classe

    come in

    class f(object):
            def __new__(cls, x):
                print cls.myattribute
                return x

    questo funziona come in

    f.myattribute = "foo"
    f(3)
    foo
    3

    allora si può fare

    g=f
    f=None
    g(3)
    foo
    3

    Il problema è che, anche se l’oggetto si comporta come una funzione, non. Di conseguenza, le Idi di non riuscire a fornire la firma.

  11. 0

    Un altro modo per farlo è quello di definire la funzione all’interno di un’altra funzione, e hanno la funzione esterna di ritorno di quella interna. Quindi la funzione interna può accedere tramite una chiusura. Ecco un semplice esempio:

    def makeFunc():
        def f():
            return f._x
        return f

    Quindi:

    >>> f = makeFunc()
    >>> f._x = "foo"
    >>> f()
    'foo'
    >>> g = f
    >>> del f
    >>> g()
    'foo'
  12. 0

    Se c’è un solo metodo necessario, ma si desidera una luce-peso classe condivisa con lo stato della classe più istanze dello stato, si potrebbe provare la chiusura modello come questo:

    # closure example of light weight object having class state,
    #    local state, and single method
    # This is a singleton in the sense that there is a single class
    #    state (see Borg singleton pattern notebook)
    #    BUT combined with local state
    # As long as only one method is needed, this one way to do it
    # If a full class singleton object is needed with multiple 
    #    methods, best look at one of the singleton patterns
    
    def LW_Object_Factory(localState):
    
        # class state - doesn't change
        lwof_args = (1, 2, 3)
        lwof_kwargs =  {'a': 4, 'b': 5}
    
        # local instance - function object - unique per
        # instantiation sharing class state
        def theObj(doc, x):
            print doc, 'instance:'
            print '\tinstance class state:\n\t\targs -', \
                  lwof_args, ' kwargs -', lwof_kwargs
            print '\tinstance locals().items():'
            for i in locals().items():
                print '\t\t', i
            print '\tinstance argument x:\n\t\t', '"{}"'.format(x)
            print '\tinstance local state theObj.foo:\n\t\t',\
                  '"{}"'.format(theObj.foo)
            print ''
    
        # setting local state from argument
        theObj.foo = localState
    
        return(theObj)
    
    lwo1 = LW_Object_Factory('foo in local state for first')
    lwo2 = LW_Object_Factory('foo in local state for second')
    
    # prove each instance is unique while sharing class state
    print 'lwo1 {} distinct instance from lwo2\n'\
          .format(id(lwo1) <> id(lwo2) and "IS" or "IS NOT")
    
    # run them
    lwo1('lwo1', 'argument lwo1') 
    lwo2('lwo2', 'argument lwo2')
  13. 0

    Qui è una strategia che è probabilmente peggiore del func_defaults idea, ma è interessante comunque. E ‘ sporca, ma non riesco a pensare a nulla praticamente di sbagliato con esso.

    Siamo in grado di implementare una funzione che può riferirsi a se stesso come una classe con un solo __new__ metodo (il metodo che normalmente crea un nuovo oggetto della classe).

    class new:
        """Returns True the first time an argument is passed, else False."""
        seen = set()
        def __new__(cls, x):
            old = x in cls.seen
            cls.seen.add(x)
            return not old
    
    def main():
        print(new(1))  # True
        print(new(2))  # True
        print(new(2))  # false
        is_new = new
        print(is_new(1))  # False

    Forse per questo motivo potrebbe essere utile per una funzione di registrazione…

    class log_once:
        """Log a message if it has not already been logged.
    
        Args:
            msg: message to be logged
            printer: function to log the message
            id_: the identifier of the msg determines whether the msg
              has already been logged. Defaults to the msg itself.
    
        This is useful to log a condition that occurs many times in a single
        execution. It may be relevant that the condition was true once, but
        you did not need to know that it was true 10000 times, nor do you
        desire evidence to that effect to fill your terminal screen.
        """
        seen = set()
        def __new__(cls, msg, printer=print, id_=None):
            id_ = id_ or msg
            if id_ not in cls.seen:
                cls.seen.add(id_)
                printer(id_)
    
    
    if __name__ == '__main__':
        log_once(1)
        log_once(1)
        log_once(2)
  14. 0

    Solo definire la funzione all’interno di una chiusura:

    def generate_f():
        def f():
            return f.x
        return f
    
    f = generate_f()
    
    f.x = 314
    g = f
    
    del f
    print g()
    # => 314
  15. 0

    Questo mi piace un sacco.

    from functools import update_wrapper
    
    def dictAsGlobals(f):
        nf = type(f)(f.__code__, f.__dict__, f.__name__, f.__defaults__, f.__closure__)
        try: nf.__kwdefaults__ = f.__kwdefaults__
        except AttributeError: pass
        nf.__dict__ = f.__dict__
        nf.__builtins__ = f.__globals__["__builtins__"]
        return update_wrapper(nf, f)
    
    @dictAsGlobals
    def f():
        global timesCalled
        timesCalled += 1
        print(len.__doc__.split("\n")[0])
        return factor0 * factor1
    
    vars(f).update(timesCalled = 0, factor0 = 3, factor1 = 2)
    
    print(f())
    print(f())
    print(f.timesCalled)

Lascia un commento