The sell() method takes the nshares argument, however inside the method the variable shares is used instead. This patch changes the name of the argument according to the variable used within the method.
198 lines
5.2 KiB
Markdown
198 lines
5.2 KiB
Markdown
\[ [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 created 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, shares):
|
|
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/)
|