Tkinter callback arguments

Peter Otten __peter__ at web.de
Mon Nov 2 12:09:47 EST 2009


Alf P. Steinbach wrote:

> * Peter Otten:
>> Alf P. Steinbach wrote:
>> 
>>> * Peter Otten:
>>>> Alf P. Steinbach wrote:
>>>>
>>>>>>     for x in range(0,3):
>>>>>>         Button(......, command=lambda x=x: function(x))
>>>>> An alternative reusable alternative is to create a button-with-id
>>>>> class.
>>>>>
>>>>> This is my very first Python class so I'm guessing that there are all
>>>>> sorts of issues, in particular naming conventions.
>>>> Pseudo-private attributes
>>> That means there is some way of making attributes private?
>> 
>> No, there isn't. And the name mangled __attribute is hardly ever needed.
>> Use _attribute to convey the message "If you mess with this attribute
>> you're on your own".
> 
> Thanks!
> 
> 
>>> Probably that comes across as an inane question but I ask it anyway. I
>>> haven't really started to look at Python classes. I'm guessing that by
>>> asking here I may learn things that are not obvious from the
>>> documentation.
>>>
>>>
>>>> , javaesque getter methods,
>>> What do you mean by that?
>> 
>> In Java you have a private attribute and a public getter method. In
>> Python you can just make the attribute public, i. e.
>> 
>> # bad
>> class A:
>>     def __init__(self):
>>         self._id = 42
>>    def id(self): return self._id
>> 
>> # good
>> class A:
>>     def __init__(self):
>>         self.id = 42
> 
> I think I get the gist that locally saving lines of code, and generally
> avoiding having to write empty argument list parentheses, and thereby also
> indicating in a way that one is accessing a logical data attribute, is
> considered good in Python, which goes to initial development time and the
> amount of code that one
> must scan to grok it both for definition and usage  --  is that correct?

Remember that .id does contain static data in your case. If it were a 
lengthy calculation the .id() method would be OK.

> But the trade-off is inviting modification of a supposedly fixed id, which
> goes to correctness and later fix-it time?
> 
> 
>> You can always switch to
>> 
>> class A: # assuming 3.x
>>     @property
>>     def id(self):
>>         id = arbitrary_code()
>>         return id
>> 
>> later.
> 
> Thanks, now I learned about @property... :-)
> 
> But when the thing has been used it's much more difficult to restrict the
> functionality (making read-only, breaking code that changes id) than to
> add functionality (making also writeable, breaking none of existing code).
> 
> So isn't "later" a bit late to make it read-only, shouldn't that be the
> initial design, and then possibly adding a setter later if id's turn out
> to not be so constant after all?

You can break existing code with changes in a library in so many ways that I 
don't think this specific problem matters much. But if you don't feel 
comfortable with writing a warning into the documentation use a getter 
function or a property.

>>> What I associate with Java getter method is mainly the "get" prefix, for
>>> Java introspection.
>>>
>>>
>>>> unidiomatic None-checks
>>> What's the idiomatic Python way for an optional thing?
>> 
>> if some_value is None: ...
> 
> Thanks!
> 
> But why is this preferred?
> 
> 
>>> In this case one alternative I see could be to get rid of the
>>> __on_tc_command method and more directly tell tkinter.Button to call the
>>> relevant function, doing the if-else choice once only in the IdButton
>>> constructor.
>>>
>>> Is that what you mean?
>>>
>>> I'm thinking more in terms of customization points when I write code.
>>>
>>> So I tend to avoid hardcoding things internally in methods, instead
>>> designing the choices out to where they're accessible to client code.
>>>
>>>
>>>> , broken naming conventions (**args),
>>> How to do this argument forwarding in modern style?
>> 
>> I meant that keyword args are traditionally named kw or kwargs,  the name
>> "args" is normally used for positional arguments:
>> 
>> def f(*args, **kw):
>>     "whatever"
> 
> Thanks!
> 
> *Note to myself*: check if there are more such conventions.
> 
> 
>>> Or is there an alternative to argument forwarding for this example?
>>>
>>>
>>>> spaces in funny places...
>>> Bah. ;-)
>>>
>>>
>>>>> And the idea of creating a reusable solution for such a small issue
>>>>> may be un-pythonic?
>>>> Screw pythonic, the signal/noise ratio is awful in any language.
>>>>  
>>>>> But just as an example, in Python 3.x,
>>>> ...for achieving less in more lines?
>>> Now, that's a good Python-independent question! :-)
>>>
>>> Re your question's the number of lines: /if/ the code is heavily reused
>>> then the number of lines doesn't matter since they're only written
>>> /once/;
>> 
>> Every time someone has to read the code he will read, hesitate, read
>> again, and then hopefully come to the conclusion that the code does
>> nothing, consider not using it, or if it is not tied into a larger
>> project removing it.
> 
> I don't understand what you mean.

