Mailing List Archive

IDLE mini-hack & ? (adding menu items, understanding *args, self)
Greg Ewing wrote:

> Isidor wrote:
> >
> > ##def date_time_stamp(self):
> > def date_time_stamp():
>
> I think the first version of that line was better.
> If it's a method of a class, it'll need a self
> argument. That would account for the "No arguments
> expected" exception - it's being called with one
> argument (self) but it's not expecting any.

Hello Greg -

Thanks for responding to my query. I also thought that having "self"
in there would help. I didn't think this because I knew what I was
doing, but, because like a good monkey, I see that most funcs in a
class have a "self" argument, so I figured, yup, better try it out.
Unfortunately, it didn't work. In fact, it brought up an "odd" error:


case 1: using: def date_time_stamp(self): ...generates...

>>> Exception in Tkinter callback
Traceback (innermost last):
File "C:\Program Files\Python152\Lib\lib-tk\Tkinter.py", line 764, in
__call__
return apply(self.func, args)
TypeError: too many arguments; expected 1, got 2


Why do I call it an "odd" error message? Well, because to the
untrained eye (e.g., mine) the previous error message is completely
at odds with the following message:

case 2: using def date_time_stamp(): ...generates...

>>> Exception in Tkinter callback
Traceback (innermost last):
File "C:\Program Files\Python152\Lib\lib-tk\Tkinter.py", line 764, in
__call__
return apply(self.func, args)
TypeError: no arguments expected


To me it sounds as if in case 1, when I gave an argument, it said I
gave too many while in case 2, when I didn't give an argument, it
said it didn't want any. (Kind of like life?..;)

But of course, what's wrong here is my totally ass-backwards
interpretation of the error messages. I would never have figured this
out if it hadn't been for an email sent to me personally by a kind
gent who shall go unnamed here (he is being bcc'ed (hello and
thanks!) (am i correct in assuming that a private emailer does not
want public acknowledgment?)). He wrote:

::::::::::::::::::::
You seem to have done everything right (I don't use TKInter enough to
help with the real debugging), then ignored the information in the
exception...

Traceback (innermost last):
File "C:\Program Files\Python152\Lib\lib-tk\Tkinter.py", line 764, in
__call__
return apply(self.func, args)
TypeError: no arguments expected

Which is trying to say: "Tkinter tried to call a function with too
many arguments"

So...

def date_time_stamp( *args, **namedargs)
:::::::::::::::::

Since at the time I understood nothing about the uses of *args,
**namedargs , I did not understand his clue. Thanks to some recent
posts and replies discussing these argument-passing-methods, I have a
better grip on what's happening here. So, with a little time on my
hands (yeah, right ;), I did a simple copy and paste from his
message, and voila, it worked. I was able to pass through the
function definition and into the interior of the function. He wasn't
giving me a *clue*, he was giving me the freakin' *answer*! (Again,
thanks.)

error messages, a newbie's eye-view:

So now that it works, I am looking back, trying to reinterpret the
error messages to fit with reality...not my ass-backwards version of
them.

case 1: def blah(self):
error: too many arguments; expected 1, got 2

case 2: def blah():
error: no arguments expected

