Unification of Methods and Functions

D

David MacQuigg

David MacQuigg said:
David MacQuigg wrote:
James has been telling me it is terribly wrong to keep data as
attributes of classes
[snip]

This makes a lot more sense than a total ban on data in classes.

Just to clarify, once again - I was not suggesting a ban on data in
classes - I was objecting to the specific way in which the data was
being passed around and used in the class heirarchy.

You have some good ideas, but you have been making extreme statements
and over-generalizing.
"""
5/17/04:
OK: "The whole idea of having these structures in any program is
wrong."

Firstly, the program uses a class hierarchy as a data structure. That
isn't what class heirarchies are designed for, and not how they should
be used IMO. But it's what any bright student will pick up from the
example.
"""

We could avoid a lot of non-productive discussion if you were more
careful with these statements. Instead of the above, you could say:
"""
Generally, its not a good idea to store *changeable* data in the class
itself. The Animals_2 program has variables like _numAnimals, which
change when any instances of subclasses are created. If this were a
much bigger program with hundreds of classes being implemented by many
different programmers, one of those programmers might forget to add a
call to the parent's __init__, and all counts of classes above the new
class would be wrong.
"""

Then we could have a productive discussion on whether, in this case,
_numAnimals is OK. I would argue yes, but I understand your point
about the possibility of new, incorrectly written classes causing a
problem.

The reasons I would argue yes are:
1) This is a very simple hierarchy, even if it grows to a hundred
classes.
2) The incrementing of the _num... variables is a simple and standard
part of every __init__ in this hierarchy. A programmer who would
ignore that, is a programmer who would completely mess up modification
of a more complex program involving automatic generation of classes.
3) This is not a database or transaction processing system that needs
to stay online for days at a time, and is vulnerable to out-of-sync
problems arising from interupted sessions, etc.
4) This is an example for students who are new to OOP. I would rather
keep the example simple, and point out how it might be improved, than
burden the students with all the details of a production-quality
program.

I've made your program, Animals_JM.py at
http://ece.arizona.edu/~edatools/Python/Exercises/ the next example
*after* Animals_2. The topic of techniques to make a program robust
and maintainable is worth a whole chapter, but it would be a
distraction in the introductory chapter.

I would still like to see a way to gain the benefits of both Animals_2
and Animals_JM in one program. My attempts to follow up on
suggestions in this discussion have only added complexity, not
improved the program.

-- Dave
 
D

David MacQuigg

If you're writing code, you're writing it to be modified at some
point, unless it's a few lines-long script. If the modifications are
made by someone else then they will reasonably expect that the code
doesn't contain implicit deathtraps, like non-local data.

The trade-off, in the case of _numAnimals is: Do we want a simple
program with instant display of this number, or a more complex program
that scans the entire hierarchy to re-create the total every time it
is needed.

There is an analogy in the netlisting programs used in circuit design.
Netlisting a big design is a time-consuming operation, so each cell is
netlisted when it is saved. When the design is simulated, there is a
quick check of the time-stamps on the entire hierarchy, to make sure
all of the incremental netlists are up-to-date. One could argue that
somehow the time-stamps could get out-of-sync (incorrect clock setting
on a client machine, etc.). Yet nobody says that not netlisting the
entire design every time is bad programming.

It's always a compromise. Robustness is never absolute.

-- Dave
 
G

Greg Ewing

David said:
There is an analogy in the netlisting programs used in circuit design.
Netlisting a big design is a time-consuming operation, so each cell is
netlisted when it is saved. When the design is simulated, there is a
quick check of the time-stamps on the entire hierarchy, to make sure
all of the incremental netlists are up-to-date.

This is a form of cacheing, which is a well-proven technique.
But it's an optimisation, and as such it should be the last
thing added to a design, and then only when it becomes necessary.

Also, there's a difference between cacheing and maintaining
redundant data representations. With a cache, if in doubt you
can always throw away the cache and re-calculate. But if your
design relies on incrementally keeping parallel data structures
up to date, if anything gets out of whack, you're hosed,
because you don't have the ability to re-calculate the
redundant data from scratch.
 
K

Kevin G

On Thu, 20 May 2004 13:32:14 +1200, Greg Ewing

