How can a function access its own attributes?

前端 未结 16 1739
再見小時候
再見小時候 2020-11-28 07:49

is it possible to access the python function object attributes from within the function scope?

e.g. let\'s have

def f():
    return          


        
相关标签:
16条回答
  • 2020-11-28 07:49

    I doubt this is the best way to accomplish this, but you can access the attributes by using the method's name within the method:

    >>> 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
    
    0 讨论(0)
  • 2020-11-28 07:50

    The answer is rather simple. Just use the fact name is looked for at execution time, not compile time:

    def f():
        return f._x
    
    f._x = "foo"
    f()           # -> "foo"
    
    0 讨论(0)
  • 2020-11-28 07:52

    Solution

    Make one of the function's default arguments be a reference to the function itself.

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

    Example usage:

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

    Explanation

    The original poster wanted a solution that does not require a global name lookup. The simple solution

    def f():
        return f.x
    

    performs a lookup of the global variable f on each call, which does not meet the requirements. If f is deleted, then the function fails. The more complicated inspect proposal fails in the same way.

    What we want is to perform early binding and store the bound reference within the object itself. The following is conceptually what we are doing:

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

    In the above, self is a local variable, so no global lookup is performed. However, we can't write the code as-is, because f is not yet defined when we try to bind the default value of self to it. Instead, we set the default value after f is defined.

    Decorator

    Here's a simple decorator to do this for you. Note that the self argument must come last, unlike methods, where self comes first. This also means that you must give a default value if any of your other arguments take a default value.

    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)
    

    Example:

    >>> foo.subject = 'Fred'
    >>> bar = foo
    >>> del foo
    >>> bar('runs')
    'Fred runs swiftly'
    
    0 讨论(0)
  • 2020-11-28 07:54

    Well, let's look at what function is:

    >>> 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! So the attribute was added to the function object but it won't see it because it is looking for global x instead.

    We can try to grab the frame of the function execution and try to look what's there (essentially what Anthony Kong suggested but w/o inspect module):

    >>> 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! So maybe we can get the name of the function from the name of the code block and then look in round-about way for the attribute? Sure enough:

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

    That's great! But would it stand the renaming and deletion of original function?

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

    Ah, very doubtful. The function object and its code object still insist they are called foo. Sure enough, here is where it breaks:

    >>> 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! So in general it can't be done through introspection via the execution frames. The problems seems to be that there is a difference between function object and code object - code objects are what is executed and is just one attribute func_code of the function-object and as such has no access to the func_dict attribute, where our attribute x is:

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

    There is of course other chicanery you can do so that it seems as function - in particular the trick with class definition... but that is not a function per se. It all depends on what do you really need to do with that.

    0 讨论(0)
  • 2020-11-28 07:54

    Here is a strategy that is probably worse than the func_defaults idea, but is interesting nonetheless. It's hacky but I can't think of anything practically wrong with it.

    We can implement a function that can refer to itself as a class with a single __new__ method (the method that normally creates a new object of that class).

    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
    

    Perhaps this pattern could be useful for a logging function...

    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)
    
    0 讨论(0)
  • 2020-11-28 07:55

    Here's a decorator that injects current_fun into the functions globals before executing the function. It's quite the hack, but also quite effective.

    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
    

    Here's a usage example

    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
    
    0 讨论(0)
提交回复
热议问题