Unification of Methods and Functions

J

James Moughan

David MacQuigg said:
I could give you an example from IC Design, but for the course I
teach, I chose to use a similar hierarchy based on something everyone
would understand - a taxonomy of animals. Nothing in this example is
something you wouldn't find in a real program to model an integrated
circuit. Instead of animal names like Cat, we would have the names of
cells in the hierarchy, names like bgref25a. Instead of a variable to
count the number of animals at each level, we might have several
variables to track the total current on each of several supply lines.
Like the counts in the Animals.py hierarchy, we need the total current
to each cell, including all of its subcells.

As far as I understand it, this is fairly different; the way I would
implement it, each cell would be an instance, not a class. I would
have to look at some kind of code/spec to really know, though. Do you
have an example of a piece of software were you follow this approach?
I'm sure there are other examples from other specialties. In
accounting, I can imagine a hierarchy of accounts, with a total for
each account including all of its subaccounts. Don't just assume that
the problem isn't real because you haven't encountered it in your
work.

Sorry, but that's not how you would do an accountancy package in OO.
Again each account would be an instance, not a class, otherwise adding
a new account would require either code modification or metaclass
hacking. Instances scope down, and having a count of the sub-accounts
stored in an object would be OK in my book. Though you wouldn't need
to, since the container for the instances would do it for you.

But even this isn't how you would organize an accountancy program.
You'd use a database.
I think I could do it in 30 pages and 4 hours total ( lecture, lab,
and homework ), but not if I need to cover the topics that both Mark
Lutz and I consider important to basic OOP in the current version of
Python. The 30 pages assumes the unification of methods and functions
that I have proposed.

70 -> 30 because of this? Really?

Anyway, I guess you know what you have time to do, it being your
course 'n all. :)
The total current to an IC is the sum of the currents to all of its
subcircuits. That current is a single number, for example, 35
microamps. It has a name "Iss". Iss is a characteristic of the IC
which appears in data sheets, etc. It is a variable representing the
state of the entire IC. It does not represent the state of any
subcircuit in the IC, even though it gets "altered" whenever one of
those subcircuit currents changes.

So the IC is an instance which stores or accesses data about the
instances which it contains, i.e. circuit elements; not a class which
you sub-class to get subcircuit elements. I'm discussing classes and
class heirarchies, not instance heirarchies.
Looks like this whole argument comes down to what we mean by the word
"specific". Let's drop it and focus on the more interesting topics in
this thread.

From here, it looks like the problem is the difference between an
instance and a class?

I'll need an example to see how these general worries can affect the
Animals_2 hierarchy. What I see is quite robust. I added a Feline
class between Mammal and Cat, and I had to change only two lines in
the Cat class. ( And I could avoid even that if I had used a "super"
call instead of a direct call to the Mammal functions.)

And if someone unfamiliar with the code neglects to call the
superclass initializer when they create a new animal, then the code
will break in unpredictable places. Not calling the superclass is a
common problem - but easily fixable, *providing* the effects show up
in the subclass, and not at some other random place in the heirarchy.
Sounds good.


We are modeling the real world here. When you add a lion to a zoo,
you add one to the count of all animals.

When you add a lion to a zoo, you add one entry in the lion table of
your database. When you want to know the number of animals in the
zoo, the database query counts the number of entries in all of the
animal tables. *Real* databases are built that way because of
experience; repetition of data invariably causes out-of-synch
problems.

I worked on a project a couple of years ago to port a legacy database
into MySQL, and it was made damned close to impossible by this sort of
thinking; how the heck do you port a database which has had it's
shortcomings patched over to the point where it depends on the
inconsistent results for it's output? :-\
When you add 2 microamps to
the core currents in a bandgap voltage reference, you add that same 2
microamps to the total supply current.

Again, I'd be inclined to handle this problem at the instance level,
not the class level.
I'm no expert in OOP, but what I have seen so far is not near as clear
in structure as the origninal Animals_2 example.

When you have to troll through a 20K line program where the
functionality for a single subsystem is scattered through multiple
classes in a heirarchy, it puts a really different perspective on what
'clear structure' is. Likewise, when everything is modularized
explicitly and you can alter a complex system which you barely
understand to include completely new core functionality in an hour or
two of work.
I'll need to see something better before I abandon the curent example.
The problem may be our expectations of OOP. I see classes as modeling
the real world, including variables that are altered by changes in
subclasses. You seem to have some computer science notion of what a
class should be. I'm not saying its wrong, but unless it helps me
solve my real-world problems, in a better way than what I am doing
now, I won't use it.

You're right, I don't give a damn whether classes model the real
world; what matters, in general order of preference, is:

- Robustness
- Maintainability
- Programmer time
- Efficiency

I will sell any theoretical principle to the salt mines for those.
'Classes-model-the-real-world' is an abstract theory, an evolutionary
holdover from when OO was the Next Big Thing in AI. If it leads you
to design a non-robust, hard to maintain system then, in my book, it
gets dropped.
I'm reminded of the criticism Linus Torvalds got when he first
published Linux. The academic community thought it was the worst,
most fundamentally flawed design they had ever seen. It did not fit
some expectation they had that a "microkernel" architecture was the
proper way to design an OS. Luckily, Mr. Torvalds was not dependent
on their approval, and had the confidence to move ahead.

I've been trying to explain why the pattern in your example will cause
bugs; you've been justifying it in terms of OOP metaphors. *shrug* The
comparison is clear enough to me... :)

Exactly as we see in objects in the real world.

Objects perhaps, but not classes. This seems to be the distinction
which is driving this whole problem; you are regarding extending a
class as if it were adding a member to an instance.
Trust me, the need is real. We just need to find the optimum example
to show how Python solves the problem.

Mail me some code where you need to do this in a real system, and I'll
show you how to refactor it. :)
In my work as a software product engineer, I've learned to deal with
two very common criticisms. 1) The user doesn't need to do that. 2)
The user is an idiot for not understanding our wonderful methodology.
These are generally irrefutable arguments that can only be trumped by
a customer with a big checkbook.

I generally don't engage in these
arguments, but on one occasion, I couldn't resist. I was trying to
show an expert how a complicated feature could be done much more
easily with simpler functions we already had in our program.

His argument was basically -- every expert in this company disagrees
with you, and you're an idiot for not understanding how our new
feature works. I replied that I was the one who wrote the User Guide
on that feature. He started to say something, but it was only a
fragment of a word, and it kind of fell on the table and died. There
was a few seconds of silence, while he tried to figure out if he could
call me a liar. I just looked right at him without blinking.

Forget what you have learned in books. Think of a real zoo. Think
how you would write the simplest possible program to do what Animals_2
does -- keep track of all the different classes of animals, and
display the characteristics of any animal or class, including
characteristics that are shared by all animals in a larger grouping.

What you're asking is how would I implement something simply,
providing that I have to implement it with method X. Give me a
functional spec - what the system needs to do from the user point of
view - and I'll tell you how I would do it.

In the case of a real zoo, I can pretty much guarantee that it would
begin
Classes *are* objects.

In Python, classes are 'first class objects' - you can pass them
around as references, alter them and so on. That's in the same way as
functions are 'first class objects'. That's a different and older
piece of terminology than the term 'object' in OOP, which unhappily
coincides with it.

Wherever I can, I'll convert the discussion over to use the term
instance if you're more comfortable with the terminology, but in the
canonical definition a class is not an object. Objects get created
from classes, and the term is equivalent to instance for practical
purposes (though you get into occasional pieces of terminology like
'object instance' where it's just convenient to not have to say
'object object', and 'object variable' would get kinda confusing too.)
Check any basic text on OOP for confirmation, say
http://java.sun.com/docs/books/tutorial/java/concepts/

