I have a scenario where I have objects with static methods. They are all built using an outside def build_hello()
as class variables.
def build_hello(name: str):
@staticmethod
def hello_fn():
return "hello my name is "
# Assign an attribute to the staticmethod so it can be used across all classes
hello_fn.first_name = name
print(hello_fn() + hello_fn.first_name) # This works
return hello_fn
class World:
hello_fn = build_hello("bob")
# Error, function object has no attribute "first_name"
World.hello_fn.first_name
What is happening here? I am able to access the attribute of hello_fn()
within the build_hello()
function call. but when its added to my object, that attribute no longer lists.
Also if I call dir()
on the static method. I do not see it present:
dir(World.hello_fn)
['__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__',
'__type_params__']
Method retrieving from a class, in Python, be it regular instance methods, class methods or static methods, use an underlying mechanism to actually build the method object at the time it is requested (usually with the .
operator, but also with a getattr(...)
call):
The objects that are bound to class namespaces have a __get__
method - it is this __get__
which builds the object that is retrieved and that will be called as the method - so, if it is a classmethod, the cls
is inserted as first parameter, or the self
argument for regular methods, and nothing inserted for staticmethods.
The thing is that the __get__
method for a regular function will make it behave like a common instance methods. The @classmethod
and @staticmethod
decorators have a different behavior for the __get__
method which produce the desired final effects when calling the method.
So, when you create a staticmethod
wrapping your function, it is not this object that is retrieved when you do MyClass.mymethod
- rather, it is whatever the __get__
method of @staticmethod returns - in this case, it returns the underlying function as is.
TL;DR: put your attributes in the underlying function, before wrapping it with the staticmethod
call, as that is what is returned by World.hello_fn
:
def build_hello(name: str):
def hello_fn():
return "hello my name is "
# Assign an attribute to the staticmethod so it can be used across all classes
hello_fn.first_name = name
print(hello_fn() + hello_fn.first_name) # This works
return staticmethod(hello_fn) # Just wrap the function with the staticmethod decorator here!
class World:
hello_fn = build_hello("bob")
World.hello_fn.first_name
Alternatively, you can reach the function after applying the decorator through the attribute __func__
of the staticmethod object:
def build_hello(name: str):
@staticmethod
def hello_fn():
return "hello my name is "
hello_fn.__func__.first_name = name
return hello_fn