Also, there's a difference between cacheing and maintaining
redundant data representations. With a cache, if in doubt you
can always throw away the cache and re-calculate. But if your
design relies on incrementally keeping parallel data structures
up to date, if anything gets out of whack, you're hosed,
because you don't have the ability to re-calculate the
redundant data from scratch.

Seems to me there's a semantic problem here: if you can't re-calculate
it, then by definition it's not redundant.

-Kevin
 
J

James Moughan

David MacQuigg said:
The trade-off, in the case of _numAnimals is: Do we want a simple
program with instant display of this number, or a more complex program
that scans the entire hierarchy to re-create the total every time it
is needed.

There is an analogy in the netlisting programs used in circuit design.
Netlisting a big design is a time-consuming operation, so each cell is
netlisted when it is saved. When the design is simulated, there is a
quick check of the time-stamps on the entire hierarchy, to make sure
all of the incremental netlists are up-to-date. One could argue that
somehow the time-stamps could get out-of-sync (incorrect clock setting
on a client machine, etc.). Yet nobody says that not netlisting the
entire design every time is bad programming.

It's always a compromise. Robustness is never absolute.

All true; there are certainly times when you need to store complex
calculations, otherwise we'd all be programming in Haskell.

However, there are different ways to achieve the same result. Some
would be transparent and relatively easy to debug, like storing the
number at a container-instance level. The particular way it is done
in Animals_2 is, IMO, one of the less desirable alternatives,
especially as a pattern to teach students.

Jam
 
J

James Moughan

David MacQuigg said:
On Mon, 17 May 2004 16:40:00 +1200, Greg Ewing

David MacQuigg wrote:
James has been telling me it is terribly wrong to keep data as
attributes of classes
[snip]

This makes a lot more sense than a total ban on data in classes.

Just to clarify, once again - I was not suggesting a ban on data in
classes - I was objecting to the specific way in which the data was
being passed around and used in the class heirarchy.

You have some good ideas, but you have been making extreme statements
and over-generalizing.
"""
5/17/04:
OK: "The whole idea of having these structures in any program is
wrong."

Firstly, the program uses a class hierarchy as a data structure. That
isn't what class heirarchies are designed for, and not how they should
be used IMO. But it's what any bright student will pick up from the
example.
"""

We could avoid a lot of non-productive discussion if you were more
careful with these statements. Instead of the above, you could say:
"""
Generally, its not a good idea to store *changeable* data in the class
itself. The Animals_2 program has variables like _numAnimals, which
change when any instances of subclasses are created. If this were a
much bigger program with hundreds of classes being implemented by many
different programmers, one of those programmers might forget to add a
call to the parent's __init__, and all counts of classes above the new
class would be wrong.
"""

This wouldn't quite be my position, though. It doesn't hurt to have
changable data in a class; it's a question of what happens when you
loose the locality of that data, especially in terms of debugging
effort.

Quite likely I have not always communicated myself to you perfectly. I
see how someone could ignore the word heirarchy in the above quote and
loose most of the meaning, which was that data should generally not be
distributed throughout the class heirarchy as if it were a data
structure.

However, I had thought by now that I had made it clear that I do not
regard it as "terribly wrong to keep data as attributes of classes";

2004-05-10: "...you should think carefully before using classes to
store data"

"2004-05-10": "we want to keep the data stored in each class local to
that
class. So, Mammal can store the number of Mammals, if that turns out
to be a good solution..."

So I am not sure why you would misrepresent my position so thoroughly.
Then we could have a productive discussion on whether, in this case,
_numAnimals is OK. I would argue yes, but I understand your point
about the possibility of new, incorrectly written classes causing a
problem.

The reasons I would argue yes are:
1) This is a very simple hierarchy, even if it grows to a hundred
classes.
2) The incrementing of the _num... variables is a simple and standard
part of every __init__ in this hierarchy. A programmer who would
ignore that, is a programmer who would completely mess up modification
of a more complex program involving automatic generation of classes.
3) This is not a database or transaction processing system that needs
to stay online for days at a time, and is vulnerable to out-of-sync
problems arising from interupted sessions, etc.
4) This is an example for students who are new to OOP. I would rather
keep the example simple, and point out how it might be improved, than
burden the students with all the details of a production-quality
program.

And I would have argued no;