I think you mean instances. I make a
distinction between class variables and instance variables, depending
on whether the variable is different from one instance to another.
Every instance has a different cat.name, but all cats share the genus
"feline". In fact, they share that genus with all other members of
the Feline class. That is why I moved it from Cat to Feline as soon
as our example was big enough to include a Feline class.


You are adding requirements to what I already have. OK if it doesn't
slow the introductory presentation too much.


Think of a real zoo. If you ask the zookeeper how many animals he
has, will he tell you only the number that are animals, but are not
also lions or tigers or any other species? That number would be zero.

In a real zoo, it would not require brain-surgery on the zookeeper to
introduce a new type of animal. :)

Also, as I say, in a real zoo you would use a database for this task.
Any good database design person would balk at the idea of even storing
the number of a particular animal as an explicit variable in the
database. Think about that.
I really do want numMammals to display the total number of all
mammals, whether or not they are a member of some other class in
addition to Mammal.

If I were to guess at your objection to this, I would assume you are
worried that the different counters will get "out-of-sync", if for
example, someone directly changes one of these variables, rather than
calling the appropriate functions to make a synchronized change.

My answer to that is to make the counter variables private. I've
added a leading underscore to those names. numMammals is now
_numMammals.

OOS due to an explicit variable alteration is pretty much the
worst-case scenario, yes. It would require quite an incompetent coder
to do it, but it could still easily happen. The more likely problem
is the failure to call the superclass constructor - which may even be
harder to debug, since at least in the other case you can run through
the program in a debugger and find where the variable is being
changed, or just grep numMammals.

Your faith in private variables is touching. :)

Supposing I want to store data for two zoos. If the data on the
number of animals is stored at class-level, then I literally can't do
that without closing down the program and opening up a different data
set. Munging the code to have numMammals_at_zoo_1 isn't a good
solution either. :)
My non-CIS students are not familiar with the Decorator pattern. I
fear that will make this example incomprehesible to them.


This is an impressive bit of coding, but I can assure you, as an
introduction to OOP, it will blow away any non-CIS student. It may
also be difficult to modify, for example, if we want to do what
Animals_2 does, and provide a custom display of characteristics for
each class.

Hmm, do you mean the talk method or the formatting of the inventory
data? Because of the rather convoluted scoping rules you have to call
say animal_farm.<superclass name>.talk(self) to access the superclass
from within the talk method, which is a minor annoyance I guess.

To format the show method differently for each class would be a little
more awkward, though you're not doing that at the moment.
One possibility is to make this an Animals_3 example. Animals_1 was a
simple two-class structure. It served to introduce instance
variables, and some basic concepts like inheritance. When we moved to
Animals_2, we pointed out the limitations of Animals_1, like not
having enough classes to put variables like 'genus' where they really
belong.

Maybe we should go one more step, and make this a third example. We
can point out the limitations of Animals_2 in the introduction to
Animals_3. I can see the benefit of moving the print statements to
the top level. This is needed if we ever want to make the classes in
Animals_2 work in some kind of framework with other classes. The
show() functions in Animals_2 could be modified to return a list of
strings instead of printing directly to the console.

I've posted your program as Solution 3 to the exercise at
http://ece.arizona.edu/~edatools/Python/Exercises/ Could you give us
a brief description of the advantages and disadvantages compared to
the original. I'm not able to do that, because I'm having difficulty
restating what you have said above in terms that students will
understand. I cannot, for example, explain why your solution is more
robust.

Advantages:
Robustness; no requirement for a programmer to keep a count of
instances or update the count of the super class by calling it's
initializer. This minimises the chance of programmer error.
Avoids replication of data, eliminating OOS errors.

Maintainability; all of the code for counting is contained in a
single module, so modifications will not require changes to
multiple
similar pieces of code throughout the class structure.

Agility; we can maintain more than one zoo in a program by
creating
new instances of of the animal_farm class. Class level data would
limit us to data on a single zoo system.

Generality; the Collection class can be used for any problem of
this type.

Your vast experience may be blinding you to the problems non-CIS
students will have with these more complex solutions. I may be
pushing a paradigm to some limit, but these are real-world problems
that should be easily solved with a good OOP language.

This isn't something I'd show a programming beginner either! I'd
choose a different way of demonstrating a class heirarchy, myself.
 
A

Antoon Pardon

Op 2004-05-12 said:
I really hate to get involved in this "explicit self" debate, as I
know it has been debated ad-nauseum for many years, but I wish people
would not attribute to me a point-of-view that I do not have. For me
the "explicit self" is *not* the issue. I have a slight preference
for .var over self.var or $var, but this is a matter of personal
preference, not an implicit vs explicit question.

My definition of explicit is that you can tell the meaning of a
statement without reference to the surrounding code. By that
definition all of the above forms are explicit. They are all instance
variables, and there is no other interpretation. The choice between
them is a *minor issue*, and I trust GvR to make these choices.

The real issue is whether we can unify all forms of functions and
methods. This requires we do something different with 'self'. From a
unification standpoint, an equally acceptable solution is that we add
'self' to *all* functions and methods, whether they need it or not.

One could argue that all forms of methods and functions are unified.
It is just that python does some magic so that the method you
access from an object is not the actual function but the curried
function with the object.

If we have the following class:


class Incrementor:

def __init__(self, first_value):
self.value = first_value

def next(self):
result = self.value
self.value += 1
return result


We could rewrite it as follows without methods -- except for __init__ --
and get the same object functionality :


class Incrementor:

def __init__(self, first_value):

def next(self):

result = self.value
self.value += 1
return result

self.value = first_value
self.next = curry(next)(self) # or self.next = curry(next,self)
# depending on the curry version


Now if explaining currying functions to your students will help
them understand, I don't know, although I don't find it that
complicated.
 
T

Terry Reedy

Antoon Pardon said:
One could argue that all forms of methods and functions are unified.
It is just that python does some magic so that the method you
access from an object is not the actual function but the curried
function with the object.

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.

Terry J. Reedy
 
D

David MacQuigg

I'm beginning to understand what you are saying. Thanks for hanging
in there.

As far as I understand it, this is fairly different; the way I would
implement it, each cell would be an instance, not a class. I would
have to look at some kind of code/spec to really know, though. Do you
have an example of a piece of software were you follow this approach?

Using instances instead of classes is something I hadn't considered.
I could add one line after each class to instantiate it. I'll have to
play with this a bit, and see if I can come up with a *simple* program
to do the same thing as Animals_2, but using instances. I don't yet
have the code written for my circuit-design platform, but I can give
you a spec that describes some of the needs I see.

1) We need to represent a hierarchy of cells, typically 5 to 10 levels
deep.
2) Each cell has data associated with it. Some of this data is a
cummulative total of similar data from all subcells -- total currents,
total chip area, etc.
3) The data for each cell is unique, and it must be displayed in a
format unique to that cell.
4) We need a simple function, show() that can be called for any cell
or no cell at all (just knowing the cell type). This function should
show the cell data and the data from all cells above it. In case no
cells of a particular type have been added to the design, the
cummulative data should just show a default value, like zero.

In addition to these functional requirements, we need to make the
program structure as simple as possible, to use as an introduction to
OOP for design engineers who have no CIS background, but have learned
Python up to the point where OOP is introduced.

