2

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) 
3
  • change setattr(self, "with_" + field, _with_field) to setattr(Base, "with_" + field, _with_field) Commented Jun 4, 2018 at 7:52
  • @user1767754, super cool! It works. Commented Jun 4, 2018 at 7:54
  • can anyone elaborate a little bit on this self, Base tihing? Commented Jun 4, 2018 at 8:12

1 Answer 1

2

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.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.