Writing code is not fire and forget. It has to be debugged, tested, 
maintained, and will be read quite a few times in the process. Therefore it 
is important that you make it easy to read and understand.

> Not that it's a shiny example of code that does a lot, but it (1)
> simplifies and shortens creation of buttons with id, and (2) provides a
> nice start for adding other customizations of those buttons, and (3)
> supports searching for a button with given command id, e.g. for purpose of
> hiding or enable/disable.
> 
> If I didn't just want to try out writing a Python class, which I've never
> done before so it appeared interesting, I'd probably just skipped points 2
> and 3 and written the same functionality as a factory function, like
> 
> 
> <code>
> import tkinter
> 
> def id_button( owner_widget, id, command = None, **kwargs ):
>      def tk_command( an_id = id, a_command = command ):
>          if a_command is not None: a_command( id )
>      return tkinter.Button( owner_widget, kwargs, command = tk_command )
> 
> def on_button_click( id ):
>      print( "Button " + str( id ) + " clicked!" )
> 
> window = tkinter.Tk()
> 
> n_buttons = 3
> for x in range( 1, n_buttons + 1 ):
>      id_button(
>          window, id = x, text = "Button " + str( x ), command =
>          on_button_click ).pack()
> 
> window.mainloop()
> </code>

I prefer that one, though without extra spaces in funny places.
 
> but once you have the class, for whatever reason, it would be silly not to
> use it, especially since it provides points 2 and 3 which the function
> doesn't.
> 
> By the way, I as yet know next to *nothing* about binding of variable
> references within a function such as tk_command above. Probably I've done
> Unnecessary Things(TM) above?

Nothing too obvious; you could avoid the noop tk_command, and maybe provide 
the button instead of the id (all code untested):

def make_button(owner, id, command=None, **kwargs):
    button = tkinter.Button(owner, kwargs)
    button.id = id
    if command is not None:
        button["command"] = functools.partial(command, button)
    button.pack()

def button_click(button):
    print(button["text"], "clicked!")

>>> the net effect can even be to reduce the total number of lines, or at
>>> least the number of apparent function points (or whatever size metric).
>>> That's part of what "reusable" means. For example, if the OP used the
>>> code then he or she didn't type them lines, but just copy/paste'd them,
>>> less work than typing in a lambda definition in every button creation,
>>> and more clear code at every creation.
>> 
>> But most of your code does *nothing*.
> 
> See above, points 2 and 3. Most of that class has to do with 2,
> customization ability.

Couldn't

class IdButton(tkinter.Button):
    def __init__(self, id, **kw):
        self.id = id
        tkinter.Button.__init__(self, **kw)

be customised as easily?