It seems to me that the error message is being "spoken" by the
"receiving" function (i.e., the "blah" func). In case 1, it is set up
to handle one argument, but gets more than that and balks. In case 2,
it is set up to handle no arguments, gets some, and balks. You see, I
was thinking that the error was being "spoken" by something *after*
the function had been defined (um, I have mentioned several times
that I'm new at this, right?;)...so that whatever was being invoked
after the def statement was balking in a very strange way...telling
me that no arguments were expected when I didn't *give* it any
arguments. Maybe this would have been clearer to me if the error
message in case 2 were (similar to the error message in case 1): "too
many arguments; expected 0, got 2", then I would have realized that
both errors were variations on a theme rather than opposites. Of
course, this new interpretation could be totally wrong too. If that
is the case, I hope someone will point it out to me.

One thing I don't really understand here is why it is necessary to
raise an exception when an argument is passed to a function that
isn't prepared to handle it. If the argument is needed *inside* the
function, I could understand an exception being raised then (e.g.,
"hey, where's my argument"), but in my case i'm doing nothing (or at
least I *think* I'm doing nothing) with those arguments. Why do I
need to make a "landing strip" for them? To add more metaphors to the
blender, something about this reminds me of the trick of putting a
potato in a car exhaust pipe. If the exhaust has nowhere to go, it
backs up until nothing can move in or out of cylinders and the motor
dies (or at least it did in Beverly Hills Cop...or whatever that
Eddie Murphy movie was). So, maybe by allowing the unused arguments
to pass through my function, everything flows nicely and nothing gets
stopped up. Hmm, strange. Am I on basically the right track here with
this idea?

Understanding the "self" ("the toughest thing of all" - stated in a
philosophy of science seminar on reflexivity i once attended):

So, anyway, what did I have inside that function? Well, basically
just some test code to see if I was getting that far:

print "cheese"

It worked, but poorly. It pushed whatever text was already on the
line (at the command prompt) on to the next line and jumped to the
front of the line. Hmm. I suspected that using "print" was like using
a sledgehammer where a scalpel was needed. Scanning through the other
functions in EditorWindow.py, I saw a lot of things being done to/by
self.text. In the init constructor, I saw that "self.text =
Text(...lots of arguments that would make sense for a text widget
type thing...)", and I realized, aha, "text" must be the arena in
which we are actually playing when using IDLE. So, then I wondered,
what "methods" does "text" provide me to insert text? I looked at the
Tkinter text man page and found a section on insert that looked
reasonably like what I wanted to do, but it didn't look anything like
what I was seeing in EditorWindow.py (to me, at least). So then I id
a "Find in Files..." (oh my, what a wonderful feature!) for
text.insert in the idle files and found a nice example in line 51 of
Autoexpand.py. Something like:

self.text.insert("insert", newwords)

My guess is that insert is a method of text, and "insert" (first
argument) is the location of the insertion point and will be the
location where the second argument gets injected into "text". So,
just for kicks, I copied that line into my newly-functioning def,
changed "newwords" to "timestr" (which contained the date-time string
I wanted), and tried it out. (I suspected it wasn't going to work,
but I wanted to try anyway.)

case 3:

def date_time_stamp( *args, **namedargs):
timestr = ...stuff...
self.text.insert("insert", timestr)

>>> Exception in Tkinter callback
Traceback (innermost last):
File "C:\Program Files\Python152\Lib\lib-tk\Tkinter.py", line 764,
in __call__
return apply(self.func, args)
File "C:\PROGRA~1\PYTHON15\TOOLS\IDLE\EditorWindow.py", line 586,
in date_time_stamp
self.text.insert("insert", timestr)
NameError: self

I was curious: I wanted to know whether a function inside a class
could automatically see the methods and attributes of that class.
Well, this error message makes me think that the answer is no. Self
has to be "created" in the arguments of the function. Ok, so now I'm
starting to understand *why* everyone always says you have to have
"self" as the first argument. But, I wondered, maybe "self" doesn't
work here because I haven't created it within the context of this
func, but "text" would be recognized because it is created in the
class that houses the func. So I tried the following (hey, give me a
break, I'm a newbie! ;) :

case 4:
def date_time_stamp( *args, **namedargs):
timestr = ...stuff...
text.insert("insert", timestr)


>>> Exception in Tkinter callback
Traceback (innermost last):
File "C:\Program Files\Python152\Lib\lib-tk\Tkinter.py", line 764,
in __call__
return apply(self.func, args)
File "C:\PROGRA~1\PYTHON15\TOOLS\IDLE\EditorWindow.py", line 587,
in date_time_stamp
text.insert("insert", timestr)
NameError: text

Nope, as I suspected, that didn't work either. If I am correct, then,
these results point to the interesting fact that a function knows
nothing about its context that it isn't told, and that the first
argument (conventionally "self") of an argument within a class is
used by the class to pass itself and its context to the function, and
is used by the func to "catch" everything the class has to offer. So
I did what I should have done all along:

case 5:

def date_time_stamp(self, *args, **namedargs):
timestr = ...stuff...
self.text.insert("insert", timestr)

result upon selecting the menu/or hitting the right keys:

>>> 19990726.1735

Yay. It works. Just to see if I understood what was going on here, I
tried the following (pushing my luck, I know!):

case 6:
def date_time_stamp(blah, *args, **namedargs):
timestr = ...stuff...
blah.text.insert("insert", timestr)

Yup, that worked too. So now I understand what they mean when they
say that calling the first argument "self" is just a convention.
*Having* the argument there isn't the convention, in fact, in most
cases it's *necessary*, but it can be *called* almost anything. "Aha,
the grasshopper is beginning to understand." ;^)

"The self is just an empty vessle into which...."
"The self must be empty before...."

no no no no... ;)


Ok, seriously now. Here is something I would like to know. Is it
possible for function y in a class to have access to a variable
defined in function x of that same class (without having to
explicitly pass the variable with a return statement)?:

def class something:

1 ...stuff, including __init__...

2 def funcx(self, *args, **namedargs):
3 zip = "googoo"

4 def funcy(self, *args, **namedargs):
5 print zip

Something causes me to suspect that this won't work. What would make
it work? Can I attach zip to "self" somehow in funcx, e.g., line 3:
'self.zip = "googoo"'? If I then change line 5 to 'print self.zip',
will this work? Will it only work if self.zip is defined in the
__init__ func? Until I trust myself to create a working class, I
don't think I can reliably test this out myself. In the meantime, I
will welcome any insights anyone is willing to provide (including
"read chap x, sect y of tutorial or language reference").

Ok, I hope this rambling troubleshooting log helps some other newbie
understand what the hell is going on with passing arguments to
functions, including class "context".

Thank to Greg and my friendly e-mailer for the helpful advice! With
this post and my original one, it should be possible to make a "date-
time stamp" (or any other "insert something here") menu item. If you
can't find the original post and want to do this, email me, and I'll
put it together more coherently for you.

Thanks again, and take care.

Isidor
IDLE mini-hack & ? (adding menu items, understanding *args, self) [ In reply to ]
Isidor <rodisi01@my-deja.com> wrote:

[monstrous snip]
> One thing I don't really understand here is why it is necessary to
> raise an exception when an argument is passed to a function that
> isn't prepared to handle it. If the argument is needed *inside* the
> function, I could understand an exception being raised then (e.g.,
> "hey, where's my argument"), but in my case i'm doing nothing (or at
> least I *think* I'm doing nothing) with those arguments. Why do I
> need to make a "landing strip" for them?

def myfunction(foo, bar):
print foo
print bar

myfunction(1, 2, 3)

You send '3' to myfunction. If myfunction now just ignored '3' silently,
you'd be endlessly confused why your program doesn't work, especially if
you didn't write myfunction yourself. But luckily, you get an exception
stating that you do something wrong. Python just tells you when you do
something that *can't* be right.

Unrelated to this, a common confusion with Python occurs with methods
(as opposed to functions):

class Myclass:
def mymethod(self, foo, bar):
print foo
print bar

myobj = Myclass()
myobj.mymethod(1, 2, 3)

This is going to give an exception that too many arguments were passed.
This is because according to Python, *4* arguments were passed instead of
3.

If you call a method on an object, that object is always passed as the
first argument to that object, implicitly (usually it's called 'self').
Inside the method you can then use self to do various things to the
object (for instance, call other methods on it, or set attributes).

[snip]
> So, maybe by allowing the unused arguments
> to pass through my function, everything flows nicely and nothing gets
> stopped up. Hmm, strange. Am I on basically the right track here with
> this idea?
[big snip]

Actually, you can make functions that allow extra arguments -- but only when
you explicitly tell Python:

(file: test.py)
def foo(a, b, *rest):
print a
print b
print rest

>>> from test import foo
>>> foo(1, 2)
1
2
()
>>> foo(1, 2, 3)
1
2
(3,)
>>> foo(1, 2, 3, 4, 5)
1
2
(3, 4, 5)

You can also do this (this only works with keyword arguments):

(file: test2.py)
def foo(a, b, **rest):
print a
print b
print rest

>>> from test2 import foo
>>> foo(1, 2)
1
2
{}
>>> foo(1, 2, 3)
Traceback (innermost last):
File "<stdin>", line 1, in ?
TypeError: too many arguments; expected 2, got 3
>>> foo(1, 2, something=3)
1
2
{'something': 3}
>>> foo(1, 2, something=3, other=4)
1
2
{'other': 4, 'something': 3}

So, as long as you tell Python what you want, Python allows lots of
flexibility. But if you don't tell Python that you want it, Python assumes
you don't want it -- it gives an error, which is good, as it probably
*was* an error.

Some languages take this further and also require you to add the *types* of
the arguments you espect in your function (they are strongly typed). So, if
you have a function argument 'foo', you need to specify in your function
definition if 'foo' is a string, or a number, or an object made from some
class, etc. This can also be used to prevent errors (and compile into
faster code); the language compiler can detect if you do something that's
not intended (pass a number where you specified only a string could go).

But dynamic typing like Python has other advantages -- you
have more flexibility and you don't get caught up messing with argument
types all the time (it can cost lots of time early in development, and it's
harder to change something).

The last discussion is just for random information and does not have much
to do with Python. :)

I hope my explanation was useful.

Regards,

Martijn
IDLE mini-hack & ? (adding menu items, understanding *args, self) [ In reply to ]
Hi again :)

