Doug
Doug

Reputation: 35136

How do I assign a method decorator from a class property in python?

So, I know I can do this without a problem:

button = widget.libray.Button()

class X():
  @button.on_click
  def click(self, event):
    ...

Without a problem.

...but it's irritating, because now 'button' is outside the class scope; I'd like this to be a class property. How do I use the @blah decorate syntax for a class member?

I can certainly not use the decorator at all, like this:

class UiDemo(cocos.cocosnode.CocosNode):

  def on_enter(self):
    self.b = Button(text='Hello World')
    self.bt = b.event(self.bt) # <--- !!! Instead of decorator!
    vbox = VBox(elements=[self.b])
    self.dialog = Dialog(title='My Dialog', x=100, y=100, content=vbox, width=200, height=100)
    cocos.director.director.window.push_handlers(self.dialog)

  # @self.b.event doesn't work here, obviously.
  def bt(self, button):
     print('Button pressed: %s' % button)

  def on_exit(self):
    cocos.director.director.window.remove_handlers(self.dialog)

  def draw(self):
    self.dialog.on_draw()

class Client:
  def run(self):
    cocos.director.director.init()
    test = UiDemo()
    main_scene = cocos.scene.Scene(test)
    cocos.director.director.run (main_scene)

I mean, I see the problem; the method is defined on the class when the class is defined (I guess), so even if you create the widget in the init() step, it doesn't exist at the point where the class definition is parsed, and so it can't be used as a decorator at that time.

What I'm wondering is if there's some way of working around this?

Surely there's a fair number of cases where you want your decorator property to reference to an instance of something that's attached to the class?

(as you can see from my example above, this is not just a "what if" situation; this is the second time this week I've ended up using the verbose self.func = self.instance.dec(self.func) form to do this)

Any suggestions?

(...obviously, other than, use libraries that don't do that because it's dumb; that's not really a solution. :P)

Upvotes: 0

Views: 298

Answers (2)

BrenBarn
BrenBarn

Reputation: 251383

You could make the button a class attribute:

class X():
  button = widget.libray.Button()

  @button.on_click
  def click(self, event):
    # blah

This will of course cause problems if you try to instantate more than one instance of X (since they will share the same button), but that's already a problem if you have button defined globally.

You have accurately diagnosed the problem, though: decoration happens at class-definition time, but you want the methods to be associated with an instance. Ultimately, trying to force it with these sorts of tricks is likely to be confusing. The button instance that you are creating is conceptually associated with an instance of X, not the class itself. You might reasonably make a GUI class that you intend to instantiate many times (for some particular style of widget, or whatever), and in that case you'd have to give up on these tricks, because you really do need the instance at the time you want to bind the event.

If you really want to, you could devise a more elaborate scheme to handle this. For instance, you could have decorators like @onclick('button') that store the name button as an attribute on the method object. You could then have code in __init__ that iterates over the methods of the object and binds them to events on the GUI widget with the corresponding name. (That is, you create a button and store it self.button, and then __init__ links up the name of this attribute with the name passed to the decorator, and binds the event.)

However, it is probably simpler to just do the obvious thing: don't try to get fancy and use decorators to handle instance-specific behavior at the class level. Just bind the events inside the method code, as in your second example.

Upvotes: 3

shx2
shx2

Reputation: 64318

It seems to me the confusion arises since you want to decorate when there's no need to. What you really need to do is to register a callback. Beside registering it, there is not point also decorating the callback.

Just have the button as a member, as you already have, and register the callback to it.

def on_enter(self):
    self.b = Button(text='Hello World')
    b.event(self.bt) # register, don't decorate

Furthermore, you can't use a decorator (unless creating a global Button), because decorating is done when the class is defined, and the Button member is only instantiated when an object is created, thus you can't register a callback to it at that point.

Upvotes: 1

Related Questions