Robustness and generality are not prime requirements at this point,
although introducing something like _private variables is OK, because
it won't distract from the introduction.
Sorry, but that's not how you would do an accountancy package in OO.
Again each account would be an instance, not a class, otherwise adding
a new account would require either code modification or metaclass
hacking. Instances scope down, and having a count of the sub-accounts
stored in an object would be OK in my book. Though you wouldn't need
to, since the container for the instances would do it for you.

Seems like this is very much like the Animals example. We create an
instance for each asset, but there is one Asset class which is not an
instance. You can't transfer funds to the Asset class, only one of
its accounts. The Asset class, like the Mammal class, keeps data that
is representative of all instances of the class.
But even this isn't how you would organize an accountancy program.
You'd use a database.

That would not work as an example introducing OOP. It would be like
saying to the students -- You can't understand OOP, so we're not even
going to try. Just learn this database program, and use it to solve
whatever problems you may encounter.
70 -> 30 because of this? Really?

I would also cut way back on the "motivational" parts, and a number of
other things that fit well in the current presentation, but are not
necessary in the presentation I have in mind. My 30 page estimate is
based not on crossing out topics in Learning Python, but on looking at
the 7 pages I have now, and guessing what more I need to add.

One of the best books I've ever read on a complex subject is
Introduction to Quantum Theory by David Park. The book is split into
two parts: the theory, which is presented in a brief, straightforward,
but unrushed manner; and the examples, which amplify and re-iterate
what is in the theory section. The first time through, you need to
spend a lot of time reading the examples along with the theory. Years
later, you can quickly read just the theory section, and it is a real
pleasure.

I think I can present the "theory" of OOP in 7 pages, at a sensible
pace, including everything I think will be needed to write most
programs ( bound and unbound methods, lambdas, static methods, etc. ).
http://ece.arizona.edu/~edatools/Python/Prototypes.doc Adding
examples and exercisise may take another 25 pages. Adding
supplementary topics ( multiple inheritance, method resolution order,
some of the techniques you are showing to make classes more versatile
and robust, etc. ) may take another 30 pages.
Anyway, I guess you know what you have time to do, it being your
course 'n all. :)


So the IC is an instance which stores or accesses data about the
instances which it contains, i.e. circuit elements; not a class which
you sub-class to get subcircuit elements. I'm discussing classes and
class heirarchies, not instance heirarchies.

I was also assuming a class hierarchy. The idea of a hierarchy of
instances is new to me. I guess I'll need an example to know what you
are talking about.
From here, it looks like the problem is the difference between an
instance and a class?
???


And if someone unfamiliar with the code neglects to call the
superclass initializer when they create a new animal, then the code
will break in unpredictable places. Not calling the superclass is a
common problem - but easily fixable, *providing* the effects show up
in the subclass, and not at some other random place in the heirarchy.

Adding a call to a superclass __init__ is a common pattern in the
examples I've seen. See the section "Calling Superclass Constructors"
starting on page 321 in Learning Python, 2nd ed.

We need to make a distinction between users and programmers in our
expectations of what kind of errors they will make. Users only need
to *read* a class definition and understand what it does. Programmers
are the group that needs to remember to add a call to the superclass
when they write a new class.

I am a user of the Qt Toolkit, but I would not attempt to add a class
to their existing hierarchy. Nor would I expect Trolltech to provide
me with some kind of robust class-generating function that was
guaranteed to generate an error-free class at any point in their
hierarchy I might chose to insert it.
When you add a lion to a zoo, you add one entry in the lion table of
your database. When you want to know the number of animals in the
zoo, the database query counts the number of entries in all of the
animal tables. *Real* databases are built that way because of
experience; repetition of data invariably causes out-of-synch
problems.

I think the database problem is different than our Animals example.
With a database, you typically have many users directly changing the
data over a long period of time when errors can accumulate. Maybe
some user runs a "transaction" script to debit one account and credit
another, but his line goes down before the second part completes. So
it makes sense in this case to store only the primitive data, and
accept the overhead of recalculating everything else from that data
whenever it is needed.
I worked on a project a couple of years ago to port a legacy database
into MySQL, and it was made damned close to impossible by this sort of
thinking; how the heck do you port a database which has had it's
shortcomings patched over to the point where it depends on the
inconsistent results for it's output? :-\


Again, I'd be inclined to handle this problem at the instance level,
not the class level.


When you have to troll through a 20K line program where the
functionality for a single subsystem is scattered through multiple
classes in a heirarchy, it puts a really different perspective on what
'clear structure' is. Likewise, when everything is modularized
explicitly and you can alter a complex system which you barely
understand to include completely new core functionality in an hour or
two of work.

I would expect a Python programmer seeing the Animals_2 example for
the first time, could add a class between Feline and Mammal by the end
of an hour, and be quite confident that nothing was broken. I studied
your example for two hours, and I still don't understand it well
enough to make some basic changes.
You're right, I don't give a damn whether classes model the real
world; what matters, in general order of preference, is:

- Robustness
- Maintainability
- Programmer time
- Efficiency

This is a good set of priorities for a production program. For
teaching OOP, I would say clarity is the over-riding concern.
I will sell any theoretical principle to the salt mines for those.
'Classes-model-the-real-world' is an abstract theory, an evolutionary
holdover from when OO was the Next Big Thing in AI. If it leads you
to design a non-robust, hard to maintain system then, in my book, it
gets dropped.


I've been trying to explain why the pattern in your example will cause
bugs; you've been justifying it in terms of OOP metaphors. *shrug* The
comparison is clear enough to me... :)

I find the "piece of machinery" metaphor to be helpful, but no big
deal. Mostly, it re-inforces the idea of encapsulation, keeping the
dirty details of how an alternator works inside the component, and
presenting to the outside world, a simple interface: if you want more
current at that output, put more voltage on this input.

I'm not using metaphors to justify bad programming. I am just having
trouble seeing how one program, side-by-side with another, is
supposedly bad.
Objects perhaps, but not classes. This seems to be the distinction
which is driving this whole problem; you are regarding extending a
class as if it were adding a member to an instance.
???


Mail me some code where you need to do this in a real system, and I'll
show you how to refactor it. :)

Let's see if we can get the Animals_2 example right, based on the
requirements I stated above. It will be a few months before I have my
design platform ready, and it will be far more difficult to discuss
basic ideas in the context of a big system. I think Animals_2.py has
one of everything I will be using.
What you're asking is how would I implement something simply,
providing that I have to implement it with method X. Give me a
functional spec - what the system needs to do from the user point of
view - and I'll tell you how I would do it.

See above for functional spec.
In the case of a real zoo, I can pretty much guarantee that it would
begin

Don't forget. My basic purpose with the Animals_2 example is to teach
OOP, not the use of a particular database.
In Python, classes are 'first class objects' - you can pass them
around as references, alter them and so on. That's in the same way as
functions are 'first class objects'. That's a different and older
piece of terminology than the term 'object' in OOP, which unhappily
coincides with it.

Wherever I can, I'll convert the discussion over to use the term
instance if you're more comfortable with the terminology, but in the
canonical definition a class is not an object. Objects get created
from classes, and the term is equivalent to instance for practical
purposes (though you get into occasional pieces of terminology like
'object instance' where it's just convenient to not have to say
'object object', and 'object variable' would get kinda confusing too.)
Check any basic text on OOP for confirmation, say
http://java.sun.com/docs/books/tutorial/java/concepts/

I agree the terminology is confusing, and I try to use "object" only
in the most generic sense. "Class" and "instance", as Python uses
them are OK with me.

The question, as I understand it, is whether classes should hold just
methods, or both methods and data. Again, the examples I have seen
show plenty of data being stored as attributes of classes. See
Learning Python, 2nd ed. p. 317. The general form of the class
statement according to Mark Lutz is:

