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