SuperDisk
SuperDisk

Reputation: 2840

Give a class its own `self` at instantiation time?

I've got a button class that you can instantiate like so:

engine.createElement((0, 0), Button(code=print, args=("Stuff!",)))

And when it is clicked it will print "Stuff!". However, I need the button to destroy itself whenever it is clicked. Something like this:

engine.createElement((0, 0), Button(code=engine.killElement, args=(self,)))

However, that would just kill the caller, because self refers to the caller at that moment. What I need to do is give the class its own 'self' in advance...

I thought of just making the string 'self' refer to the self variable upon click, but what if I wanted to use the string 'self' in the arguments?

What is the way to do this? Is my architecture all wrong or something?

Upvotes: 0

Views: 291

Answers (4)

martineau
martineau

Reputation: 123481

Maybe something like this would help:

class Button(object):
    def __new__(cls, *args, **kwargs):
        obj = object.__new__(cls, *args, **kwargs)
        kwargs['args'].append(obj)
        return obj

    def __init__(self, code, args):
        self.code = code
        self.args = args

    def click(self):
        return self.code, self.args

b = Button(code="engine.killElement", args=[])
print b.click()

Output:

('engine.killElement', [<__main__.Button object at 0x00B59AF0>])    

Upvotes: 0

abarnert
abarnert

Reputation: 365845

This is impossible in general.

However, if you're creating the Button class, you can pass a special sentinel value that means "yourself". For example:

class Button(object):
    yourself = 'yourself'
    def __init__(self, code, args):
        self.code = code
        self.args = [self if arg is yourself else arg for arg in args]

Then:

engine.createElement((0, 0), Button(code=engine.killElement, args=(Button.yourself,)))

Picking an appropriate sentinel can be tricky—obvious choices like None, 0, or '' may be legitimate values, and even tricky things you come up with may turn out to be useful arguments during debugging. Making yourself a class variable, or a global within the module, means that if you ever do need to redefine the sentinel, you only need to change it in one place, instead of everywhere you use it.

See http://bytes.com/topic/python/answers/555169-sentinel-values-special-cases for a brief discussion on picking an appropriate sentinel value. There's another blog out there with more information, but I haven't found it in a quick search… Anyway, here are some quick ideas:

  1. None is always the best answer if it works.
  2. Define an empty class to be the sentinel. Either the class object, or any instance of the class object, can be used.
  3. Create a global instance of the object class (object()).
  4. Define an empty function and use it (or its func_code or whatever).
  5. Ellipsis (or type(Ellipsis), which is a type named ellipsis, but that name isn't accessible) is almost always safe, because it's only used in __getitem__ and friends (and possibly in defining slice objects to pass to them).
  6. If there's a type that could not possibly be a valid value, and you've already got instances around, use one of those—e.g., the func_code member of the __init__ function.

Upvotes: 1

kindall
kindall

Reputation: 184220

You've essentially set it up so that you need a reference to an object in order to create that object, which is of course impossible. You could do something like this (a list is as good as a tuple for argument unpacking):

arglist = []
button = Button(code=engine.killElement, args=arglist)
arglist.append(button)
engine.createElement((0, 0), button)

This is inelegant, unclear, and verbose, but it'll get the reference to the instance into the instance.

You could use a sentinel value as another poster suggested. Perhaps a better suggestion would be to simply use a convention (like Python itself) that self is always passed as the first argument to the specified function and doesn't need to be specified explicitly. Then your callbacks are written to always take self, even if they don't do anything with it.

But generally you would not specify an object's behavior by passing it to that object's constructor, but through inheritance. In other words you'd subclass Button, override its onClick method or whatever, and instantiate the subclass. Having onClick know what instance it's attached to is a non-issue. So I come down on the side of yes, your architecture is a wee bit all wrong.

Upvotes: 2

David Wolever
David Wolever

Reputation: 154544

Unfortunately this is impossible — the arguments to the button's constructor are evaluated before the constructor is evaluated. You'd need to assign the button to a variable, then set the callback afterwards:

b = Button(code=engine.killElement)
b.args = (b, )

Or something similar.

Upvotes: 3

Related Questions