class <name>(superclass, ...): # Assign to name.
data = value # Shared class data
def method(self, ...): # Methods
self.member = value # Per-instance data

Methods are more prevalent in the examples than data, but as far as I
know, there is no warning to the effect: This is possible, but not
good practice.
In a real zoo, it would not require brain-surgery on the zookeeper to
introduce a new type of animal. :)

Adding the Feline class to the existing hierarchy was very easy.
Also, as I say, in a real zoo you would use a database for this task.
Any good database design person would balk at the idea of even storing
the number of a particular animal as an explicit variable in the
database. Think about that.

Just so we can move on, I will accept your assertion that a truly
bulletproof zoo program would use a database. We still need an
example that doesn't use a database, or anything other than Python's
basic functionality, to teach students about OOP.
OOS due to an explicit variable alteration is pretty much the
worst-case scenario, yes. It would require quite an incompetent coder
to do it, but it could still easily happen. The more likely problem
is the failure to call the superclass constructor - which may even be
harder to debug, since at least in the other case you can run through
the program in a debugger and find where the variable is being
changed, or just grep numMammals.

Your faith in private variables is touching. :)

The Python policy on private variables, which I agree with, is that we
don't need to prevent deliberate action, just make sure the user
understands a variable is private, not to be altered except by the
provided functions.
Supposing I want to store data for two zoos. If the data on the
number of animals is stored at class-level, then I literally can't do
that without closing down the program and opening up a different data
set. Munging the code to have numMammals_at_zoo_1 isn't a good
solution either. :)

OK, now I understand. Let's make "Multi_Zoo.py" an example later in
the chapter, not part of the introduction.
Hmm, do you mean the talk method or the formatting of the inventory
data? Because of the rather convoluted scoping rules you have to call
say animal_farm.<superclass name>.talk(self) to access the superclass
from within the talk method, which is a minor annoyance I guess.

To format the show method differently for each class would be a little
more awkward, though you're not doing that at the moment.

I am intending to do exactly that. Because the example is short, you
are tempted to say I really don't need different show functions for
each class. I can add more unique items to each class if I must,
making it look more like a "real program", or you can accept the
requirement that the small differences in the example display are
important.

How about something like this: We put all data and formatting unique
to each class within that class, but we don't use the trick of each
class calling its parent to generate a complete display. The
sequencing of displays could then be done by some generic function,
calling in sequence the unique display functions from each class.
Advantages:
Robustness; no requirement for a programmer to keep a count of
instances or update the count of the super class by calling it's
initializer. This minimises the chance of programmer error.
Avoids replication of data, eliminating OOS errors.

Maintainability; all of the code for counting is contained in a
single module, so modifications will not require changes to
multiple
similar pieces of code throughout the class structure.

Agility; we can maintain more than one zoo in a program by
creating
new instances of of the animal_farm class. Class level data would
limit us to data on a single zoo system.

Generality; the Collection class can be used for any problem of
this type.

This is excellent. I will add it to my web-page.
This isn't something I'd show a programming beginner either! I'd
choose a different way of demonstrating a class heirarchy, myself.

OK, I'm convinced we need a third example. Animals_2 will remain part
of the introduction, and our next example Animals_3 will be our
bullet-proof, production-quality,
what-you-better-do-if-someone-is-paying-you, final solution.

I would still like to see some simplification of Animals_JM.py
Although clarity is not specifically on your list of requirements, I
think it does impact maintainability and programmer time.

Meanwhile, I'll try doing something with your "instances instead of
classes" suggestion.

-- 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?

By unification, I mean making the calling sequence identical for all
methods and functions. No more special syntax for lambda functions,
static methods, etc. No time wasted on these topics in a text like
Learning Python. A simpler presentation of OOP, based on what
students already know at this point - functions, modules, and global
variables.
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.

The one fundamental difference between methods and functions is the
presence of instance variables in methods. You can hide that
difference with the self.var trick. You can hide it even further by
not using 'self', but some other word that looks just like the other
variable names in the function. None of this removes the actual
difference, or makes learning about instance variables any easier for
the student.

The choice of how to identify instance variables is a minor issue. I
think it is actually better not to *hide* the instance variables, but
to *highlight* them. Ruby uses $var for instance variables. Prothon
uses a leading dot, which is quite visible in most code editors.

The real issue is not self.var vs .var It is the complexity resulting
from having different calling sequences. Compare the 96-page
presentation of OOP in Learning Python, 2nd ed. to what I have written
at http://ece.arizona.edu/~edatools/Python/Prototypes.doc
In 7 pages, I can present the basics of OOP. Adding examples and
exercises will probably bring this to 30 pages. The key to this
simplification is not having to deal with unnecessary complexities,
like lambda functions, static methods, etc.
Introducing a syntax like '.var' that would only be
meaningful in a method and not a function would be dis-unification.

Actually, .var could be used in a function outside a class, just as
you can now use self.var in the current function syntax. Before using
a function with .var, the global variable __self__ must be set. This
is normally done by calling the function from an instance. Before
using a function with self.var, the first argument must be set. This
is normally done by calling the function fom an instance.

To say that adding an instance variable to a function breaks unity is
like saying that adding a global variable to a function breaks unity.
Adding an instance variable to a function simply makes the function
dependent on an external variable. Same with a global.

-- Dave
 
G

Greg Ewing

David said:
1) We need to represent a hierarchy of cells, typically 5 to 10 levels
deep.
2) Each cell has data associated with it. Some of this data is a
cummulative total of similar data from all subcells -- total currents,
total chip area, etc.
3) The data for each cell is unique, and it must be displayed in a
format unique to that cell.
4) We need a simple function, show() that can be called for any cell
or no cell at all (just knowing the cell type). This function should
show the cell data and the data from all cells above it. In case no
cells of a particular type have been added to the design, the
cummulative data should just show a default value, like zero.

It's still not clear whether the hierarchy you're talking
about is a hierarchy of *kinds* of cells (i.e. a class
hierarchy), or a physical hierarchy of actual cells (i.e.
an instance hierarchy).

Both of these hierarchies can exist at the same time, and
they are different.

For example, you might have a kind of cell called NandGate,
and a kind of cell called Transistor. An instance of NandGate
contains instances of Transistor -- this is an instance
hierarchy. But Transistor is not a subclass of NandGate --
that would imply a Transistor is a kind of NandGate, which
is not true.

On the other hand, NandGate may well be a subclass of Gate,
and Gate and Transistor may be subclasses of Component. But
that doesn't mean Gates *contain* NandGates.

If prototype-based programming leads to confusion between
class hierarchies and instance hierarchies, the I would
say it's not helpful.
 
M

Michele Simionato

David MacQuigg said:
## This does not:
uf = Cat.talk # Save an unbound function.
uf() #=>
# AttributeError: type object 'Cat' has no attribute 'name'

I would like to be able to do something like:
__self__ = cat2
uf()

So, if I understand correcly, you want Cat.talk to be the same than
cat.talk and Cat to be a prototype just as cat (so Cat must
increase the animal counter and its __init__ method has to
be called at definition time). It is enough to change a
couple of lines in the prototype module:

import sys
from types import FunctionType
from inspect import isfunction

class methodwrapper(object):
def __init__(self,func,cls):
self.__func__ = func
self.__cls__ = cls
def __get__(self,none,cls):
globs=sys.modules[cls.__module__].__dict__.copy()
globs["self"] = globs[cls.__name__] = cls
globs["super"] = super(self.__cls__,cls)
return FunctionType(
self.__func__.func_code,
globs,
self.__func__.func_name,
self.__func__.func_defaults,
self.__func__.func_closure)

