def a(x, y=1):
print("I am from function a")
a.name = "john"
a.age = 30
#a()
print(a.name)
#['__annotations__', '__builtins__', '__call__', '__class__', '__closure__', '__code__', '__defaults__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__get__', '__getattribute__', '__getstate__', '__globals__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__kwdefaults__', '__le__', '__lt__', '__module__', '__name__', '__ne__', '__new__', '__qualname__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'age', 'name']
print(dir(a))
#.__name__ -> keep original function as the original function will be replaced
print(a.__name__)
b = a
print(b.__name__) # .__name__ <- check function name
print(a.__defaults__)
print(a.__code__) #.__code__ <- check address in RAM
print(a.__code__,hex(id(a))) #.__code__ <- check address in RAM
print(a.__code__.co_varnames)
print(a.__class__) #.__class__ <- check datatype; e.g. function, int, list
# press ctrl+/ <- all comment
a = 1
print(a.__class__)
print(a.__add__(3)) # a + 1
print(a.__le__(2)) # a < 2