Classes and subclasses
Python classes are used to give greater structure and object orientated facilities to many projects, so it's important to have a firm grasp on class behaviors and syntax. Even if you've used object orientated design in other language like Java or PHP, Python has some particularities that you need to understand. Listing A-15 shows a simple Python class.
Listing A-15. Python class syntax and behavior
class Drink(): """ Drink class """ def __init__(self,size): self.size = size # Used to display object instance def __str__(self): return 'Drink: size %s' % (self.size) # Helper method for size in ounces def sizeinoz(self): if self.size == "small": return "8 oz" elif self.size == "medium": return "12 oz" elif self.size == "large": return "24 oz" else: return "Unknown" thedrink = Drink("small") print(thedrink) print("thedrink is %s " % thedrink.sizeinoz())
The first thing to notice about
listing A-15 is it uses the Python class
keyword to
declare the start of a Python class. Next, you can see various
class methods which are declared with the same def
keyword as standard Python methods. Notice all the class methods
use the self
argument to gain access to the
class/object instance, a construct that's prevalent in almost all
Python classes, since Python doesn't grant access to the instance
transparently in class methods like other object oriented languages
(e.g. in Java you can just reference this inside a method without
it being a method argument).
Now let's move on the some calls
made on the Drink class in listing A-15. The first call in listing
A-15 Drink("small")
creates an instance of the
Drink
class. When this call is invoked, Python first
triggers the __init__
method of the class to
initialize the instance. Notice the two arguments
__init__(self,size)
. The self
variable represents the
instance of the object itself, while the size
variable
represents an input variable provided by the instance creator,
which in this case is assigned the small
value. Inside
the __init__
method, the self.size
instance variable is created and assigned the size
variable value.
The second call in listing A-15
print(thedrink)
outputs the thedrink
class instance which prints Drink: size small
. What's
interesting about the output is that it's generated by the class
method __str__
, which as you can see in listing A-15
returns a string with the value of the size
instance
variable. If the class didn't have a __str__
method
definition, the call to print(thedrink)
would output
something like <__main__.Drink object at
0xcfb410>
, which is the rather worthless/unfriendly
in-memory representation of the instance. This is the purpose of
the __str__
method in classes, to output a friendly
instance value.
The third call in listing A-15
print("thedrink is %s " % thedrink.sizeinoz())
invokes
the sizeinoz()
class method on the instance and
outputs a string based on the size
instance variable
created by __init__
. Notice the
sizeinoz(self)
method declares self
--
just like __init__
and __str__
-- to be
able to access the instance value and perform its logic.
Now that you have a basic
understanding of Python classes, let's explore Python subclasses.
Listing A-16 illustrates a subclass created from the
Drink
class in listing A-15.
Listing A-16. Python subclass syntax and behavior
class Coffee(Drink): """ Coffee class """ beans = "arabica" def __init__(self,*args,**kwargs): Drink.__init__(self,*args) self.temperature = kwargs['temperature'] # Used to display object instance def __str__(self): return 'Coffee: beans %s, size %s, temperature %s' % (self.beans,self.size,self.temperature) thecoffee = Coffee("large",temperature="cold") print(thecoffee) print("thecoffee is %s " % thecoffee.sizeinoz())
Notice the class
Coffee(Drink)
syntax in listing A-16, which is
Python's inheritance syntax (i.e. Coffee
is a subclass
or inherits its behavior from Drink
). In addition,
notice that besides the subclass having its own __init__
and __str__
methods, it also has the beans
class field.
Now let's creates some calls on
the Coffee
subclass in listing A-16. The first call in
listing A-16 Coffee("large",temperature="cold")
creates a Coffee
instance which is a subclass of the
Drink
instance. Notice the Coffee
instance uses the arguments "large",temperature="cold"
and in accordance with this pattern, the __init__
method definition is def __init__(self,*args,**kwargs)
.
Next, is the initialization of
the parent class with Drink.__init__(self,*args)
, the
*args
value in this case is large
and
matches the __init__
method of the parent
Drink
class. Followed is the creation of the
self.temperature
instance variable which is assigned
the value from kwargs['temperature']
(e.g. the value
that corresponds to the key temperature
).
The second call in listing A-16
print(thecoffee)
outputs the thecoffee
class instance which prints Coffee: beans arabica, size
large, temperature cold
. Because the Coffee
class has its own __str__
method, it's used to output
the object instance -- overriding the same method from the parent
class -- if there were no such method definition, then Python would
look for a __str__
method in the parent class (i.e.
Drink
) and use that, and if no __str__
method were found, then Python would output the in-memory
representation of the instance.
The third and final call in
listing A-16 print("thecoffee is %s " %
thecoffee.sizeinoz())
invokes the sizeinoz()
class method on the instance and outputs a string based on the
size
instance variable. The interesting bit about this
call is it demonstrates object orientated polymorphism, the
Coffee
instance calls a method in the parent
Drink
class and works just like if it were a
Drink
instance.