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.