class _Prototype(type):
def __init__(cls,name,bases,dic):
for k,v in dic.iteritems():
if isfunction(v):
setattr(cls,k,methodwrapper(v,cls))
super(_Prototype,cls).__init__(name,bases,dic)
cls.__init__()
def __call__(cls,*args,**kw):
newcls = type("Prototype:%s" % cls.__name__,(cls,),
{"__module__": cls.__module__})
newcls.__init__(*args,**kw)
return newcls

class Prototype(object):
__metaclass__=_Prototype
def __init__(*args,**kw):
pass

### END

Here is an example of usage:

from prototype import Prototype

class Animal(Prototype):
numAnimals = 0
home = "Earth"
def __init__():
Animal.numAnimals += 1
def show():
print "Animals:", self.numAnimals

class Feline(Animal):
genus="Feline"
def __init__(name="unknown",sound=""):
super.__init__()
self.name=name
self.sound=sound
def talk():
print "%s talking: %s!" % (self.genus,self.sound)

class Cat(Feline): # created here
numCats = 0
def __init__ ( n = "unknown", s = "Meow" ):
super.__init__(n,s)
Cat.numCats += 1
def show():
super.show()
print " Cats:", self.numCats
def talk():
print "My name is", self.name
print "I am a %s from %s" % (self.genus, self.home)
super.talk()

Cat.talk()

cat = Cat("Garfield")
cat.talk()
 
A

Antoon Pardon

Op 2004-05-13 said:
By unification, I mean making the calling sequence identical for all
methods and functions.

It already is. It is just that the function you have defined within
the class, is not the function you call through the object.
No more special syntax for lambda functions,

All right I understand that.
static methods, etc. No time wasted on these topics in a text like
Learning Python. A simpler presentation of OOP, based on what
students already know at this point - functions, modules, and global
variables.

A method is just a function. The special calling form for methods
is not object specific. With a little extra work you can have exactly
the same calling form for classes with no methods.
The one fundamental difference between methods and functions is the
presence of instance variables in methods.

No it is not. The instance variables you speak of within a method
are just the instance variables of a parameter. It can work just
as well with a function that is written outside the class. you
just need to wrap it somewhat if you want the same calling sequence
but that is all and this wrapping can happen outside the class too.

You can hide that
difference with the self.var trick. You can hide it even further by
not using 'self', but some other word that looks just like the other
variable names in the function. None of this removes the actual
difference, or makes learning about instance variables any easier for
the student.

Maybe you should start with classes without methods then. Just

class Whatever:
pass.

Now you can make objects of this class, give them instance variables
and write a function that expects a whatever object as first argument
en let it play with the instance variables of the argument. If you wish
you can call that first argument self it doesn't really matter. There
is nothing method or function specific here.
The choice of how to identify instance variables is a minor issue. I
think it is actually better not to *hide* the instance variables, but
to *highlight* them. Ruby uses $var for instance variables. Prothon
uses a leading dot, which is quite visible in most code editors.

The real issue is not self.var vs .var It is the complexity resulting
from having different calling sequences.

There really aren't different calling sequences, just to different
related function. Lets consider the two following functions.

def addf(a, b):

return a + b


def addc(a):

def addh(b):

return a + b

return addh


Now you can use both to add two numbers, but the calling sequence
will be different: You call addf(n1, n2), but you call addc(n1)(n2).
The seconde is called the curried version of the first.
Now if I know I will need to add 2 to al lot of numbers I can
do the following: add2 = addc(2) and now when I need to add 2 to
a number I can just call add2(n). So now I have a function that
has the value 2 bound to it. In a way it has become a method of
2. We can do that even explicitly

class Int(int):
pass

two = Int(2)
add2 = addc(two)
two.add = add2

(2, 7, 8, 9)


The only thing that is happening is that if you write a function
within a class, python transforms it into the curried version
and does the rest of the things implicitely that I have done
above explicitly.

Compare the 96-page
presentation of OOP in Learning Python, 2nd ed. to what I have written
at http://ece.arizona.edu/~edatools/Python/Prototypes.doc
In 7 pages, I can present the basics of OOP. Adding examples and
exercises will probably bring this to 30 pages. The key to this
simplification is not having to deal with unnecessary complexities,
like lambda functions, static methods, etc.


Actually, .var could be used in a function outside a class, just as
you can now use self.var in the current function syntax. Before using
a function with .var, the global variable __self__ must be set. This
is normally done by calling the function from an instance. Before
using a function with self.var, the first argument must be set. This
is normally done by calling the function fom an instance.
To say that adding an instance variable to a function breaks unity is
like saying that adding a global variable to a function breaks unity.

It does in some sense.

I also could have written the add2 function as follows

self = 2
def add2(n):

return self + n


I don't think that would have been a good idea. Having __self__ being
a global variable is disaster waiting to happen. What if a method
calls another method, what if you use threads?
Adding an instance variable to a function simply makes the function
dependent on an external variable. Same with a global.

But currently in python "self" is not an external variable, it is
just the first argument of the function. Manipulating an argument
is different from manipulating an external variable.
 
D

David MacQuigg

It's still not clear whether the hierarchy you're talking
about is a hierarchy of *kinds* of cells (i.e. a class
hierarchy), or a physical hierarchy of actual cells (i.e.
an instance hierarchy).

In the Animals_2 example we are discussing
http://ece.arizona.edu/~edatools/Python/Animals.py, the classes (
Animal, Mammal, Feline, Cat ) are *kinds* of animals. The instances (
cat1, cat2, etc. ) represent the actual objects in the inventory.

James has been telling me it is terribly wrong to keep data as
attributes of classes, or to make the methods in each class dependent
on superclasses. The latest suggestion (if I understand it correctly)
is to make a dummy instance from each class, just so we can store data
that is common to the whole class. The "functional spec" above
deliberately avoids any requirement for classes, instances, other
implementation detail.

I'm still trying to come up with a simple example using James' latest
suggestions (something simpler than the automatic class generator he
wrote).

class Feline(Mammal):
_numFelines = 0
genus = "feline"
def __init__(self):
Mammal.__init__(self)
Feline._numFelines += 1
def show(self):
Mammal.show(self)
print " Felines:", Feline._numFelines
## show = staticmethod(show)
feline = Feline()

By adding an extra line after each class, we can eliminate the static
methods. Now when I want to show the characteristics of the Feline
class, I can call feline.show() and get the same result as I had
before using the static method Feline.show().

I still need to move the data to the instance, and get rid of the
super calls ( Mammal.__init__ and Mammal.__show__ ) I am having great
difficulty understanding James' suggestions. Maybe you could help
clarify what is wrong with the original example, and what we need to
do to fix it.
Both of these hierarchies can exist at the same time, and
they are different.

This is true, and the real program will be more complex than the
Animals_2 example. In Animals_2 it is sufficient to know the total
number of Cats. In the real program, we will need the total number of
gates of type NAND2. In *addition*, we will need to know the total
current to each instance of NAND2, including all the subcircuits under
that instance. Those totals, in my way of thinking, should be stored
on each NAND2 instance.
For example, you might have a kind of cell called NandGate,
and a kind of cell called Transistor. An instance of NandGate
contains instances of Transistor -- this is an instance
hierarchy. But Transistor is not a subclass of NandGate --
that would imply a Transistor is a kind of NandGate, which
is not true.

