Exercise 6.1
Introduction
In Exercise 5.1, you defined a class Stock
that
represented a holding of stock. In this exercise, we will use that
class. So, restart the Python shell and perform these steps:
>>> ================================ RESTART ================================
>>> from stock import Stock
>>> goog = Stock('GOOG',100,490.10)
>>> ibm = Stock('IBM',50, 91.23)
>>>
(a) Representation of Instances
At the interactive shell, inspect the underlying dictionaries of the two instances you created:
>>> goog.__dict__
... look at the output ...
>>> ibm.__dict__
... look at the output ...
>>>
(b) Modification of Instance Data
Try setting a new attribute on one of the above instances:
>>> goog.date = '6/11/2007'
>>> goog.__dict__
... look at output ...
>>> ibm.__dict__
... look at output ...
>>>
In the above output, you’ll notice that the goog
instance has a
attribute date
whereas the ibm
instance does not. It is important
to note that Python really doesn’t place any restrictions on
attributes. For example, the attributes of an instance are not
limited to those set up in the __init__()
method.
Instead of setting an attribute, try placing a new value directly into
the __dict__
object:
>>> goog.__dict__['time'] = '9:45am'
>>> goog.time
'9:45am'
>>>
Here, you really notice the fact that an instance is just a layer on top of a dictionary. Note: it should be emphasized that direct manipulation of the dictionary is uncommon—you should always write your code to use the (.) syntax.
(c) The role of classes
The definitions that make up a class definition are shared by all instances of that class. Notice, that all instances have a link back to their associated class:
>>> goog.__class__
... look at output ...
>>> ibm.__class__
... look at output ...
>>>
Try calling a method on the instances:
>>> goog.cost()
49010.0
>>> ibm.cost()
4561.5
>>>
Notice that the name cost is not defined in either goog.__dict__
or ibm.__dict__
. Instead, it is being supplied by the class
dictionary. Try this:
>>> Stock.__dict__['cost']
... look at output ...
>>>
Try calling the cost()
method directly through the dictionary:
>>> Stock.__dict__['cost'](goog)
49010.0
>>> Stock.__dict__['cost'](ibm)
4561.5
>>>
Notice how you are calling the function defined in the class
definition and how the self
argument gets the instance.
Try adding a new attribute to the Stock
class:
>>> Stock.foo = 42
>>>
Notice how this new attribute now shows up on all of the instances:
>>> goog.foo
42
>>> ibm.foo
42
>>>
However, notice that it is not part of the instance dictionary:
>>> goog.__dict__
... look at output and notice there is no 'foo' attribute ...
>>>
The reason you can access the foo
attribute on instances is that
Python always checks the class dictionary if it can’t find something
on the instance itself.
(d) Bound methods
A subtle feature of Python is that invoking a method actually involves two steps and something known as a bound method. For example:
>>> s = goog.sell
>>> s
<bound method Stock.sell of Stock('GOOG',100,490.1)>
>>> s(25)
75
>>>
Bound methods actually contain all of the pieces needed to call a method. For instance, they keep a record of the function implementing the method:
>>> s.__func__
<function sell at 0x10049af50>
>>>
This is the same value as found in the Stock
dictionary.
>>> Stock.__dict__['sell']
<function sell at 0x10049af50>
>>>
Bound methods also record the instance, which is the self
argument.
>>> s.__self__
Stock('GOOG',75,490.1)
>>>
When you invoke the function using ()
all of the pieces come
together. For example, calling s(25)
actually does this:
>>> s.__func__(s.__self__, 25) # Same as s(25)
50
>>>