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