link experiment
This commit is contained in:
620
Notes/05_Object_model/01_Dicts_revisited.md
Normal file
620
Notes/05_Object_model/01_Dicts_revisited.md
Normal file
@@ -0,0 +1,620 @@
|
||||
# 5.1 Dictionaries Revisited
|
||||
|
||||
The Python object system is largely based on an implementation based on dictionaries. This
|
||||
section discusses that.
|
||||
|
||||
### Dictionaries, Revisited
|
||||
|
||||
Remember that a dictionary is a collection of names values.
|
||||
|
||||
```python
|
||||
stock = {
|
||||
'name' : 'GOOG',
|
||||
'shares' : 100,
|
||||
'price' : 490.1
|
||||
}
|
||||
```
|
||||
|
||||
Dictionaries are commonly used for simple data structures.
|
||||
However, they are used for critical parts of the interpreter and may be the *most important type of data in Python*.
|
||||
|
||||
### Dicts and Modules
|
||||
|
||||
In a module, a dictionary holds all of the global variables and functions.
|
||||
|
||||
```python
|
||||
# foo.py
|
||||
|
||||
x = 42
|
||||
def bar():
|
||||
...
|
||||
|
||||
def spam():
|
||||
...
|
||||
```
|
||||
|
||||
If we inspect `foo.__dict__` or `globals()`, you'll see the dictionary.
|
||||
|
||||
```python
|
||||
{
|
||||
'x' : 42,
|
||||
'bar' : <function bar>,
|
||||
'spam' : <function spam>
|
||||
}
|
||||
```
|
||||
|
||||
### Dicts and Objects
|
||||
|
||||
User defined objects also use dictionaries for both instance data and classes.
|
||||
In fact, the entire object system is mostly an extra layer that's put on top of dictionaries.
|
||||
|
||||
A dictionary holds the instance data, `__dict__`.
|
||||
|
||||
```python
|
||||
>>> s = Stock('GOOG', 100, 490.1)
|
||||
>>> s.__dict__
|
||||
{'name' : 'GOOG','shares' : 100, 'price': 490.1 }
|
||||
```
|
||||
|
||||
You populate this dict (and instance) when assigning to `self`.
|
||||
|
||||
```python
|
||||
class Stock(object):
|
||||
def __init__(self,name,shares,price):
|
||||
self.name = name
|
||||
self.shares = shares
|
||||
self.price = price
|
||||
```
|
||||
|
||||
The instance data, `self.__dict__`, looks like this:
|
||||
|
||||
```python
|
||||
{
|
||||
'name': 'GOOG',
|
||||
'shares': 100,
|
||||
'price': 490.1
|
||||
}
|
||||
```
|
||||
|
||||
**Each instance gets its own private dictionary.**
|
||||
|
||||
```python
|
||||
s = Stock('GOOG',100,490.1) # {'name' : 'GOOG','shares' : 100, 'price': 490.1 }
|
||||
t = Stock('AAPL',50,123.45) # {'name' : 'AAPL','shares' : 50, 'price': 123.45 }
|
||||
```
|
||||
|
||||
If you created 200 instances of some class, there are 100 dictionaries sitting around holding data.
|
||||
|
||||
### Class Members
|
||||
|
||||
A separate dictionary also holds the methods.
|
||||
|
||||
```python
|
||||
class Stock(object):
|
||||
def __init__(self,name,shares,price):
|
||||
self.name = name
|
||||
self.shares = shares
|
||||
self.price = price
|
||||
|
||||
def cost(self):
|
||||
return self.shares * self.price
|
||||
|
||||
def sell(self,nshares):
|
||||
self.shares -= nshares
|
||||
```
|
||||
|
||||
The dictionary is in `Stock.__dict__`.
|
||||
|
||||
```python
|
||||
{
|
||||
'cost': <function>,
|
||||
'sell': <function>,
|
||||
'__init__': <function>
|
||||
}
|
||||
```
|
||||
|
||||
### Instances and Classes
|
||||
|
||||
Instances and classes are linked together.
|
||||
The `__class__` attribute refers back to the class.
|
||||
|
||||
```python
|
||||
>>> s = Stock('GOOG', 100, 490.1)
|
||||
>>> s.__dict__
|
||||
{ 'name': 'GOOG', 'shares': 100, 'price': 490.1 }
|
||||
>>> s.__class__
|
||||
<class '__main__.Stock'>
|
||||
>>>
|
||||
```
|
||||
|
||||
The instance dictionary holds data unique to each instance, whereas the class dictionary holds data collectively shared by *all* instances.
|
||||
|
||||
### Attribute Access
|
||||
|
||||
When you work with objects, you access data and methods using the `.` operator.
|
||||
|
||||
```python
|
||||
x = obj.name # Getting
|
||||
obj.name = value # Setting
|
||||
del obj.name # Deleting
|
||||
```
|
||||
|
||||
These operations are directly tied to the dictionaries sitting underneath the covers.
|
||||
|
||||
### Modifying Instances
|
||||
|
||||
Operations that modify an object update the underlying dictionary.
|
||||
|
||||
```python
|
||||
>>> s = Stock('GOOG', 100, 490.1)
|
||||
>>> s.__dict__
|
||||
{ 'name':'GOOG', 'shares': 100, 'price': 490.1 }
|
||||
>>> s.shares = 50 # Setting
|
||||
>>> s.date = '6/7/2007' # Setting
|
||||
>>> s.__dict__
|
||||
{ 'name': 'GOOG', 'shares': 50, 'price': 490.1, 'date': '6/7/2007' }
|
||||
>>> del s.shares # Deleting
|
||||
>>> s.__dict__
|
||||
{ 'name': 'GOOG', 'price': 490.1, 'date': '6/7/2007' }
|
||||
>>>
|
||||
```
|
||||
|
||||
### Reading Attributes
|
||||
|
||||
Suppose you read an attribute on an instance.
|
||||
|
||||
```python
|
||||
x = obj.name
|
||||
```
|
||||
|
||||
The attribute may exist in two places:
|
||||
|
||||
* Local instance dictionary.
|
||||
* Class dictionary.
|
||||
|
||||
Both dictionaries must be checked. First, check in local `__dict__`.
|
||||
If not found, look in `__dict__` of class through `__class__`.
|
||||
|
||||
```python
|
||||
>>> s = Stock(...)
|
||||
>>> s.name
|
||||
'GOOG'
|
||||
>>> s.cost()
|
||||
49010.0
|
||||
>>>
|
||||
```
|
||||
|
||||
This lookup scheme is how the members of a *class* get shared by all instances.
|
||||
|
||||
### How inheritance works
|
||||
|
||||
Classes may inherit from other classes.
|
||||
|
||||
```python
|
||||
class A(B, C):
|
||||
...
|
||||
```
|
||||
|
||||
The base classes are stored in a tuple in each class.
|
||||
|
||||
```python
|
||||
>>> A.__bases__
|
||||
(<class '__main__.B'>, <class '__main__.C'>)
|
||||
>>>
|
||||
```
|
||||
|
||||
This provides a link to parent classes.
|
||||
|
||||
### Reading Attributes with Inheritance
|
||||
|
||||
First, check in local `__dict__`. If not found, look in `__dict__` of class through `__class__`.
|
||||
If not found in class, look in base classes through `__bases__`.
|
||||
|
||||
### Reading Attributes with Single Inheritance
|
||||
|
||||
In inheritance hierarchies, attributes are found by walking up the inheritance tree.
|
||||
|
||||
```python
|
||||
class A(object): pass
|
||||
class B(A): pass
|
||||
class C(A): pass
|
||||
class D(B): pass
|
||||
class E(D): pass
|
||||
```
|
||||
With Single Inheritance, there ia single path to the top.
|
||||
You stop with the first match.
|
||||
|
||||
### Method Resolution Order or MRO
|
||||
|
||||
Python precomputes an inheritance chain and stores it in the *MRO* attribute on the class.
|
||||
|
||||
```python
|
||||
>>> E.__mro__
|
||||
(<class '__main__.E'>, <class '__main__.D'>,
|
||||
<class '__main__.B'>, <class '__main__.A'>,
|
||||
<type 'object'>)
|
||||
>>>
|
||||
```
|
||||
|
||||
This chain is called the **Method Resolutin Order**.
|
||||
The find the attributes, Python walks the MRO. First match, wins.
|
||||
|
||||
### MRO in Multiple Inheritance
|
||||
|
||||
There is no single path to the top with multiple inheritance.
|
||||
Let's take a look at an example.
|
||||
|
||||
```python
|
||||
class A(object): pass
|
||||
class B(object): pass
|
||||
class C(A, B): pass
|
||||
class D(B): pass
|
||||
class E(C, D): pass
|
||||
```
|
||||
|
||||
What happens when we do?
|
||||
|
||||
```python
|
||||
e = E()
|
||||
e.attr
|
||||
```
|
||||
|
||||
A similar search process is carried out, but what is the order? That's a problem.
|
||||
|
||||
Python uses *cooperative multiple inheritance*.
|
||||
These are some rules about class ordering:
|
||||
|
||||
* Children before parents
|
||||
* Parents go in order
|
||||
|
||||
The MRO is computed using those rules.
|
||||
|
||||
```python
|
||||
>>> E.__mro__
|
||||
(
|
||||
<class 'E'>,
|
||||
<class 'C'>,
|
||||
<class 'A'>,
|
||||
<class 'D'>,
|
||||
<class 'B'>,
|
||||
<class 'object'>)
|
||||
>>>
|
||||
```
|
||||
|
||||
### An Odd Code Reuse
|
||||
|
||||
Consider two completely unrelated objects:
|
||||
|
||||
```python
|
||||
class Dog(object):
|
||||
def noise(self):
|
||||
return 'Bark'
|
||||
|
||||
def chase(self):
|
||||
return 'Chasing!'
|
||||
|
||||
class LoudDog(Dog):
|
||||
def noise(self):
|
||||
# Code commonality with LoudBike
|
||||
return super().noise().upper()
|
||||
```
|
||||
|
||||
And
|
||||
|
||||
```python
|
||||
class Bike(object):
|
||||
def noise(self):
|
||||
return 'On Your Left'
|
||||
|
||||
def pedal(self):
|
||||
return 'Pedaling!'
|
||||
|
||||
class LoudBike(Bike):
|
||||
def noise(self):
|
||||
# Code commonality with LoudDog
|
||||
return super().noise().upper()
|
||||
```
|
||||
|
||||
There is a code commonality in the implementation of `LoudDog.noise()` and
|
||||
`LoudBike.noise()`. In fact, the code is exactly the same.
|
||||
|
||||
### The "Mixin" Pattern
|
||||
|
||||
The *Mixin* pattern is a class with a fragment of code.
|
||||
|
||||
```python
|
||||
class Loud(object):
|
||||
def noise(self):
|
||||
return super().noise().upper()
|
||||
```
|
||||
|
||||
This class is not usable in isolation.
|
||||
It mixes with other classes via inheritance.
|
||||
|
||||
```python
|
||||
class LoudDog(Loud, Dog):
|
||||
pass
|
||||
|
||||
class LoudBike(Loud, Bike):
|
||||
pass
|
||||
```
|
||||
|
||||
This is one of the primary uses of multiple inheritance in Python.
|
||||
|
||||
### Why `super()`
|
||||
|
||||
Always use `super()` when overriding methods.
|
||||
|
||||
```python
|
||||
class Loud(object):
|
||||
def noise(self):
|
||||
return super().noise().upper()
|
||||
```
|
||||
|
||||
`super()` delegates to the *next class* on the MRO.
|
||||
|
||||
The tricky bit is that you don't know what it is when you create the Mixin.
|
||||
|
||||
### Some Cautions
|
||||
|
||||
Multiple inheritance is a powerful tool. Remember that with power comes responsibility.
|
||||
Frameworks / libraries sometimes use it for advanced features involving composition of components.
|
||||
|
||||
## Exercises
|
||||
|
||||
In Exercise 4.1, you defined a class `Stock` that represented a holding of stock.
|
||||
In this exercise, we will use that class.
|
||||
|
||||
### (a) Representation of Instances
|
||||
|
||||
At the interactive shell, inspect the underlying dictionaries of the two instances you created:
|
||||
|
||||
```python
|
||||
>>> from stock import Stock
|
||||
>>> goog = Stock('GOOG',100,490.10)
|
||||
>>> ibm = Stock('IBM',50, 91.23)
|
||||
>>> 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:
|
||||
|
||||
```python
|
||||
>>> 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.
|
||||
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:
|
||||
|
||||
```python
|
||||
>>> 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:
|
||||
|
||||
```python
|
||||
>>> goog.__class__
|
||||
... look at output ...
|
||||
>>> ibm.__class__
|
||||
... look at output ...
|
||||
>>>
|
||||
```
|
||||
|
||||
Try calling a method on the instances:
|
||||
|
||||
```python
|
||||
>>> 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:
|
||||
|
||||
```python
|
||||
>>> Stock.__dict__['cost']
|
||||
... look at output ...
|
||||
>>>
|
||||
```
|
||||
|
||||
Try calling the `cost()` method directly through the dictionary:
|
||||
|
||||
```python
|
||||
>>> 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:
|
||||
|
||||
```python
|
||||
>>> Stock.foo = 42
|
||||
>>>
|
||||
```
|
||||
|
||||
Notice how this new attribute now shows up on all of the instances:
|
||||
|
||||
```python
|
||||
>>> goog.foo
|
||||
42
|
||||
>>> ibm.foo
|
||||
42
|
||||
>>>
|
||||
```
|
||||
|
||||
However, notice that it is not part of the instance dictionary:
|
||||
|
||||
```python
|
||||
>>> 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.
|
||||
|
||||
This part of the exercise illustrates something known as a class
|
||||
variable. Suppose, for instance, you have a class like this:
|
||||
|
||||
```python
|
||||
class Foo(object):
|
||||
a = 13 # Class variable
|
||||
def __init__(self,b):
|
||||
self.b = b # Instance variable
|
||||
```
|
||||
|
||||
In this class, the variable `a`, assigned in the body of the class itself, is a *class variable*.
|
||||
It is shared by all of the instances that get created.
|
||||
|
||||
```python
|
||||
>>> f = Foo(10)
|
||||
>>> g = Foo(20)
|
||||
>>> f.a # Inspect the class variable (same for both instances)
|
||||
13
|
||||
>>> g.a
|
||||
13
|
||||
>>> f.b # Inspect the instance variable (differs)
|
||||
10
|
||||
>>> g.b
|
||||
20
|
||||
>>> Foo.a = 42 # Change the value of the class variable
|
||||
>>> f.a
|
||||
42
|
||||
>>> g.a
|
||||
42
|
||||
>>>
|
||||
```
|
||||
|
||||
### (d) Bound Methods
|
||||
|
||||
A subtle feature of Python is that invoking a method actually involves
|
||||
two steps and something known as a bound method.
|
||||
|
||||
```python
|
||||
>>> s = goog.sell
|
||||
>>> s
|
||||
<bound method Stock.sell of Stock('GOOG',100,490.1)>
|
||||
>>> s(25)
|
||||
>>> goog.shares
|
||||
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:
|
||||
|
||||
```python
|
||||
>>> s.__func__
|
||||
<function sell at 0x10049af50>
|
||||
>>>
|
||||
```
|
||||
|
||||
This is the same value as found in the `Stock` dictionary.
|
||||
|
||||
```python
|
||||
>>> Stock.__dict__['sell']
|
||||
<function sell at 0x10049af50>
|
||||
>>>
|
||||
```
|
||||
|
||||
Take a close look at both references do `0x10049af50`. They are both the same in `s` and `Stock.__dict__['sell']`.
|
||||
Bound methods also record the instance, which is the `self` argument.
|
||||
|
||||
```python
|
||||
>>> 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:
|
||||
|
||||
```python
|
||||
>>> s.__func__(s.__self__, 25) # Same as s(25)
|
||||
>>> goog.shares
|
||||
50
|
||||
>>>
|
||||
```
|
||||
|
||||
### (e) Inheritance
|
||||
|
||||
Make a new class that inherits from `Stock`.
|
||||
|
||||
```python
|
||||
>>> class NewStock(Stock):
|
||||
def yow(self):
|
||||
print('Yow!')
|
||||
|
||||
>>> n = NewStock('ACME', 50, 123.45)
|
||||
>>> n.cost()
|
||||
6172.50
|
||||
>>> n.yow()
|
||||
Yow!
|
||||
>>>
|
||||
```
|
||||
|
||||
Inheritance is implemented by extending the search process for attributes.
|
||||
The `__bases__` attribute has a tuple of the immediate parents:
|
||||
|
||||
```python
|
||||
>>> NewStock.__bases__
|
||||
(<class 'stock.Stock'>,)
|
||||
>>>
|
||||
```
|
||||
|
||||
The `__mro__` attribute has a tuple of all parents, in the order that
|
||||
they will be searched for attributes.
|
||||
|
||||
```python
|
||||
>>> NewStock.__mro__
|
||||
(<class '__main__.NewStock'>, <class 'stock.Stock'>, <class 'object'>)
|
||||
>>>
|
||||
```
|
||||
|
||||
Here’s how the `cost()` method of instance `n` above would be found:
|
||||
|
||||
```python
|
||||
>>> for cls in n.__class__.__mro__:
|
||||
if 'cost' in cls.__dict__:
|
||||
break
|
||||
|
||||
>>> cls
|
||||
<class '__main__.Stock'>
|
||||
>>> cls.__dict__['cost']
|
||||
<function cost at 0x101aed598>
|
||||
>>>
|
||||
```
|
||||
|
||||
[Next](02_Classes_encapsulation)
|
||||
Reference in New Issue
Block a user