Hm, I replied to Isidor explaining various ways to call arguments, but
I hadn't read his post well enough -- he figured that out for himself.

I'll just blame my error on the monstrous length of Isidor's post. :)

Regards,

Martijn
IDLE mini-hack & ? (adding menu items, understanding *args, self) [ In reply to ]
Isidor wrote:
>
> One thing I don't really understand here is why it is necessary to
> raise an exception when an argument is passed to a function that
> isn't prepared to handle it.

It isn't strictly necessary, but it helps to catch
errors earlier. Python is so dynamic that there aren't
many opportunities to catch errors until the last
moment, but when there is one, Python takes it.
It's a language design decision, and in my opinion,
it's a good one. The vast majority of the time, passing
the wrong number of arguments to a function is an error.
For the rare cases where it isn't, Python lets you
explicitly say that it's okay.

> My guess is that insert is a method of text, and "insert" (first
> argument) is the location of the insertion point

That's right. To add text to the end of a text widget,
you can use "end" instead of "insert", e.g.

self.text.insert("end", "something to append")

> a function knows nothing about its context that it isn't told

There are only three sets of names that can be
directly referred to in a function:

(1) Its parameters and local variables
(2) Names defined in the top level of the module where the
function was defined
(3) Names of built-in Python functions

Note that this does *not* include the parameters and
locals of any *other* function, even if you nest one
function definition inside another. (This surprises people
who are used to languages like Pascal or Scheme.)