1) I'm not sure why this example is simpler than any other
singly-inherited heirarchy. IMO the way it works makes it more
complex than most I've seen.
2) If there's one thing you can rely on, it's people making mistakes,
especially the simple ones. Making a program easy to debug in the
face of simple mistakes is IMO an extremely good idea.
3) That's not especially relevant to what I've been talking about,
that is, programmer error and debugging.
4) Whatever you teach students the first time round takes a long, long
time to leave their heads, if it ever does. Picking practically and
theoretically sound examples would not be hard. These do not have to
be complex at all, just simple examples of typical uses of class
heirarchies.

I've made your program, Animals_JM.py at
http://ece.arizona.edu/~edatools/Python/Exercises/ the next example
*after* Animals_2. The topic of techniques to make a program robust
and maintainable is worth a whole chapter, but it would be a
distraction in the introductory chapter.

I would still like to see a way to gain the benefits of both Animals_2
and Animals_JM in one program. My attempts to follow up on
suggestions in this discussion have only added complexity, not
improved the program.

-- Dave

Simple method - choose a different example. However, you do not
appear to be open to this suggestion.
 
D

David MacQuigg

Simple method - choose a different example. However, you do not
appear to be open to this suggestion.

If I were not open to suggestions, I would not be spending all this
time patiently extracting the good suggestions from this long thread,
and putting up with dogmatic statements and personal attacks. I could
repond by saying most of what I'm reading is hot air, but that would
be equally provocative. Let's see if we can bring this to a better
conclusion.

I like your example, but not as a *substitute* for Animals_2. It's
just too complex for non-CIS students at this point in the course. I
think I understand your concerns about Animals_2. I think the best
way to deal with those concerns is not by complicating the example, or
by taking out everything that could lead to problems, but rather by
pointing out the problems, and showing subsequent examples that fix
those problems, even at the expense of more complexity.

If you have a simple all-in-one alternative to Animals_2, show me.

Meanwhile, what I have understood as problems and suggested solutions
includes:
1) Class variables like numMammals that are not intended to be
directly altered by the user should be made private variables, by
adding a leading underscore. _numMammals.
2) Direct calls to a function from a parent class (super calls) might
not get updated if a programmer adds a new class in the hierarchy.
Calls like Animal.__init__(self) should be replaced by a general call
to the __init__ function from whatever class is directly above the
current class.
Mammal.__bases__[0].__init__(self)
Note: Calls like Mammal.talk(self), which really *are* intended to use
a function from a specific class, should be left as is.
3) Variables like _numMammals which depend on variables in other
classes could get out-of-sync with those other variables ( e.g. if a
programmer adds a new class and forgets to call __init__). These
variables could be replaced by functions which re-calculate the
dependent values from the original independent data.
Note: This is a trade-off of simplicity and efficiency vs robustness
against future programmer error.

I've implemented these suggestions in Animals_2c.py at
http://ece.arizona.edu/~edatools/Python/Exercises/

Here is a snippet from one of the classes:

class Animal(object):
_numAnimals = 0
def numAnimals():
return ( Animal._numAnimals +
Mammal.numMammals() + Reptile.numReptiles() )
numAnimals = staticmethod(numAnimals)

I've re-defined _numAnimals to hold only the count of Animal
instances. To get the total count of all animals, I've got a new
function numAnimals. This function does not walk the entire
hierarchy, as I suspect you might require, but it does add up the
totals from all subclasses directly below Animal. In each of those
subclasses, we define a function which totals the numbers from all of
its subclasses. In this way we get a simple recursive re-calculation
of all totals anywhere in the hierarchy, and the only actual data is
the count in each class of instances of that class.

We still have an inter-dependency problem when adding classes to the
hierarchy. We need to remember to add the appropriate num... function
for the new class to its parent class. For example, when we added a
Reptile class between Animal and Snake, we modified the function above
to include Reptile.numReptiles() in the total.

We also need to make sure we include *all* children of Reptile in the
new numReptiles function.

class Reptile(Animal):
-numReptiles = 0
def numReptiles():
return ( Reptile._numReptiles +
Snake.numSnakes() + Lizard.numLizards() )

Oops. We forgot Geckos !!

To avoid these errors in programming, we may need a function that
automatically searches for subclasses, and calls their num functions.
I'm not sure how to do this without making it excessively complex.
Suggestions will be appreciated.

-- Dave
 
D

David MacQuigg

All true; there are certainly times when you need to store complex
calculations, otherwise we'd all be programming in Haskell.

