Initial commit
This commit is contained in:
197
Exercises/ex6_2.md
Normal file
197
Exercises/ex6_2.md
Normal file
@@ -0,0 +1,197 @@
|
||||
\[ [Index](index.md) | [Exercise 6.1](ex6_1.md) | [Exercise 6.3](ex6_3.md) \]
|
||||
|
||||
# Exercise 6.2
|
||||
|
||||
*Objectives:*
|
||||
|
||||
- Learn more about scoping rules
|
||||
- Learn some scoping tricks
|
||||
|
||||
*Files modified:* `structure.py`, `stock.py`
|
||||
|
||||
In the last exercise, you create a class `Structure` that made it easy to define
|
||||
data structures. For example:
|
||||
|
||||
```python
|
||||
class Stock(Structure):
|
||||
_fields = ('name','shares','price')
|
||||
```
|
||||
|
||||
This works fine except that a lot of things are pretty weird about the `__init__()`
|
||||
function. For example, if you ask for help using `help(Stock)`, you don't get
|
||||
any kind of useful signature. Also, keyword argument passing doesn't work. For
|
||||
example:
|
||||
|
||||
```python
|
||||
>>> help(Stock)
|
||||
... look at output ...
|
||||
|
||||
>>> s = Stock(name='GOOG', shares=100, price=490.1)
|
||||
Traceback (most recent call last):
|
||||
File "<stdin>", line 1, in <module>
|
||||
TypeError: __init__() got an unexpected keyword argument 'price'
|
||||
>>>
|
||||
```
|
||||
|
||||
In this exercise, we're going to look at a different approach to the problem.
|
||||
|
||||
## (a) Show me your locals
|
||||
|
||||
First, try an experiment by defining the following class:
|
||||
|
||||
```python
|
||||
>>> class Stock:
|
||||
def __init__(self, name, shares, price):
|
||||
print(locals())
|
||||
|
||||
>>>
|
||||
```
|
||||
|
||||
Now, try running this:
|
||||
|
||||
```python
|
||||
>>> s = Stock('GOOG', 100, 490.1)
|
||||
{'self': <__main__.Stock object at 0x100699b00>, 'price': 490.1, 'name': 'GOOG', 'shares': 100}
|
||||
>>>
|
||||
```
|
||||
|
||||
Notice how the locals dictionary contains all of the arguments passed
|
||||
to `__init__()`. That's interesting. Now, define the following function
|
||||
and class definitions:
|
||||
|
||||
```python
|
||||
>>> def _init(locs):
|
||||
self = locs.pop('self')
|
||||
for name, val in locs.items():
|
||||
setattr(self, name, val)
|
||||
|
||||
>>> class Stock:
|
||||
def __init__(self, name, shares, price):
|
||||
_init(locals())
|
||||
```
|
||||
|
||||
In this code, the `_init()` function is used to automatically
|
||||
initialize an object from a dictionary of passed local variables.
|
||||
You'll find that `help(Stock)` and keyword arguments work perfectly.
|
||||
|
||||
```python
|
||||
>>> s = Stock(name='GOOG', price=490.1, shares=50)
|
||||
>>> s.name
|
||||
'GOOG'
|
||||
>>> s.shares
|
||||
50
|
||||
>>> s.price
|
||||
490.1
|
||||
>>>
|
||||
```
|
||||
|
||||
## (b) Frame Hacking
|
||||
|
||||
One complaint about the last part is that the `__init__()` function
|
||||
now looks pretty weird with that call to `locals()` inserted into it.
|
||||
You can get around that though if you're willing to do a bit of stack
|
||||
frame hacking. Try this variant of the `_init()` function:
|
||||
|
||||
```python
|
||||
>>> import sys
|
||||
>>> def _init():
|
||||
locs = sys._getframe(1).f_locals # Get callers local variables
|
||||
self = locs.pop('self')
|
||||
for name, val in locs.items():
|
||||
setattr(self, name, val)
|
||||
>>>
|
||||
```
|
||||
|
||||
In this code, the local variables are extracted from the stack frame of the caller.
|
||||
Here is a modified class definition:
|
||||
|
||||
```python
|
||||
>>> class Stock:
|
||||
def __init__(self, name, shares, price):
|
||||
_init()
|
||||
|
||||
>>> s = Stock('GOOG', 100, 490.1)
|
||||
>>> s.name
|
||||
'GOOG'
|
||||
>>> s.shares
|
||||
100
|
||||
>>>
|
||||
```
|
||||
|
||||
At this point, you're probably feeling rather disturbed. Yes, you just wrote a function that reached
|
||||
into the stack frame of another function and examined its local variables.
|
||||
|
||||
## (c) Putting it Together
|
||||
|
||||
Taking the ideas in the first two parts, delete the `__init__()` method that was originally part of the
|
||||
`Structure` class. Next, add an `_init()` method like this:
|
||||
|
||||
```python
|
||||
# structure.py
|
||||
import sys
|
||||
|
||||
class Structure:
|
||||
...
|
||||
@staticmethod
|
||||
def _init():
|
||||
locs = sys._getframe(1).f_locals
|
||||
self = locs.pop('self')
|
||||
for name, val in locs.items():
|
||||
setattr(self, name, val)
|
||||
...
|
||||
```
|
||||
|
||||
Note: The reason this is defined as a `@staticmethod` is that the `self` argument
|
||||
is obtained from the locals--there's no need to additionally have it passed as
|
||||
an argument to the method itself (admittedly this is a bit subtle).
|
||||
|
||||
Now, modify your `Stock` class so that it looks like the following:
|
||||
|
||||
```python
|
||||
# stock.py
|
||||
from structure import Structure
|
||||
|
||||
class Stock(Structure):
|
||||
_fields = ('name','shares','price')
|
||||
def __init__(self, name, shares, price):
|
||||
self._init()
|
||||
|
||||
@property
|
||||
def cost(self):
|
||||
return self.shares * self.price
|
||||
|
||||
def sell(self, nshares):
|
||||
self.shares -= shares
|
||||
```
|
||||
|
||||
Verify that the class works properly, supports keyword arguments, and has a
|
||||
proper help signature.
|
||||
|
||||
```python
|
||||
>>> s = Stock(name='GOOG', price=490.1, shares=50)
|
||||
>>> s.name
|
||||
'GOOG'
|
||||
>>> s.shares
|
||||
50
|
||||
>>> s.price
|
||||
490.1
|
||||
>>> help(Stock)
|
||||
... look at the output ...
|
||||
>>>
|
||||
```
|
||||
|
||||
Run your unit tests in `teststock.py` again. You should see at least one more test pass. Yay!
|
||||
|
||||
At this point, it's going to look like we just took a giant step backwards. Not
|
||||
only do the classes need the `__init__()` method, they also need the `_fields`
|
||||
variable for some of the other methods to work (`__repr__()` and `__setattr__()`). Plus,
|
||||
the use of `self._init()` looks pretty hacky. We'll work on this, but be patient.
|
||||
|
||||
\[ [Solution](soln6_2.md) | [Index](index.md) | [Exercise 6.1](ex6_1.md) | [Exercise 6.3](ex6_3.md) \]
|
||||
|
||||
----
|
||||
`>>>` Advanced Python Mastery
|
||||
`...` A course by [dabeaz](https://www.dabeaz.com)
|
||||
`...` Copyright 2007-2023
|
||||
|
||||
. This work is licensed under a [Creative Commons Attribution-ShareAlike 4.0 International License](http://creativecommons.org/licenses/by-sa/4.0/)
|
||||
Reference in New Issue
Block a user