On the other hand, NandGate may well be a subclass of Gate,
and Gate and Transistor may be subclasses of Component. But
that doesn't mean Gates *contain* NandGates.

If prototype-based programming leads to confusion between
class hierarchies and instance hierarchies, the I would
say it's not helpful.

Let's make sure we are talking about the same thing. If by
prototype-based programming, you mean Prothon-style, classless
prototypes, then I agree. The benefits of having a distinction
between classes and instances, in my current understanding of
"classless programming", far outweigh any simplification that might
flow from having just one "tier" in the object model. That is why I
have avoided in my proposal the "classless" aspects of Prothon.

On the other hand, if you are suggesting that something in my current
proposal will lead to confusion, I would like more specifics.

-- Dave
 
D

David MacQuigg

It already is. It is just that the function you have defined within
the class, is not the function you call through the object.

In the current sytnax, we need a special "staticmethod" statement to
define a method we can call without an instance. In the proposed
syntax, there is no distinction between the different method styles,
and no need for special syntax. A "staticmethod" has the same
arguments as normal method, and the same as an ordinary function.
This is what I mean by having the same "calling sequence".

For an example of how unification simplifies the presentation of OOP,
see the first 8 pages of
http://ece.arizona.edu/~edatools/Python/Prototypes.doc
There really aren't different calling sequences, just to different
related function.

In the Animals.py example at http://ece.arizona.edu/~edatools/Python/
there is a method Mammal.show(). Normally, the calling sequence is
Mammal.show(cat1)
where cat1 is just an instance needed to satisfy the requirement for a
special first argument. The method does not *use* cat1, and in
general, there may not be an instance of every class available. If we
convert Mammal.show to a static method, we don't need the special
first argument. The calling sequence is then
Mammal.show()

Do you understand what I mean by "different calling sequences"?

Having __self__ being
a global variable is disaster waiting to happen. What if a method
calls another method, what if you use threads?

If a method calls another method, the same thing happens in the
proposed syntax as in current Python. Normally, the current instance
doesn't change.
Python 2>>> Mammal.talk(self)
Python 3>>> Mammal.talk()
If you want to *over-ride* the normal behavior and *force* a specific
binding:
Python 2>>> Mammal.talk(cat2)
Python 3>>> __self__ = cat2; Mammal.talk()
I don't know why anyone would ever want to do this within a method,
but it is possible.

I don't understand the problem with threads. Perhaps an example would
help.

-- Dave
 
A

Antoon Pardon

Op 2004-05-14 said:
In the current sytnax, we need a special "staticmethod" statement to
define a method we can call without an instance.

Not realy, this would work just as well:

def __func(a,b):
...


class C:

def __init__(self):

self.func == __func


Why one wouldn't just write func and use it, instead of having
a staticmethod is beyond me, but it you want it be my guest.
In the proposed
syntax, there is no distinction between the different method styles,
and no need for special syntax. A "staticmethod" has the same
arguments as normal method, and the same as an ordinary function.
This is what I mean by having the same "calling sequence".

For an example of how unification simplifies the presentation of OOP,
see the first 8 pages of
http://ece.arizona.edu/~edatools/Python/Prototypes.doc


In the Animals.py example at http://ece.arizona.edu/~edatools/Python/
there is a method Mammal.show(). Normally, the calling sequence is
Mammal.show(cat1)
where cat1 is just an instance needed to satisfy the requirement for a
special first argument. The method does not *use* cat1, and in
general, there may not be an instance of every class available. If we
convert Mammal.show to a static method, we don't need the special
first argument. The calling sequence is then
Mammal.show()

Do you understand what I mean by "different calling sequences"?

And do you understand it when I say, that it is not different
calling sequences but different functions. Two functions that
use the same name, are related in functionaly but are different.

Did you understand what happened with the "two" object?
If a method calls another method, the same thing happens in the
proposed syntax as in current Python. Normally, the current instance
doesn't change.

In current python no global variables are set by a call to a method.
Python 2>>> Mammal.talk(self)
Python 3>>> Mammal.talk()
If you want to *over-ride* the normal behavior and *force* a specific
binding:
Python 2>>> Mammal.talk(cat2)
Python 3>>> __self__ = cat2; Mammal.talk()
I don't know why anyone would ever want to do this within a method,
but it is possible.

I don't understand the problem with threads. Perhaps an example would
help.

You talk about a global variable that is being set, What if two threads
quasi simultaneously call a method. To what value is this global
__self__ set.
 
R

Rich Krauter

The one fundamental difference between methods and functions is the
presence of instance variables in methods.


To me, that statement is missing the whole point. OOP is not procedural
programming. That is the fundamental difference.

"Unifying" the syntax in an attempt to make it easier for your students
to learn probably won't accomplish what you want - one, I don't think
what you propose is easier or better, and two, and more importantly,
simply knowing python's (or any other language's) OOP syntax doesn't
automatically make you an OO guru.

Knowing when, where and why to implement classes is much harder than
learning python's current syntax for building them. Your proposal
doesn't change that.


You can hide that
difference with the self.var trick. You can hide it even further by
not using 'self', but some other word that looks just like the other
variable names in the function. None of this removes the actual
difference, or makes learning about instance variables any easier for
the student.The choice of how to identify instance variables is a minor issue. I
think it is actually better not to *hide* the instance variables, but
to *highlight* them. Ruby uses $var for instance variables. Prothon
uses a leading dot, which is quite visible in most code editors.
Actually, .var could be used in a function outside a class, just as
you can now use self.var in the current function syntax. Before using
a function with .var, the global variable __self__ must be set.

I may be way off here, but I don't see how a global __self__ could work:

<not python>

class B:
def __init__(name,data):
.data = data*5
.name = '***%s***'%name

class A:
def __init__(name,data):
.data = data # __self__ is a?
.obj = B() # __self__ is now the B instance?
.name = name # now what?

a = A()

</not python>


Rich
 
S

Sean Ross

On Thu, 2004-05-13 at 17:59, David MacQuigg wrote:
[snip]
Hi.

I realize this is not germane but Ruby uses @var for instance variables.
$var is used for globals.

Sean
 
G

Greg Ewing

David said:
James has been telling me it is terribly wrong to keep data as
attributes of classes

There's nothing inherently wrong with that, as long as
you're certain you will only ever want one instance of that
piece of data.

In my chip example, you might want to keep a count of the
total number of transistors in your chip as a class variable
of the Transistor class. But that only works if your program
only ever deals with one chip at a time. If there can be more
than one chip, then you'll need a Chip class whose instances
either keep a count of Transistors contained in that chip,
or have a method that can compute the count when needed.

Even if you think you will only deal with one chip at a
time, it would be good design practice to plan on having
a Chip class anyway, in case the requirements change. The
amount of work involved is about the same either way, and
the design which avoids class variables is more flexible.
 
C

Corey Coughlin

You know, it's interesting, I'm an engineer at a semiconductor
company, and I've written netlist manipulation objects over and over
again with only the most basic oop structure, and they usually work
fine. Associating data with each cell is usually not that hard,
although the show() function showing data from all cells above it (?)
seems kind of strange. This type of problem is a case where object
encapsulation is usually a lot more effective than inheritance. In my
netlist objects, the most basic object is just a name object, which
everything inherits because everything in a netlist has a name.
Beyond that, there's some container inheritance, I came up with an
ordered dictionary for pin data, some simple inheritance where Pin
objects inherit from Net objects, but that's about it.