However, there are different ways to achieve the same result. Some
would be transparent and relatively easy to debug, like storing the
number at a container-instance level. The particular way it is done
in Animals_2 is, IMO, one of the less desirable alternatives,
especially as a pattern to teach students.

I'm still waiting for that one simple example that you say will do it
all, and do it right.

-- Dave
 
G

Greg Ewing

Kevin said:
Seems to me there's a semantic problem here: if you can't re-calculate
it, then by definition it's not redundant.]

Obviously if it's redundant then in principle you can
always recalculate it. But if you don't plan for that,
you can end up not having any piece of code in the
system you can call to recalculate it. Approaching the
problem from the cacheing perspective at least ensures
that you do have such a piece of code.
 
G

Greg Ewing

David said:
I'm still waiting for that one simple example that you say will do it
all, and do it right.

I don't think you should be trying to cram all the features
of OOP into a single example.

Also, I worry that the zoo topic will seem contrived. Why
not base your examples on some real problems from your
audience's subject area?
 
D

David MacQuigg

Kevin said:
Seems to me there's a semantic problem here: if you can't re-calculate
it, then by definition it's not redundant.]

Obviously if it's redundant then in principle you can
always recalculate it. But if you don't plan for that,
you can end up not having any piece of code in the
system you can call to recalculate it. Approaching the
problem from the cacheing perspective at least ensures
that you do have such a piece of code.

Another good example is the redundant setup parameters in a circuit
design system. A setting to designate the location of output files
may start in some hidden setup file, get read in during startup, and
get copied to a number of internal variables in various tools that
have been "integrated" into the system. If you change that setting,
there is no way to propagate the changes through all of the tools.
You have to restart the whole system. Seems like there is an inverse
relationship between the price of software and the quality.

-- Dave
 
D

David MacQuigg

I don't think you should be trying to cram all the features
of OOP into a single example.

I agree. Digestible chunks is the right approach. Animals_1.py is
the first example of classes, including data, methods, instance
variables, and inheritance. Thats a pretty large chunk, but what
makes it digestible is the student's prior knowledge of modules,
functions, and all forms of data.

Animals_2a1.py (the current version) adds more levels to the
hierarchy, a special method __init__, static methods, bound and
unbound methods, the odd scoping rule for class variables, and
_private variables. This is all the basics that students will need if
they will be writing their own classes.

There probably won't be an Animals_3, but instead, "level 3" will be a
bunch of examples to illustrate various techniques, like the robust
programming that JM has been pushing, and maybe some topics from
chapters 21 - 23 of Learning Python.
Also, I worry that the zoo topic will seem contrived. Why
not base your examples on some real problems from your
audience's subject area?

Given the goals of the level one and two examples ( cover all the
basic features in two simple examples ) I think any example will be
contrived. The level 3 examples will be the right place to introduce
real circuit design problems.

-- Dave
 
D

David MacQuigg

I've completed a revision of my OOP chapter. See Prototypes.doc at
http://ece.arizona.edu/~edatools/Python The major change is adding
some footnotes pointing out the limitations of Animals_2, and adding a
section on Robust Programming, using your Animals_JM as an example.

Comments are welcome.

-- Dave
 
D

David MacQuigg

My view is similar: in Python, a method is a dressed up (wrapped) function.
'Unification of methods and functions' reads to me like 'unification of
dressed-up bodies and naked bodies'. What would that mean? One thing I
like about Python is that there is only function-body syntax and not a
separate, slightly different method-body syntax. To me, having only one
type of code body *is* unification. So it is hard for me to see what is
being proposed. Introducing a syntax like '.var' that would only be
meaningful in a method and not a function would be dis-unification.

There seems to be a lot of misunderstanding on the question of "magic"
in Python's method binding syntax vs the proposed syntax. I see
students having difficulty learning the current syntax. Python
experts say its real easy, and point to the explicit "self" as an
advantage of the current syntax, vs the "magic" of setting a global
__self__ variable. All of this is missing the problem.

If Python were consistent, and *always* used a special first argument,
there wouldn't be a problem. The magic first argument would be no
more difficult than magically setting a global variable. The problem
is that some methods require a magic first argument, and others
require that it *not* be there. In one case you call cat1.talk() and
in another it is Cat.talk(cat1). I know this is not terribly
difficult to understand -- one is a bound method, the other unbound.
Still it is a problem for beginners, and it leads to unecessary
complexities like static methods.