>>> Re your question's what (the) reusability achieves.
>>>
>>> First, when you and others have used such a thing in a number of places
>>> then you gain confidence in correctness. For example, you don't wonder
>>> whether Python's str type is correct, and when you test your program you
>>> don't test the str type implementation. Since it's been used so much you
>>> know that it (mainly) is correct, and that any remaining bug in there
>>> can't be all that serious, because if it was then it would've surfaced
>>> in earlier use of the type.
>> 
>> The theory may be OK, but in practice it doesn't always work out.
>> Example: Why do you introduce button.id_string() instead of
>> str(button.id)?
> 
> Because the string representation of an id then /can/ be customized
> independently of the id. For example, id's might be integers but for
> string representation you might want symbolic action names (e.g., you
> might have two or more buttons with same title but different actions, so
> that title would be ungood to identify button). And for another example,
> when debugging or testing you might want the string represention of an id
> to provide more information about the button and/or its context, and then
> id_string provides a single
> central customization point  --  provided it's used, of course. <g>

And what about the IdEntry class? To me it would make more sense to 
customize the type of the .id value.

>> The
>> programmer will hesitate, wonder whether to use button.id() or
>> button.id_string(), how the two may interconnect...
> 
> Hm, see immediately above.
> 
> It's perhaps a different way of thinking?
> 
> 
>> It feels more like a hoop to jump through than a helpful service
>> providing tried an tested code.
> 
> Yes.
> 
> It's the old "every computer science problem can be solved by adding an
> extra layer of indirection".
> 
> Sometimes it's nice when you can do that centrally. Retrofitting the
> indirection to existing client code can be hard.

Again, just-in-case indirections come at a cost. You are probably right 
about the way of thinking.

>>> This advantage of confidence in correctness can be realized even without
>>> heavy reuse, because the encapsulation that's necessary for reuse, here
>>> having the code in a class, also makes it possible with more centralized
>>> testing.
>> 
>> Was this sentence/paragraph produced by http://pdos.csail.mit.edu/scigen/
>> ?
> 
> No. :-)  But you're right that testing isn't that much of an issue for
> that class. If that's what you meant.

I read it twice but didn't understand it. You probably misplaced a word, but 
I can't figure out which one.

>>> A centralized, encapsulated piece of code can be tested to death and
>>> deemed correct (enough) and frozen, while the application of a code
>>> pattern in umpteen places in umpteen programs, generally can't.
>> 
>> I'd like to see a good test suite for your IdButton class, especially how
>> it copes with the design decision that you can override the on_clicked()
>> stub, or provide a command function, or both.
> 
> The design is that for any given IdButton there's a single point of
> responsibility for the click action, namely either a button creation code
> supplies that action, or it relies on the action defined in the class.
> 
> I.e. again customization ability, that the button creation code can
> /override/ the class provided action, per button, without getting into
> general overriding of class methods, just by defining a nice little lambda
> inline in the call.
> 
> But as I learn more Python I may perhaps find that overriding class
> methods can
> also be done that conveniently  --  I don't know, keep in mind I'm a
> Python newbie, and doing Very Much Else than studying Python. :-)
> 
> 
>>> Second, when you do find a bug, or need more functionality, or whatever,
>>> there's just /one place/ to fix/extend, whatever, instead of updating
>>> umpteen places in umpteen programs, and trying to be sure that you've
>>> covered all instances and done the right thing every place regardless of
>>> local variations (which is pretty hopeless in general, but my experience
>>> with that kind of badness has mostly been with C, not Python). More
>>> technically, it's reduced redundancy, in this case avoiding redundant
>>> application of a code pattern everywhere one needs a button with id (if
>>> that happens often). Reduced redundancy = good.
>> 
>> I agree with that maxim. Incidentally I have just found a nice example of
>> redundancy for you:
>> 
>>>>>      def __on_tk_command( self ):
>>>>>          if self.__specified_command != None:
>>>>>              self.__specified_command( self )
>>>>>          else:
>>>>>              self.on_clicked()
>> 
> 
> Uh, could you expand on how that's redundant and how to make it less so?

How about

class IdButton(tkinter.Button):
    def __init__(self, owner, id, command=None, **kwargs):
         tkinter.Button.__init__(
              self, owner, kwargs, command=self.on_clicked)
         self.id = id
         self.specified_command = command

    def on_clicked(self):
         if self.specified_command is not None:
              self.specified_command(self)

Peter




More information about the Python-list mailing list