I would like to dynamically define some methods for Python class. I googled for a while and found this. I changed the code a bit to fulfill my requirement.
Here's my codes:
class Base(object):
def add_method(self, field):
def func(self, value):
self.colors[field] = value
return self
return func
def define_method(self, *fields):
for field in fields:
setattr(self, "with_" + field, self.add_method(field))
class MyColor(Base):
def __init__(self):
self.colors = {
"black": "000",
"red": "f00",
"green": "0f0"
}
# ========== ==========
# by doing this, I assume `with_red()` and `with_green()`
# will be generated, and they're chain-able.
super(MyColor, self).define_method("red", "green")
s = MyColor()
s.with_red("111").with_green("222")
print(s.colors)
# should output: {"black": "000", "red": "111", "green": 222}
The codes will raise error:
Traceback (most recent call last):
File "main.py", line 26, in <module>
s.with_red("111").with_green("222")
TypeError: _with_field() missing 1 required positional argument: 'value'
What is wrong?
Thanks for your time!
========== Edit ==========
Sorry, I changed my original implementation on Base
class, which is as below(has a bug, which always change the last field
passed to define_method()
). @Alex's answer stands.
class Base:
def define_method(self, *fields):
for field in fields:
def _with_field(self, value):
self.colors[field] = value
return self
setattr(self, "with_" + field, _with_field)
What happens is that you set e.g. 'with_red' attribute on your MyColor instance to a local function defined in Base constructor - note that this is not a class method, just a function, and it takes 2 arguments: 'self' and 'value':
import inspect
...
s = MyColor()
print(inspect.getargspec(s.with_red))
ArgSpec(args=['self', 'value'], varargs=None, keywords=None, defaults=None)
An easy fix here would be to make this function take a single argument:
def _with_field(value):
self.colors[field] = value
return self
With this change your code produces the expected output.
Another option is to set 'with_red' attribute on the class - which makes it a method, then self is passed implicitly and you can keep _with_field
signature with two arguments.