I'm trying to set methods of a class programmatically by calling setattr
in a loop, but the reference I pass to the function that is called by this method defaults back to its last value, instead of what was passed at the time of the setattr
call. Curiously, I'm also setting the __doc__
attribute and this assignment actually works as expected:
class Foo2:
def do_this(self, pass_this: str):
print(pass_this)
class Foo:
def __init__(self):
self.reference = "ti-hihi"
self.foo2 = Foo2()
for (method_name, pass_this) in [("bar", "passed-for-bar"), ("bar2", "passed-for-bar2")]:
my_doc = f"""my_custom_docstring: {pass_this}"""
def some_func():
self.foo2.do_this(pass_this=pass_this)
some_func.__doc__ = my_doc
setattr(self, method_name, some_func)
if __name__ == '__main__':
f = Foo()
f.bar() # prints "pass-for-bar2" instead of "pass-for-bar"
f.bar.__doc__ # prints "pass-for-bar" as expected
I already tried a few things but couldn't figure it out.
Things I tried:
lambda -- my best bet, tbh
def some_func(reference):
self.foo2.do_this(pass_this=reference)
some_func.__doc__ = my_doc
setattr(self, method_name, lambda: some_func(pass_this))
deepcopy
import copy
def some_func():
self.foo2.do_this(pass_this=copy.deepcopy(pass_this))
some_func.__doc__ = my_doc
setattr(self, method_name, some_func)
another deepcopy variant which feels dangerous if I think about the place I want to put this:
import copy
def some_func():
self.foo2.do_this(pass_this=pass_this)
some_func.__doc__ = my_doc
setattr(self, method_name, copy.deepcopy(some_func))
... and a few combinations of those but I'm missing some crucial piece.
CodePudding user response:
Methods are class attributes, so some_func
needs to be attached to type(self)
, not self
itself.
class Foo:
def __init__(self):
self.reference = "ti-hihi"
self.foo2 = Foo2()
for (method_name, pass_this) in [("bar", "passed-for-bar"), ("bar2", "passed-for-bar2")]:
my_doc = f"""my_custom_docstring: {pass_this}"""
def some_func(self):
self.foo2.do_this(pass_this=pass_this)
some_func.__doc__ = my_doc
setattr(type(self), method_name, some_func)
As such, the __init_
method is not really the proper place to do this, as you'll be repeatedly attaching the (effectively same) methods to the class every time you instantiate the class. A class decorator would be more appropriate:
def add_methods(cls):
for (method_name, pass_this) in [("bar", "passed-for-bar"), ("bar2", "passed-for-bar2")]:
my_doc = ...
def some_func(self):
self.foo2.do_this(pass_this=pass_this)
some_func.__doc__ = my_doc
setattr(cls, method_name, some_func)
return cls
@add_methods
class Foo:
def __init__(self):
self.reference = "ti-hihi"
self.foo2 = Foo2()
add_methods
doesn't really require any special information about Foo
. Indeed, add_methods
can be applied to any class that
- Defines a
foo2
attribute for its instances, and foo2
has a type that provides an appropriatedo_this
method.
Even that assumption can be made more explicit by defining a suitable base class.
def FooBase:
def __init__(self):
self.foo2 = Foo2()
@add_methods
def Foo(FooBase):
pass
Now you can simply state that add_methods
is designed to work with subclass of FooBase
.
CodePudding user response:
Thanks to @AndrewAllaire for this tip, using functools.partial
solved it for me
some_func = partial(self.foo2.do_this, pass_this=pass_this)