The recent discussion on "Method binding confusion" 5/2/04 shows that
even experts can get confused. Here is the example, reduced to its
simplest terms:

import math

def mypow(x, y):
return x**y

class MathA:
pow = math.pow

class MathB:
pow = mypow

ma = MathA()
mb = MathB()

print ma.pow(2,4) #=>
16.0
print mb.pow(2,4) #=>
# TypeError: mypow() takes exactly 2 arguments (3 given)

How would you explain this to non-CIS students in a class on circuit
design, where there is very little time to discuss programming?

-- Dave
 
D

Dave Brueck

David said:
If Python were consistent, and *always* used a special first argument,
there wouldn't be a problem. The magic first argument would be no
more difficult than magically setting a global variable. The problem
is that some methods require a magic first argument, and others
require that it *not* be there. In one case you call cat1.talk() and
in another it is Cat.talk(cat1).

But the above explanation misses one very important point: one is used 99.9% of
the time, the other 0.1% of the time, if that. Mentioning them side by side
like you do implies that they are both equially common, which is not remotely
the case.
I know this is not terribly
difficult to understand -- one is a bound method, the other unbound.
Still it is a problem for beginners, and it leads to unecessary
complexities like static methods.

I don't see how this is at all related to static methods. A method is some
piece of functionality that is specific to a particular class. If you have some
function already lying around, why would you want it as a method, much less a
static method? (I can think of a couple of rather contrived examples, but
nothing compelling or common in a normal application - help me out here)
The recent discussion on "Method binding confusion" 5/2/04 shows that
even experts can get confused. Here is the example, reduced to its
simplest terms:
[snip example that IMO doesn't reflect any sensical use case]
How would you explain this to non-CIS students in a class on circuit
design, where there is very little time to discuss programming?

What kind of an instructor would bring this up in an introductory programming
course? If you're short on time, there are oodles and oodles of more commonly
used topics to cover.

I find the current distinction between methods and functions as one that makes
quite a bit of sense. It doesn't really cause problems, and I've yet to see a
proposal that is cleaner while still making as much sense (IOW, perhaps it IS
imperfect, but I can't think of anything better, and apparently nobody else can
either).

Most importantly, I've yet to see it cause any real or lasting problems for
beginners - either (1) nobody cares unless you point it out and make an issue
out of it, or (2) they ask, you take 30 seconds to explain it, and they say,
"oh, ok" and move on. Occasionally someone particularly bright (and/or with
experience in certain other languages) will really grasp the 'why' behind the
difference, and you'll see the light go on as they begin to understand the
power available to them.

-Dave
 
D

Donn Cave

Quoth David MacQuigg <[email protected]>:
| import math
|
| def mypow(x, y):
| return x**y
|
| class MathA:
| pow = math.pow
|
| class MathB:
| pow = mypow
|
| ma = MathA()
| mb = MathB()
|
| print ma.pow(2,4) #=>
| 16.0
| print mb.pow(2,4) #=>
| # TypeError: mypow() takes exactly 2 arguments (3 given)
|
| How would you explain this to non-CIS students in a class on circuit
| design, where there is very little time to discuss programming?

I wouldn't. I would say

"Classes allow you to create objects with their own functions,
called methods, that you write. Each function takes `self'
as its first parameter."

"Here's a class:"
class A:
def __init__(self):
self.data = 'spud'
def hello(self):
print 'Hello, I am a class A', self.data

"Classes can inherit functions from other classes:"
class B(A):
def __init__(self):
self.data = 'gerbil'

"The actual object - a class `instance' - is created by invoking
the class name, applying arguments which will be passed to __init__."

Then I would go over that, showing what happens and why, until the
concepts introduced above seem to be clear for everyone. That would
conclude my treatment of classes. As an elementary language, there
are some slightly hard things to learn about Python, but this isn't
going to be one of them unless you make it hard.

Donn
 
J

James Moughan

David MacQuigg said:
If I were not open to suggestions, I would not be spending all this
time patiently extracting the good suggestions from this long thread,
and putting up with dogmatic statements and personal attacks. I could
repond by saying most of what I'm reading is hot air, but that would
be equally provocative. Let's see if we can bring this to a better
conclusion.

I like your example, but not as a *substitute* for Animals_2. It's
just too complex for non-CIS students at this point in the course. I
think I understand your concerns about Animals_2. I think the best
way to deal with those concerns is not by complicating the example, or
by taking out everything that could lead to problems, but rather by
pointing out the problems, and showing subsequent examples that fix
those problems, even at the expense of more complexity.

If you have a simple all-in-one alternative to Animals_2, show me.

Again, clearly I have not communicated myself well - I mean a
*completely* different example to the entire Animals approach. The
difficulty with writing a good Animals-type example comes from the
things which it is trying to do, which aren't especially well
expressed by a class-heirarchy.

Why not just take one of your earlier program examples, show the
students what it looks like when object-oriented, then extend the
class(es)?

As I've mentioned, I don't like my example *at all* for your teaching
purposes, and I'm not suggesting you use it. It was simply to
illustrate that there is another method of doing things, and what the
original problems were. (A good solution to the Animals 'problem'
would be simple, it just wouldn't demonstrate any of the things you
want to show.)


We also need to make sure we include *all* children of Reptile in the
new numReptiles function.

class Reptile(Animal):
-numReptiles = 0
def numReptiles():
return ( Reptile._numReptiles +
Snake.numSnakes() + Lizard.numLizards() )

Oops. We forgot Geckos !!

To avoid these errors in programming, we may need a function that
automatically searches for subclasses, and calls their num functions.
I'm not sure how to do this without making it excessively complex.
Suggestions will be appreciated.

-- Dave

Hmm, I think perhaps I like your original example better than this.
:-\ It's a bit like treating a cut throat with a tourniquet.

Searching the subclasses will AFAIK still require some kind of
metaprogramming, which is not too suitable for a 4-hour beginner
course on OO.
 
D

David MacQuigg

Quoth David MacQuigg <[email protected]>:
| import math
|
| def mypow(x, y):
| return x**y
|
| class MathA:
| pow = math.pow
|
| class MathB:
| pow = mypow
|
| ma = MathA()
| mb = MathB()
|
| print ma.pow(2,4) #=>
| 16.0
| print mb.pow(2,4) #=>
| # TypeError: mypow() takes exactly 2 arguments (3 given)
|
| How would you explain this to non-CIS students in a class on circuit
| design, where there is very little time to discuss programming?

I wouldn't. I would say

"Classes allow you to create objects with their own functions,
called methods, that you write. Each function takes `self'
as its first parameter."

"Here's a class:"
class A:
def __init__(self):
self.data = 'spud'
def hello(self):
print 'Hello, I am a class A', self.data

"Classes can inherit functions from other classes:"
class B(A):
def __init__(self):
self.data = 'gerbil'

"The actual object - a class `instance' - is created by invoking
the class name, applying arguments which will be passed to __init__."

This is OK for the first example. I would leave the __init__ methods
to the second example, but either way it will take about 8 pages to
comfortably explain OOP ( maybe ten if I include the "robust
programming" examples that JM says I must ). I would like students to
understand Python at the level they can follow what is going on in a
real program, and maybe write a few classes themselves.
Then I would go over that, showing what happens and why, until the
concepts introduced above seem to be clear for everyone. That would
conclude my treatment of classes. As an elementary language, there
are some slightly hard things to learn about Python, but this isn't
going to be one of them unless you make it hard.

If you are saying we can totally ignore the different method forms, I
think you are wrong. Bound and unbound methods, for example, will be
needed in almost any sizable program. The need for static methods
will arise when the student first writes a method that needs to work
without a current instance.

The example I showed is not intended to explain method binding, and I
would not use it in an introduction to OOP. It probably won't even be
"stumbled upon" in a normal program. I posted it only to show that
even experts can get confused by Python's binding syntax. Are you not
confused by this example?

-- Dave
 
D

David MacQuigg

But the above explanation misses one very important point: one is used 99.9% of
the time, the other 0.1% of the time, if that. Mentioning them side by side
like you do implies that they are both equially common, which is not remotely
the case.

I can't comment on the usage statistics you cite, but it seems to me
that unbound methods are more important than these statistics would
imply. They are necessary to make the language complete, so you can
do things that would otherwise require creating an artificial instance
just to call a method. Learning Python also treats unbound methods as
much more "normal" than your argument would imply.
I don't see how this is at all related to static methods. A method is some
piece of functionality that is specific to a particular class. If you have some
function already lying around, why would you want it as a method, much less a
static method? (I can think of a couple of rather contrived examples, but
nothing compelling or common in a normal application - help me out here)

Here is an example from our CDP ( Circuit Design Platform ):

class Bag:
def __init__(self, **kwargs):
self.__dict__.update(kwargs)

def load(infile):
strng = infile.read()
exec( 'bag = Bag(\n' + strng + ')' )
return bag
load = staticmethod(load)

We need to load a "statefile" with a deeply nested hierarchy of
parameters, which will then be represented by a "Bag" of parameters at
each level of the hierarchy. The load method needs to be called
before any Bag exists, so we add the magic "staticmethod" line, and
don't worry about 'self'. Sure, we could move the load function
outside of the Bag class, but that would disrupt the natural structure
of the program. The load function is unique to Bags, and it belongs
in the Bag class.

See the thread "classes vs dicts" and the discussion following my post
on 5/13 for more on statefiles and Bags. See also
http://www.ece.arizona.edu/~edatools/Python/Statefiles for the latest
on our solution to the statefile problem (how to handle thousands of
deeply-nested setup parameters).
The recent discussion on "Method binding confusion" 5/2/04 shows that
even experts can get confused. Here is the example, reduced to its
simplest terms:
[snip example that IMO doesn't reflect any sensical use case]
How would you explain this to non-CIS students in a class on circuit
design, where there is very little time to discuss programming?

What kind of an instructor would bring this up in an introductory programming
course? If you're short on time, there are oodles and oodles of more commonly
used topics to cover.

Seems like my challenging statement was misleading. I have no
intention of bringing up strange binding problems in an introductory
class. This was a challenge to those who think that Python's binding
syntax is simple. Even an example as strange as this would be no
problem for a student if the new syntax were adopted.
I find the current distinction between methods and functions as one that makes
quite a bit of sense. It doesn't really cause problems, and I've yet to see a
proposal that is cleaner while still making as much sense (IOW, perhaps it IS
imperfect, but I can't think of anything better, and apparently nobody else can
either).

Have you read the proposal at
http://www.ece.arizona.edu/~edatools/Python ?? The folks at
prothon.org also think they have a better solution. There are some
good ideas amongst all the crud. Unification of methods and functions
is one. It remains to be seen if they can do this without introducing
other equally bad complexities. Smooth the carpet in one place, and
the wrinkles pop up somewhere else.

When you say the distinction between methods and functions makes
sense, I assume you mean it has some value to the user. I would like
to hear more about this, because I am assuming just the opposite. In
my OOP chapter, I rely on the fact that students already understand
functions. I use the term "method" only when it is necessary to make
a distinction. Otherwise, everything is just a function, and there is
only one kind.
Most importantly, I've yet to see it cause any real or lasting problems for
beginners - either (1) nobody cares unless you point it out and make an issue
out of it, or (2) they ask, you take 30 seconds to explain it, and they say,
"oh, ok" and move on. Occasionally someone particularly bright (and/or with
experience in certain other languages) will really grasp the 'why' behind the
difference, and you'll see the light go on as they begin to understand the
power available to them.

I think there is a tendency to assume that whatever you have learned
is just the way things have to be. I was assuming that about Python
until someone prodded me to look at Ruby. That got me interested in
other languages, like Prothon. It wasn't until I looked at Prothon
that the light came on regarding this unification issue. Until that
time, I was assuming, like everyone else, that static methods, lambda
functions, and lots of other warts were fundamentally necessary.

The best introductory text on Python is Mark Lutz' Learning Python,
2nd ed. He takes 96 pages to cover OOP, and he doesn't waste time on
unnecessary topics like metaclasses. I believe an improvement in
Python's syntax could make it possible to cut the number of pages in
half, and still reach the same level of proficiency in solving
real-world problems. Why is it we can see there is clutter in Perl,
but we can't see it in Python?

-- Dave
 
D

Dave Brueck

David said:
I can't comment on the usage statistics you cite, but it seems to me
that unbound methods are more important than these statistics would
imply.

All I'm saying is that for most programs, the bound method form is way, way,
way more commonly used than is the unbound calling form.
They are necessary to make the language complete, so you can
do things that would otherwise require creating an artificial instance
just to call a method.

I don't disagree that they are necessary to make the language complete, but
over and over again you have posted examples of the form "here's a bound method
example, here's an unbound method example. They're different". Yes, they're
different, but presenting them that way makes it sound like you normally have
an equal mix of bound and unbound method calls, and that the difference is so
subtle that confusion abounds. Neither is true in practice.
Here is an example from our CDP ( Circuit Design Platform ):

class Bag:
def __init__(self, **kwargs):
self.__dict__.update(kwargs)

def load(infile):
strng = infile.read()
exec( 'bag = Bag(\n' + strng + ')' )
return bag
load = staticmethod(load)

We need to load a "statefile" with a deeply nested hierarchy of
parameters, which will then be represented by a "Bag" of parameters at
each level of the hierarchy. The load method needs to be called
before any Bag exists, so we add the magic "staticmethod" line, and
don't worry about 'self'. Sure, we could move the load function
outside of the Bag class, but that would disrupt the natural structure
of the program. The load function is unique to Bags, and it belongs
in the Bag class.

<sigh> I know what static methods are _for_, but I don't see what is
necessarily complex about them, nor how your proposed unification would help -
in fact, ISTM that it would make it much more confusing because both the
implementor and caller of the code would never be quite sure if the method was
a static one or not, and that's something that needs to be known by both
parties.

Perhaps we both dislike the current staticmethod syntax of Python, but IMO
making a static method look the same as a non-static method seems like a very
wrong thing to do. They are quite different in purpose and construction, so to
me it doesn't make sense that they should look the same in implementation OR in
usage. See, to me it makes sense that there's no 'self' in a static method -
since there's no instance for it to refer to. Likewise, it makes sense to me
that there _is_ a self in a bound method, else member variable lookup would be
magical (certainly more magical than the fact that a bound method knows what
instance it belongs to).
See the thread "classes vs dicts" and the discussion following my post
on 5/13 for more on statefiles and Bags. See also
http://www.ece.arizona.edu/~edatools/Python/Statefiles for the latest
on our solution to the statefile problem (how to handle thousands of
deeply-nested setup parameters).

Yes - I suggested the basis for your current solution. :)
Seems like my challenging statement was misleading. I have no
intention of bringing up strange binding problems in an introductory
class. This was a challenge to those who think that Python's binding
syntax is simple.

I'd say that first and foremost, it's powerful, and then simple. The example
IMO shows an abuse of the power without a good reason to do so (my reaction to
the example as "you're doing something odd for no good reason, and you get odd
behavior, so what?"). The proposed unification does not seem to take into
account the benefits of the current design - whether by choice or ignorance I
don't know.
Have you read the proposal at
http://www.ece.arizona.edu/~edatools/Python ??
Yep.

When you say the distinction between methods and functions makes
sense, I assume you mean it has some value to the user. I would like
to hear more about this, because I am assuming just the opposite.

Yes it has some value, but more than that: it fits the brain quite well. IMO it
adheres to the principle of least surprise, if you are really thinking about
what is going on (and this is why I haven't found it to be a problem for
newbies, because they usually _aren't_ that interested in precisely what is
going on). And if you're not really thinking about what is going on, it places
a pretty minimal burden on you - there's only a handful of rules a blissfully
ignorant programmer needs to adhere to, and that's pretty amazing. But once you
do understand what is going on, then you can do some pretty powerful things
that you can't do in e.g. Java (at least not directly and not without a lot of
work).
I think there is a tendency to assume that whatever you have learned
is just the way things have to be.

Maybe, but I'm not arguing that the way things are is the way things have to
be - I'm arguing that (1) the current way is better than all other proposals to
date and (2) the specific proposal you've put forward introduces more problems
than it solves and possibly results in a less powerful language (but I'm not
sure because I haven't seen examples of how all calling use cases would be
affected).
The best introductory text on Python is Mark Lutz' Learning Python,
2nd ed. He takes 96 pages to cover OOP, and he doesn't waste time on
unnecessary topics like metaclasses. I believe an improvement in
Python's syntax could make it possible to cut the number of pages in
half, and still reach the same level of proficiency in solving real-world
problems

But has Mark told you that his goal was brevity? I bet you could cut the pages
in half without ANY changes to the language, not because he did a poor job (he
obviously didn't) but because you have different goals in mind.
Why is it we can see there is clutter in Perl, but we can't see it in Python?

Nobody is taking that stance, as far as I can tell.

-Dave
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,743
Messages
2,569,478
Members
44,899
Latest member
RodneyMcAu

Latest Threads

Top