__author__ = 'keithd' ''' A few simple classes that illustrate some of the complexity (weirdness?) of class variables in Python. A few of the key points: 1) Classes and their instances involve namespaces arranged in a hierarchy. An instance has its own local namespace (in which reside value holders for each of its instance variables). If an instance cannot find a variable in its local namespace, it goes up one level in the hierarchy, to its class' namespace and hunts for the variable there. If it's not there, it will move up the class hierarchy to superclass namespaces. If it goes to the top level and still does not find anything, you get an error message. 2) It is possible, but often bad style (and dangerous) for instances to have an instance variable with the same name as a class variable. In the examples below, we do this...and you can see the (often confusing) consequences. ''' class Book: _library = [] # list of all books objects ever created _page_count = 0 # total number of pages of all books in the library def __init__(self,title,length): self.title = title print("Previous total page count: %s" % self._page_count) # This accesses the class variable self._page_count = length # AT THIS POINT, Python creates a local version of _page_count Book._page_count += length # Update the class variable Book._library.append(self) # Update the class variable print("Updated total page count: %s" % Book._page_count) # Roughly define a "classic book" as one that has more than 1 edition. class Classic(Book): _most_editions = 0 # This gets modified as instances of Classic are created. def __init__(self,title,length,editions): Book.__init__(self,title,length) self.editions = editions Classic._most_editions = max(Classic._most_editions,editions) ''' ********** SAMPLE USAGE *********** >>> from classtest import * >>> a = Classic("war and peace",1000,7) Previous total page count: 0 Updated total page count: 1000 >>> b = Classic("gone with the wind", 800,12) Previous total page count: 1000 Updated total page count: 1800 >>> a._library [, ] >>> Book._library [, ] >>> In the command-line activity above, note that books a and b have their own, local, versions of _page_count, so any attempts to access or modify _page_count via self._page_count will only involve (and affect) this local (instance) variable. However, BEFORE that local version was created, (in __init__), Python had to climb the namespace hierarchy to Book and use the class variable. The local version is not created UNTIL your code tries to MODIFY it via an assignment statement, i.e. "self._page_count = length". Once the local variable is created, if you still want access to the higher-level version (i.e. the class variable), you need to write Book._page_count. ''' # This is a HORROR because it modifies the class variable, even though it looks like a reference to an # instance variable. The Class and all instances can be affected by this despicable crime! class Horror(Book): def __init__(self,title,length): Book.__init__(self,title,length) self._library.append('blood, blood and more blood') # Modifies the CLASS variable - BAD news! # This is much less scary, since it explicitly creates the local (instance) variable, so now all references to # self._library will access the local list, not Book._library. class Horrorlite(Book): def __init__(self,title,length): Book.__init__(self,title,length) self._library = [] # Creates a local (instance) variable and a startup data structure self._library.append('blood, blood and more blood') # Modifies the instance variable - ok class Horrormax(Book): # This is maximally scary, because it LOOKS like we are creating a (safe, local) instance variable, but # by assigning it to Book._library, we are creating a POINTER to the data structure that Book._library # also points to. We are NOT making a copy. So modifications to self._library may LOOK like changes to a # instance variable, but they are actually changes to the data structure that is pointed to by Book._library # and self._library. def __init__(self,title,length): Book.__init__(self,title,length) self.library = Book._library # Creating a pointer self._library.append('blood, blood and more blood') ''' Observe that there is nothing unusual about creating a pointer to a data structure instead of a copy. Python does it all the time. Try this at the terminal: >>> x = [1,2,3] >>> y = x >>> y.append(33) >>> x [1, 2, 3, 33] >>> y [1, 2, 3, 33] Now we'll create one each of the 3 types of horror books and observe what happens to Book._library and self._library h = Horror('halloween',350) Previous total page count: 1800 Updated total page count: 2150 >>> Book._library [, , , 'blood, blood and more blood'] >>> h._library [, , , 'blood, blood and more blood'] >>> hl = Horrorlite('fargo',289) Previous total page count: 2150 Updated total page count: 2439 >>> hl._library ['blood, blood and more blood'] >>> Book._library [, , , 'blood, blood and more blood', ] NOTE: No additional blood in Book._library. That's good. >>> hm = Horrormax('silence of the lambs',200) Previous total page count: 2439 Updated total page count: 2639 >>> hm._library [, , , 'blood, blood and more blood', , , 'blood, blood and more blood'] >>> Book._library [, , , 'blood, blood and more blood', , , 'blood, blood and more blood'] NOTE: More blood! That movie always gives me nightmares!! Finally, note that h._library is affected by all these updates, but hl._library is not...as expected. >>> h._library [, , , 'blood, blood and more blood', , , 'blood, blood and more blood'] >>> hl._library ['blood, blood and more blood'] '''