web-dev-qa-db-fra.com

Comment une fonction peut-elle accéder à ses propres attributs?

Est-il possible d'accéder aux attributs d'objet de fonction python à partir de la portée de la fonction?

par exemple. ayons

def f():
    return SOMETHING

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

maintenant, que doit faire quelque chose, si nous voulons avoir le contenu de l'attribut _x "foo" retourné? si c'est même possible (simplement)

merci

METTRE À JOUR:

j'aimerais aussi le travail suivant:

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

MISE À JOUR 2:

L'affirmation qu'il n'est pas possible (si c'est le cas) et pourquoi, est plus satisfaisante que de fournir un moyen de le simuler, par exemple. avec un objet différent d'une fonction

38
mykhal

Solution

Faites de l’un des arguments par défaut de la fonction une référence à la fonction elle-même.

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

Exemple d'utilisation:

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

Explication

L’affiche originale voulait une solution qui ne nécessite pas de recherche de nom global. La solution simple

def f():
    return f.x

effectue une recherche de la variable globale f sur chaque appel, ce qui ne répond pas aux exigences. Si f est supprimé, la fonction échoue. La proposition plus complexe inspect échoue de la même manière.

Ce que nous voulons, c'est exécuter early binding et stocker la référence liée dans l'objet même. Ce qui suit est conceptuellement ce que nous faisons:

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

self est une variable locale. Par conséquent, aucune recherche globale n'est effectuée. Cependant, nous ne pouvons pas écrire le code tel quel, car f n'est pas encore défini lorsque nous essayons de lui lier la valeur par défaut de self. Au lieu de cela, nous définissons la valeur par défaut après que f soit défini.

Décorateur

Voici un simple décorateur à faire cela pour vous. Notez que l'argument self doit venir en dernier, contrairement aux méthodes, où self vient en premier. Cela signifie également que vous devez donner une valeur par défaut si l'un de vos autres arguments prend une valeur par défaut.

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)

Exemple:

>>> foo.subject = 'Fred'
>>> bar = foo
>>> del foo
>>> bar('runs')
'Fred runs swiftly'
46
Mark Lodato

Vous pouvez simplement utiliser une classe pour le faire

>>> class F(object):
...     def __call__(self, *args, **kw):
...         return self._x
... 
>>> f=F()
>>> f._x = "foo"
>>> f()
'foo'
>>> g=f
>>> del f
>>> g()
'foo'
24
John La Rooy

Eh bien, regardons quelle fonction est:

>>> 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! Donc, l'attribut a été ajouté à l'objet fonction mais il ne le verra pas car il recherche globalement x.

Nous pouvons essayer de saisir le cadre de l'exécution de la fonction et de regarder ce qu'il y a (essentiellement ce que Anthony Kong a suggéré mais sans module inspect):

>>> 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! Alors peut-être que nous pouvons obtenir le nom de la fonction à partir du nom du bloc de code et ensuite rechercher l’attribut de manière détournée? Assez sur:

>>> 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

C'est génial! Mais cela résoudrait-il le renommage et la suppression de la fonction originale?

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

Ah, très douteux. L'objet de fonction et son objet de code insistent toujours pour qu'ils s'appellent foo. Effectivement, voici où ça casse:

>>> 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! Donc, en général, cela ne peut pas être fait par introspection via les cadres d'exécution. Le problème semble être qu’il existe une différence entre objet fonction et objet code - les objets code sont ce qui est exécuté et n’est qu’un attribut func_code de la fonction objet et n’a donc pas accès func_dict attribut, où notre attribut x est:

>>> 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}

Il y a bien sûr d'autres astuces que vous pouvez faire pour que cela paraisse comme une fonction - en particulier l'astuce avec la définition de classe ... mais ce n'est pas une fonction en soi. Tout dépend de ce que vous devez vraiment faire avec ça.

15
Nas Banov

Pour résoudre ce problème, vous pouvez utiliser une fonction d'usine pour corriger votre étendue:

def factory():
    def inner():
        print inner.x
    return inner