The basic object structure is pretty simple. The highest level object
is a netlist. The netlist contains a list (just a simple python list)
of cell objects. The cell objects contain a list of nets, a list of
pins, and a list of instances. There are classes for instances, nets,
and pins (which inherit from nets). The instances do refer to their
parent cells, and the pins from the parent are copied in, but that's
about as complicated as things get in the class hierarchy. There's
also a special container class to describe the connections in a cell,
so I can send the connection mesh a net, an instance, or an
instance,pin tuple, and find out what it's connected to. That's
probably the most complicated class, since it uses a few internal
dicts and a type driven interface to resolve the input data. But
that's really about the size of it. Transistors would be instances in
cells (although a transistor cell parent is also in there) and gates
would simply be cells. Now if you had similar cells with different
current or power characteristics, I suppose you'd need to associate
those with an instance class instead of a cell class. Beyond that, I
don't really see why this is such a difficult thing to do. Unless
there's some other overriding goal this needs to accomplish that isn't
in the spec below. You might want to try just coming up with
something like the container hierarchy I have, not worrying about
inheritance, and see if that does the trick for you.
 
J

James Moughan

David MacQuigg said:
I'm beginning to understand what you are saying. Thanks for hanging
in there.



Using instances instead of classes is something I hadn't considered.
I could add one line after each class to instantiate it. I'll have to
play with this a bit, and see if I can come up with a *simple* program
to do the same thing as Animals_2, but using instances. I don't yet
have the code written for my circuit-design platform, but I can give
you a spec that describes some of the needs I see.

1) We need to represent a hierarchy of cells, typically 5 to 10 levels
deep.
2) Each cell has data associated with it. Some of this data is a
cummulative total of similar data from all subcells -- total currents,
total chip area, etc.
3) The data for each cell is unique, and it must be displayed in a
format unique to that cell.
4) We need a simple function, show() that can be called for any cell
or no cell at all (just knowing the cell type). This function should
show the cell data and the data from all cells above it. In case no
cells of a particular type have been added to the design, the
cummulative data should just show a default value, like zero.

In addition to these functional requirements, we need to make the
program structure as simple as possible, to use as an introduction to
OOP for design engineers who have no CIS background, but have learned
Python up to the point where OOP is introduced.

Robustness and generality are not prime requirements at this point,
although introducing something like _private variables is OK, because
it won't distract from the introduction.

I think another poster gave a sample architecture for this type of
thing - on a brief look it looks sound, and since he's an electronic
engineer I'd say he probably understands the precise problem better
than I do.
Seems like this is very much like the Animals example. We create an
instance for each asset, but there is one Asset class which is not an
instance. You can't transfer funds to the Asset class, only one of
its accounts. The Asset class, like the Mammal class, keeps data that
is representative of all instances of the class.

Again, no - can you imagine how disasterous out-of-synch is in
accountancy? It would be better to physically destroy the hardware
than cause an OOS error. I'd keep all of the assets in a container
and add their values when the number is needed -effectively
re-implementing a database.
That would not work as an example introducing OOP. It would be like
saying to the students -- You can't understand OOP, so we're not even
going to try. Just learn this database program, and use it to solve
whatever problems you may encounter.

We were specifically talking about real world examples - if there's no
need to use this structure then there's no need to teach it. Instead,
you could choose examples reflecting how OOP is generally used.
I would also cut way back on the "motivational" parts, and a number of
other things that fit well in the current presentation, but are not
necessary in the presentation I have in mind. My 30 page estimate is
based not on crossing out topics in Learning Python, but on looking at
the 7 pages I have now, and guessing what more I need to add.

One of the best books I've ever read on a complex subject is
Introduction to Quantum Theory by David Park. The book is split into
two parts: the theory, which is presented in a brief, straightforward,
but unrushed manner; and the examples, which amplify and re-iterate
what is in the theory section. The first time through, you need to
spend a lot of time reading the examples along with the theory. Years
later, you can quickly read just the theory section, and it is a real
pleasure.

I think I can present the "theory" of OOP in 7 pages, at a sensible
pace, including everything I think will be needed to write most
programs ( bound and unbound methods, lambdas, static methods, etc. ).
http://ece.arizona.edu/~edatools/Python/Prototypes.doc Adding
examples and exercisise may take another 25 pages. Adding
supplementary topics ( multiple inheritance, method resolution order,
some of the techniques you are showing to make classes more versatile
and robust, etc. ) may take another 30 pages.


I was also assuming a class hierarchy. The idea of a hierarchy of
instances is new to me. I guess I'll need an example to know what you
are talking about.

I just mean that one instance contains other instance, which contain
further instances... to access an instance, access it's container.
I'm sure you're familiar with this.
Adding a call to a superclass __init__ is a common pattern in the
examples I've seen. See the section "Calling Superclass Constructors"
starting on page 321 in Learning Python, 2nd ed.

Yup; people still forget the simplest things. *Especially* the
simplest things, in fact.
We need to make a distinction between users and programmers in our
expectations of what kind of errors they will make. Users only need
to *read* a class definition and understand what it does. Programmers
are the group that needs to remember to add a call to the superclass
when they write a new class.

I am a user of the Qt Toolkit, but I would not attempt to add a class
to their existing hierarchy. Nor would I expect Trolltech to provide
me with some kind of robust class-generating function that was
guaranteed to generate an error-free class at any point in their
hierarchy I might chose to insert it.

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.
I think the database problem is different than our Animals example.
With a database, you typically have many users directly changing the
data over a long period of time when errors can accumulate. Maybe
some user runs a "transaction" script to debit one account and credit
another, but his line goes down before the second part completes. So
it makes sense in this case to store only the primitive data, and
accept the overhead of recalculating everything else from that data
whenever it is needed.

Solving the problem of atomic transactions is something else, and will
make your head spin. The accepted design restrictions on databases are
not specific to examples using concurrent transactions.

The database problem is really no different to the case of a zoo - we
want to store a bunch of data about instances/entries of types/tables.
Databases just make this much more natural and robust.
I would expect a Python programmer seeing the Animals_2 example for
the first time, could add a class between Feline and Mammal by the end
of an hour, and be quite confident that nothing was broken. I studied
your example for two hours, and I still don't understand it well
enough to make some basic changes.

Yes, the code is too complex - I don't normally use run-time
metaprogramming to keep a count of cows :) - but that's because I was
straining to find a good solution within the paradigm you had set.

I'd expect a Python professional to undestand the example I gave
fairly quickly, though - but then again, most of the time they will
not need to.
This is a good set of priorities for a production program. For
teaching OOP, I would say clarity is the over-riding concern.

Sure. But close to that should be examples which reflect on the way
you would actually solve a problem.
I find the "piece of machinery" metaphor to be helpful, but no big
deal. Mostly, it re-inforces the idea of encapsulation, keeping the
dirty details of how an alternator works inside the component, and
presenting to the outside world, a simple interface: if you want more
current at that output, put more voltage on this input.

I'm not using metaphors to justify bad programming. I am just having
trouble seeing how one program, side-by-side with another, is
supposedly bad.


Let's see if we can get the Animals_2 example right, based on the
requirements I stated above. It will be a few months before I have my
design platform ready, and it will be far more difficult to discuss
basic ideas in the context of a big system. I think Animals_2.py has
one of everything I will be using.


See above for functional spec.


Don't forget. My basic purpose with the Animals_2 example is to teach
OOP, not the use of a particular database.

The example should follow the purpose, though - find an example which
reflects accurately on good OOP and it will make things considerably
clearer.
I agree the terminology is confusing, and I try to use "object" only
in the most generic sense. "Class" and "instance", as Python uses
them are OK with me.

