list/classmethod problems
Scott David Daniels
scott.daniels at acm.org
Mon Mar 13 15:45:34 EST 2006
ahart wrote:
> I'm pretty new to python and am trying to write a fairly small
> application to learn more about the language. I'm noticing some
> unexpected behavior in using lists in some classes to hold child
> objects. Here is some abbreviated code to help me explain.
>
> When I run this script, I expect to see the following string printed:
>
> "<parent><item>one</item><item>two</item></parent>"
>
> Instead, I see the following:
>
>
> "<parent><item>one</item><item>two</item><item>three</item></parent>"
>
> Apparently, the p1 instance somehow thinks that the i3 instance is in
> its list. The i3 instance should instead be in the list for p2. By the
> way, when I call the __str__() method of p2, I get the same results as
> when I do it for p1. The list appears to be acting as if it were a
> static member - which it is not.
>
> I do have some @classmethod methods in these classes in my complete
> script. Would that confuse the interpreter into thinking that other
> members are also static?
>
> I can't seem to find any information on this problem. Does anyone have
> any ideas?
First, a few kvetches:
(1) Submit the _actual_code_ when you have problems, not paraphrases:
> class Item(object)
should have been (and probably was):
class Item(object):
> def __str__(self):
> ...
> s += "</parent>"
should have been (and probably was):
def __str__(self):
...
s += "</parent>"
return s
(2) You must have been a Java or C++ programmer. Don't be so stingy
with access to your variables and methods (all the __xxx methods
and variables). Just use a single underscore (at least for the
methods) telling users, "don't muck with this." The double
underscores make figuring out what's going interactively awkward.
(3) The if statement takes a boolean expression, not parens around a
boolean expression. "if a > b:" is the way to write a test, not
"if(a>b):" (which is why I think Java or C++).
And now to your actual question:
> ####################################
> class Item(object)
> __text = ""
The line above is unnecessary (and deceptive). It sets a _class_
variable named _Item__text to a zero-length string.
> def __get_text(self):
> return self.__text
> def __set_text(self, value):
> self.__text = value
> text = property(fget=__get_text, fset=__set_text)
>
> def __init__(self, text=""):
> self.__text = text
This line sets an _instance_ variable named _Item__text to the arg.
>
> #...some other methods here...
>
> class Parent(object):
> __items = []
The line above is unnecessary (and deceptive). You want
a list-per instance, but you are accessing the class variable below
> def __get_items(self):
> return self.__items
> items = property(fget=__get_items)
>
> def addItem(self, item):
> """Adds an Item object to the internal list."""
> self.__items.append(item)
>
> def __init__(self, items=[]):
> if(len(items)>0):
> for item in items:
> self.addItem(item)
If you wrote this method as either:
def __init__(self, items=[]):
self.__items = []
for item in items:
self.addItem(item)
or:
def __init__(self, items=[]):
self.__items = list(items)
You would have the effect you want -- a list per instance.
By the way,
> if(len(items)>0):
> for item in items:
> self.addItem(item)
Two complaints about this:
First, the "Pythonic way to test for a non-zero length list is "if var"
Second, even if the test were:
> if items:
> for item in items:
> self.addItem(item)
That code is just plain slower (and less clear) than:
for item in items:
self.addItem(item)
If items is zero length, it goes through the loop body zero times.
>
> def __str__(self):
> s = "<parent>"
> for item in self.__items:
> s += "<item>%s</item>" % item.text
> s += "</parent>"
>
> #...some other methods here...
>
> if(__name__=="__main__"):
> i1 = Item("one")
> i2 = Item("two")
> i3 = Item("three")
>
> p1 = Parent([i1, i2])
> p2 = Parent([i3])
>
> print str(p1)
> ####################################
>
Now you get the output you expect.
By-the-by, why shouldn't the first class be simply?:
class Item(object)
def __init__(self, text=""):
self.text = text
Finally, defining __repr__ rather than __str__ will make it
easier to fiddle with your code interactively w/o keeping anything
from working.
--Scott David Daniels
scott.daniels at acm.org
More information about the Python-list
mailing list