>>> foo=factory()
>>> foo.x=11
>>> foo()
11
>>> bar = foo
>>> del foo
>>> bar()
11
8
AntiEgo

Voici un décorateur qui injecte current_fun dans les fonctions globales avant d'exécuter la fonction. C'est un peu le bidouillage, mais aussi assez 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

Voici un exemple d'utilisation

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
3
Geoff Reedy

Je doute que ce soit le meilleur moyen d'y parvenir, mais vous pouvez accéder aux attributs en utilisant le nom de la méthode dans la méthode:

>>> 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
3
Mark Rushakoff

La réponse est plutôt simple. Il suffit d'utiliser le nom de fait recherché au moment de l'exécution, pas lors de la compilation:

def f():
    return f._x

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

Ceci utilise une approche peu sophistiquée, mais c'est peut-être la plus correcte à ce jour, étant donné que cela fonctionne également avec l'appel g(). Cela fonctionne parce qu’il s’appuie sur l’inspection du bytecode effectuée par le module dis , en tant que raccourci.

Cela semble plus compliqué que c’est en partie parce que l’appel dis.disassemble() s’imprime sur stdout; je le redirige donc vers un StringIO. J'utilise disassemble() pour sa fonctionnalité de mise en surbrillance de la dernière instruction (ajoutez une ligne print text pour voir à quoi elle ressemble) et cela facilite la saisie du LOAD_NAME précédent et de la variable utilisée.

Il serait possible d’utiliser une bibliothèque d’inspection de bytecode plus propre sans utiliser le module dis, mais cela prouve que c’est possible. Cette approche n’est peut-être pas la plus solide, mais peut-être que cela fonctionnera dans la plupart des cas. Je n’ai pas passé assez de temps à fouiller dans les éléments internes ou les bytecodes de Python pour savoir si la plupart des bytecodes CALL_FUNCTION sont précédés immédiatement d’instructions que l’astuce regex choisirait.

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()

Cela génère la sortie suivante:

call f(): foo
call g(): foo
1
Peter Hansen

Si vous voulez qu'il soit totalement indépendant du nom de la fonction, vous avez besoin d'un peu de magie du cadre. Par exemple:

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() 
1
Anthony Kong

J'aime beaucoup ça.

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)
0
flobbie

Voici une stratégie qui est probablement pire que l'idée func_defaults, mais qui est néanmoins intéressante. C'est hacky mais je ne peux penser à quoi que ce soit qui cloche.

Nous pouvons implémenter une fonction qui peut se désigner elle-même comme une classe avec une seule méthode __new__ (la méthode qui crée normalement un nouvel objet de cette 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

Peut-être que ce modèle pourrait être utile pour une fonction de journalisation ...

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 __== '__main__':
    log_once(1)
    log_once(1)
    log_once(2)
0
fredcallaway

Il suffit de définir votre fonction dans une fermeture:

def generate_f():
    def f():
        return f.x
    return f

f = generate_f()

f.x = 314
g = f

del f
print g()
# => 314
0
Maciej Satkiewicz

Pourquoi ne pas utiliser une classe au lieu d’une fonction et abuser de la méthode __new__ pour rendre la classe appelable en tant que fonction? Puisque la méthode __new__ obtient le nom de la classe en tant que premier paramètre, elle peut accéder à tous les attributs de la classe.

comme dans

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

cela fonctionne comme dans

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

alors vous pouvez faire

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

Le problème est que même si l'objet se comporte comme une fonction, ce n'est pas le cas. Par conséquent, les IDE ne parviennent pas à vous fournir la signature.

0
user2340231

Pour ce faire, vous pouvez également définir la fonction dans une autre fonction et demander à la fonction externe de renvoyer la fonction interne. La fonction interne peut alors accéder à elle-même via une fermeture. Voici un exemple simple:

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

Ensuite:

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

Si une seule méthode est nécessaire mais que vous souhaitez une classe légère avec un état de classe partagé et un état d'instance individuel, vous pouvez essayer le modèle de fermeture comme suit:

# 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')
0
upandacross