0

I use following class to define event:

class Event(object):
    def __init__(self):
        self.handlers = set()

    def handle(self, handler):
        self.handlers.add(handler)
        return self

    def unhandle(self, handler):
        try:
            self.handlers.remove(handler)
        except:
            raise ValueError("Handler is not handling this event, so cannot unhandle it.")
        return self

    def fire(self, *args, **kwargs):
        for handler in self.handlers:
            print(handler)
            handler(*args, **kwargs)

    def getHandlerCount(self):
        return len(self.handlers)

    __iadd__ = handle
    __isub__ = unhandle
    __call__ = fire
    __len__  = getHandlerCount

I have some model class defined like this:

class SomeModel(object):
    def __init__(self):
        self._foo = 0
        self.fooChanged = Event()

    @property
    def foo(self):
        return self._foo

    @foo.setter
    def foo(self, value):
        self._foo = value
        self.fooChanged(value)

Now, suppose that I want to change foo like this:

model = SomeModel()
other_model = SomeModel()

model.fooChanged += other_model.foo

model.foo = 1

After model.foo = 1, I get following error:

TypeError: 'int' object is not callable

Now, suppose that I use this code for defining model:

class SomeModel(object):
    def __init__(self):
        self._foo = 0
        self.fooChanged = Event()

    def get_foo(self):
        return self._foo

    def set_foo(self, value):
        self._foo = value
        self.fooChanged(value)

    foo = property(get_foo, set_foo)

and this code to change the value of foo:

model = SomeModel()
other_model = SomeModel()

model.fooChanged += other_model.set_foo

model.foo = 1

Second version works fine, however, it seems little un-Pythonic to me. I have to define get_foo method, which I'd like to avoid (since properties are available). Is there some other workaround here, so first version of code could run?

Note: error will depend on self._foo type. If it's None, it will return error stating that NoneType is not callable, if it's string, error will state that str object is not callable.

Fejs
  • 2,734
  • 3
  • 21
  • 40

2 Answers2

0

After a lot of digging, I found this answer to be very informative and it pushed me in the right direction.

Using this knowledge, I was able to solve this problem by using:

model.fooChanged += lambda value: type(other_model).foo.__set__(other_model, value)

or

model.fooChanged += lambda value: type(other_model).foo.fset(other_model, value)

The later line looks more Pythonic to me, since no calls for double-underscore functions are made.

Fejs
  • 2,734
  • 3
  • 21
  • 40
0

while you write model.fooChanged += other_model.foo, I guess what you actually want is its setter method, but as other_model.foo is a property object, you have to get from its class other_model.__class__.foo.fset, write as:

model.fooChanged += lambda value: other_model.__class__.foo.fset(other_model, value)

OTOH, I think your second version is pythonic to me, as:

Explicit is better than implicit.

georgexsh
  • 15,984
  • 2
  • 37
  • 62