In the case of a method, it also doesn't include
any attributes of any class or instance - hence the
need for the "self" argument.

> 2 def funcx(self, *args, **namedargs):
> 3 zip = "googoo"
>
> 4 def funcy(self, *args, **namedargs):
> 5 print zip
>
> Something causes me to suspect that this won't work.

You're right, it won't work - whatever you mean
by "work" (it's not clear what you want it to do).

> Can I attach zip to "self" somehow in funcx, e.g., line 3:
> 'self.zip = "googoo"'?

Yes, you can certainly do that. It's the usual way
of assigning to an instance variable in Python.

> Will it only work if self.zip is defined in the
> __init__ func?

No, there's no need to do that. You can add a new
attribute to any instance of any class at any time
just by assigning to it. There is no notion of
"declaring" instance variables.

> "read chap x, sect y of tutorial or language reference").

You might like to read through the chapters of the
language reference dealing with classes and instances.
The principles are really very simple.
Once you have the right mental model of these, you
should be able to answer all those "what will happen
if I do this" questions for yourself.

Hope that helps,
Greg
IDLE mini-hack & ? (adding menu items, understanding *args, self) [ In reply to ]
On Thu, 29 Jul 1999 10:33:08 +1200, Greg Ewing wrote:
>Isidor wrote:
>> My guess is that insert is a method of text, and "insert" (first
>> argument) is the location of the insertion point
>That's right. To add text to the end of a text widget,
>you can use "end" instead of "insert", e.g.

Is there a list of these anywhere?

>> Can I attach zip to "self" somehow in funcx, e.g., line 3:
>> 'self.zip = "googoo"'?
>Yes, you can certainly do that. It's the usual way
>of assigning to an instance variable in Python.
>
>> Will it only work if self.zip is defined in the
>> __init__ func?
>No, there's no need to do that. You can add a new
>attribute to any instance of any class at any time
>just by assigning to it. There is no notion of
>"declaring" instance variables.

And you don't even need to be in the class definition!

>>> class test:
... def spam( self ):
... print self.eggs
...
>>> thistest = test()
>>> thistest.spam()
Traceback (innermost last):
File "<stdin>", line 1, in ?
File "<stdin>", line 3, in spam
AttributeError: eggs
>>> thistest.eggs = "I like eggs!"
>>> thistest.spam()
I like eggs!
>>>

So, if you added the requisite "self."s to your example, it would give
you similar behaviour. (Especially if you called testy before testx.)

Later,
Blake.

--
One Will. One Dream. One Truth. One Destiny. One Love.