The question, as I understand it, is whether classes should hold just
methods, or both methods and data. Again, the examples I have seen
show plenty of data being stored as attributes of classes. See
Learning Python, 2nd ed. p. 317. The general form of the class
statement according to Mark Lutz is:

No, that's not the issue. Classes can certainly store data, though
you should hesitate a little before doing it; the problem is that
you're using classes to store non-local data, in particular data about
subclasses of the class. As I said, keeping a count of Mammals in
Felines in Feline would be fine, if odd for most applications; keeping
that count in mammals (and accessing it through Bovine!) is a bug
waiting to happen.
class <name>(superclass, ...): # Assign to name.
data = value # Shared class data
def method(self, ...): # Methods
self.member = value # Per-instance data

Methods are more prevalent in the examples than data, but as far as I
know, there is no warning to the effect: This is possible, but not
good practice.


Adding the Feline class to the existing hierarchy was very easy.

But it required modification of the program code, and would require
modification each time you wanted to add a new animal. Better to
avoid that complexity for your user - unless you're in the business of
selling updates.
Just so we can move on, I will accept your assertion that a truly
bulletproof zoo program would use a database. We still need an
example that doesn't use a database, or anything other than Python's
basic functionality, to teach students about OOP.


The Python policy on private variables, which I agree with, is that we
don't need to prevent deliberate action, just make sure the user
understands a variable is private, not to be altered except by the
provided functions.

Oh, I agree - when people want to access private variables they often
just delete that annoying little 'private' word. The __ syntax at
least gives something searchable.

Generally, though, it's better to solve a problem through making the
program *necessarily* do what you want than through depending on
conventions to save the design.
OK, now I understand. Let's make "Multi_Zoo.py" an example later in
the chapter, not part of the introduction.


I am intending to do exactly that. Because the example is short, you
are tempted to say I really don't need different show functions for
each class. I can add more unique items to each class if I must,
making it look more like a "real program", or you can accept the
requirement that the small differences in the example display are
important.

*Shrugs* fine, if you want that as a requirement. I guess each class
could define a format function, and you could return the function
alongside the number. You would still get the advantage of
inheritance.
How about something like this: We put all data and formatting unique
to each class within that class, but we don't use the trick of each
class calling its parent to generate a complete display. The
sequencing of displays could then be done by some generic function,
calling in sequence the unique display functions from each class.

Sounds reasonable. I'd still be more comfortable if you used a
different example for your students, but it's up to you..
This is excellent. I will add it to my web-page.


OK, I'm convinced we need a third example. Animals_2 will remain part
of the introduction, and our next example Animals_3 will be our
bullet-proof, production-quality,
what-you-better-do-if-someone-is-paying-you, final solution.

I would still like to see some simplification of Animals_JM.py
Although clarity is not specifically on your list of requirements, I
think it does impact maintainability and programmer time.

There are a couple of places where it could be simplified slightly,
but usually my approach to clarifying complex algorithms is just to
blit the code with comments. I also try to avoid complexity by
picking a natural paradigm for the task, of course.
 
D

David MacQuigg

You know, it's interesting, I'm an engineer at a semiconductor
company, and I've written netlist manipulation objects over and over
again with only the most basic oop structure, and they usually work
fine. Associating data with each cell is usually not that hard,
although the show() function showing data from all cells above it (?)
seems kind of strange. This type of problem is a case where object
encapsulation is usually a lot more effective than inheritance. In my
netlist objects, the most basic object is just a name object, which
everything inherits because everything in a netlist has a name.
Beyond that, there's some container inheritance, I came up with an
ordered dictionary for pin data, some simple inheritance where Pin
objects inherit from Net objects, but that's about it.

The basic object structure is pretty simple. The highest level object
is a netlist. The netlist contains a list (just a simple python list)
of cell objects. The cell objects contain a list of nets, a list of
pins, and a list of instances. There are classes for instances, nets,
and pins (which inherit from nets). The instances do refer to their
parent cells, and the pins from the parent are copied in, but that's
about as complicated as things get in the class hierarchy. There's
also a special container class to describe the connections in a cell,
so I can send the connection mesh a net, an instance, or an
instance,pin tuple, and find out what it's connected to. That's
probably the most complicated class, since it uses a few internal
dicts and a type driven interface to resolve the input data. But
that's really about the size of it. Transistors would be instances in
cells (although a transistor cell parent is also in there) and gates
would simply be cells. Now if you had similar cells with different
current or power characteristics, I suppose you'd need to associate
those with an instance class instead of a cell class. Beyond that, I
don't really see why this is such a difficult thing to do. Unless
there's some other overriding goal this needs to accomplish that isn't
in the spec below. You might want to try just coming up with
something like the container hierarchy I have, not worrying about
inheritance, and see if that does the trick for you.

This is the kind of thinking I like. 90% of what we need to do can be
done with the simplest of programming constructs. I know your company
probably considers what you develop proprietary, but would it be
possible for you to contribute an example suitable for teaching OOP to
EE students?

I will probably still keep the Animals example in the intro, since it
shows almost everything we will ever need in the simplest possible
structure. A netlister would be a great example, however, especially
since my students are studying EDA tools.

-- Dave
 
D

David MacQuigg

There's nothing inherently wrong with that, as long as
you're certain you will only ever want one instance of that
piece of data.

In my chip example, you might want to keep a count of the
total number of transistors in your chip as a class variable
of the Transistor class. But that only works if your program
only ever deals with one chip at a time. If there can be more
than one chip, then you'll need a Chip class whose instances
either keep a count of Transistors contained in that chip,
or have a method that can compute the count when needed.

Even if you think you will only deal with one chip at a
time, it would be good design practice to plan on having
a Chip class anyway, in case the requirements change. The
amount of work involved is about the same either way, and
the design which avoids class variables is more flexible.

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

I think the generalization to a "multi-zoo" program probably belongs
in the third example, along with perhaps some techniques like James
showed to make the code more robust by generating the classes rather
than writing them. The first and second examples ( Animals_1 and
Animals_2 need to be very simple.

-- Dave
 
J

James Moughan

David MacQuigg said:
[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.
 
C

Corey Coughlin

Well yeah, my code would be proprietary, but you can probably
reconstruct most of it from the description above. Just start with a
basic outline, add some interface functions on the front end to read
and write spice or verilog netlists, and see how it goes. (Starting
with spice would be a lot easier, it's a much easier format to parse.)
Maybe some of the other EE savvy people here would be willing to help
out. The only real problem with using it as an example is that it
isn't heavily object oriented. It uses some simple inheritance, some
classes, and some operator overloading, but if you want to get into
these complex ideas, it may not be the way to go.

If you're looking to make a functional system of some kind, a basic
netlist hierarchy would definitely be a good example. Given your
descriptions of current analysis, it sounds like you might be doing
something for power analysis. In the basic hierarchy I described, you
can imagine having extra power attributes attached to each cell, and
to get the total power you could easily write a little recursive
instance power summation method. If you want to do something more
complicated, like roll in table driven power data or state dependent
power, you'll need to overlay some sort of simulation state on top of
it, which sounds like it would involve adding state to each net.
Hmm.... if everything inherited from some timescale object, and some
basic functionality was added for transistor primitives, I suppose it
could work. But starting with a good netlist container would be a
great first step. So start with a netlist class that holds a list of
cells, a cell class that holds lists (or dicts) of nets, instances,
and ports, then fill in the instance, net and port classes, and you
should be well on your way.
 

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

Forum statistics

Threads
473,755
Messages
2,569,536
Members
45,020
Latest member
GenesisGai

Latest Threads

Top