user14492304
user14492304

Reputation:

Can a function have an attribute?

I was reading about __dict__. The author of the article wrote this somewhere in his code.

def func():
    pass

func.temp = 1
print(func.temp)

and I don't understand that. Can functions have attributes? I thought it was only possible when writing classes.

i changed the question a little bit. Thank you for explaining this too....

def func():
    x=1
    def inner_func():
        pass
    

print(func.__dict__) #nothing to input

func.temp=1 #what's it ? Attribute?  
func.x=2   #Why it doesn't change the x inside the func
print()

print(func.__dict__) #Why temp and x are within dict

Upvotes: 3

Views: 294

Answers (2)

Peter Badida
Peter Badida

Reputation: 12189

In Python most of the stuff consist of a dictionary (have __dict__ attribute), therefore it is possible to (mis-)use the language even in this way.

You can modify a function, because:

def myfunc(): pass

type(myfunc)
# <class 'function'>

a function is still an instance of a function class and part of the builtins (injected, because implemented in C, also available as symtable) and in that you can find __dict__ attribute which is storing the properties.

Similarly you can create an empty class or simply use an object that contains a modifiable __dict__ and by using the "dot" you call in the background:

which then modify it, thus providing you a way to add/remove/modify attrs approximately like this:

object.__dict__["key"]
object.__dict__["key"] = value
del object.__dict__["key"]

Edit: As @MegaIng mentioned, it can be used for various purposes, one of which is functools.lru_cache() to store the cache to remove an expensive function call. (implementation here).

Edit 2: Regarding the changing of a variable within such function - that won't work, because x for you in that case is an attribute of the function stored in __dict__ dictionary. It is not a variable.

Python 3.8.5 (default, Jan 27 2021, 15:41:15) 
[GCC 9.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> def myfun(): x=1;print(x)
... 
>>> myfun()
1
>>> 
>>> dir(myfun)
['__annotations__', '__call__', '__class__', '__closure__', '__code__', '__defaults__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__get__', '__getattribute__', '__globals__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__kwdefaults__', '__le__', '__lt__', '__module__', '__name__', '__ne__', '__new__', '__qualname__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__']
>>> myfun.__globals__
{'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <class '_frozen_importlib.BuiltinImporter'>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>, 'myfun': <function myfun at 0x7fd5594a7670>}

Nothing in these sections, but there's one which contains the value you want to edit and that's __code__ i.e.:

>>> myfun.__code__
<code object myfun at 0x7fd5594afc90, file "<stdin>", line 1>

That you can disassemble - it's already been compiled to Python's "virtual" / emulated CPU's (as if you've taken CPU and its instruction set and abstracted it; converted it into a code) bytecode:

>>> import dis
>>> dis.dis(myfun.__code__)
  1           0 LOAD_CONST               1 (1)
              # here is an assignment of integer `1` into variable `x`
              # via the instruction called `STORE_FAST`
              2 STORE_FAST               0 (x)
              4 LOAD_GLOBAL              0 (print)
              6 LOAD_FAST                0 (x)
              8 CALL_FUNCTION            1
             10 POP_TOP
             12 LOAD_CONST               0 (None)
             14 RETURN_VALUE
>>> 

Now how to modify that? That's already answered in a different question :)

Upvotes: 4

pgorecki
pgorecki

Reputation: 689

In Python functions are first class objects, which means that function is an object. You can create function using standard syntax:

def foo(x):
  return x+1

or using lambdas:

bar = lambda x: x+1

In both cases foo and bar are object instances, which may have attributes. Therefore you can create new attributes like you can with regular objects.

Upvotes: 2

Related Questions