The name impossible
points to an object that represents the function. You're setting a property on this object (which python allows you to do on all objects by default):
>>> def foo():
... print("test")
...
>>> dir(foo)
['__call__', ..., 'func_closure', 'func_code', 'func_defaults', 'func_dict', 'func_doc', 'func_globals', 'func_name']
>>> foo.a = 'test'
>>> dir(foo)
['__call__', ..., 'a', ...]
Since this object implements __call__
, it can be called as a function - you can implement your own class that defines __call__
and it'll also be allowed to be called as a function:
>>> class B:
... def __call__(self, **kw):
... print(kw)
...
>>> b = B()
>>> b()
{}
>>> b(foo='bar')
{'foo': 'bar'}
If you create a new function, you can see that you haven't done anything to the definition of a function (its class), just the object that represents the original function:
>>> def bar():
... print("ost")
...
>>> dir(bar)
['__call__', ..., 'func_closure', ...]
This new object does not have a property named a
, since you haven't set one on the object bar
points to.