2

This has been previously asked on Stack Overflow, but none of the answers seem to address exactly what I need to do. In my case, I want these dynamically-added properties to be a shortcut to store and read values from a database, so unfortunately it's not as easy as in this answer (where a lambda function was used) or this one (where values where stored in a dictionary): I must call other methods of the class.

This is my attempt:

import operator

class Foo(object):

    def get_value(self, name):
        # read and return value from database
        return -1

    def set_value(self, name, value):
        # store value in database
        pass

def add_metadata_property(name):
    getter = operator.methodcaller('get_value', name)
    setter = operator.methodcaller('set_value', name) # gets value at runtime
    setattr(Foo, name, property(getter, setter))

add_metadata_property('spam')
f = Foo()
f.spam # works!
f.spam = 2

The last line, however, raises:

Traceback (most recent call last):
File "<stdin>", line 27, in <module>
TypeError: methodcaller expected 1 arguments, got 2

Any ideas on how to achieve this?

Carl Veazey
  • 18,392
  • 8
  • 66
  • 81
plok
  • 705
  • 7
  • 30
  • 2
    Have you tried using the lambda answer, providing function objects instead of lambdas? – loopbackbee Jul 01 '14 at 12:15
  • 1
    You might be interested in this answer http://stackoverflow.com/questions/1224962/create-properties-using-lambda-getter-and-setter – Germano Jul 01 '14 at 12:37

2 Answers2

4

I don't know why you use operator.methodcaller here. When you call f.spam=2, it will invoke setter. setter = operator.methodcaller('set_value', name) means setter(r) = r.set_value(name). Make no sense in your case.

I suggest you write this way, using @classmethod:

class Foo(object):

    @classmethod
    def get_value(self, name):
        # read and return value from database
        return -1
    @classmethod
    def set_value(self,  name, value):
        # store value in database
        pass

def add_metadata_property(name):
    setattr(Foo, name, property(Foo.get_value, Foo.set_value))

add_metadata_property('spam')
f = Foo()
f.spam # works!
f.spam = 2

If this helped you, please confirm it as the answer. Thanks!

Stephen Lin
  • 4,852
  • 1
  • 13
  • 26
1

Modifying class template looks a bit odd for me. I would suggest to overload __getattr__() and __setattr__() methods in your case.

class Foo:
    def __getattr__(self, name):
        print('read and return value from database for ', name)
        return 123
    def __setattr__(self, name, value):
        print('store value', value, 'for', name, 'in database')
greatvovan
  • 2,439
  • 23
  • 43