Initial commit
This commit is contained in:
147
Exercises/ex6_5.md
Normal file
147
Exercises/ex6_5.md
Normal file
@@ -0,0 +1,147 @@
|
||||
\[ [Index](index.md) | [Exercise 6.4](ex6_4.md) | [Exercise 7.1](ex7_1.md) \]
|
||||
|
||||
# Exercise 6.5
|
||||
|
||||
*Objectives:*
|
||||
|
||||
- Learn how to define a proper callable object
|
||||
|
||||
Files Modified : `validate.py`
|
||||
|
||||
Back in [Exercise 4.3](ex4_3.md), you created a series of `Validator` classes
|
||||
for performing different kinds of type and value checks. For example:
|
||||
|
||||
```python
|
||||
>>> from validate import Integer
|
||||
>>> Integer.check(1)
|
||||
>>> Integer.check('hello')
|
||||
Traceback (most recent call last):
|
||||
File "<stdin>", line 1, in <module>
|
||||
File "validate.py", line 21, in check
|
||||
raise TypeError(f'Expected {cls.expected_type}')
|
||||
TypeError: Expected <class 'int'>
|
||||
>>>
|
||||
```
|
||||
|
||||
You could use the validators in functions like this:
|
||||
|
||||
```python
|
||||
>>> def add(x, y):
|
||||
Integer.check(x)
|
||||
Integer.check(y)
|
||||
return x + y
|
||||
|
||||
>>>
|
||||
```
|
||||
|
||||
In this exercise, we're going to take it just one step further.
|
||||
|
||||
## (a) Creating a Callable Object
|
||||
|
||||
In the file `validate.py`, start by creating a class like this:
|
||||
|
||||
```python
|
||||
# validate.py
|
||||
...
|
||||
|
||||
class ValidatedFunction:
|
||||
def __init__(self, func):
|
||||
self.func = func
|
||||
|
||||
def __call__(self, *args, **kwargs):
|
||||
print('Calling', self.func)
|
||||
result = self.func(*args, **kwargs)
|
||||
return result
|
||||
```
|
||||
|
||||
Test the class by applying it to a function:
|
||||
|
||||
```python
|
||||
>>> def add(x, y):
|
||||
return x + y
|
||||
|
||||
>>> add = ValidatedFunction(add)
|
||||
>>> add(2, 3)
|
||||
Calling <function add at 0x1014df598>
|
||||
5
|
||||
>>>
|
||||
```
|
||||
|
||||
## (b) Enforcement
|
||||
|
||||
Modify the `ValidatedFunction` class so that it enforces value checks
|
||||
attached via function annotations. For example:
|
||||
|
||||
```python
|
||||
>>> def add(x: Integer, y:Integer):
|
||||
return x + y
|
||||
>>> add = ValidatedFunction(add)
|
||||
>>> add(2,3)
|
||||
5
|
||||
>>> add('two','three')
|
||||
Traceback (most recent call last):
|
||||
File "<stdin>", line 1, in <module>
|
||||
File "validate.py", line 67, in __call__
|
||||
self.func.__annotations__[name].check(val)
|
||||
File "validate.py", line 21, in check
|
||||
raise TypeError(f'Expected {cls.expected_type}')
|
||||
TypeError: expected <class 'int'>
|
||||
>>>>
|
||||
```
|
||||
|
||||
Hint: To do this, play around with signature binding. Use the `bind()`
|
||||
method of `Signature` objects to bind function arguments to argument
|
||||
names. Then cross reference this information with the
|
||||
`__annotations__` attribute to get the different validator classes.
|
||||
|
||||
Keep in mind, you're making an object that looks like a function, but
|
||||
it's really not. There is magic going on behind the scenes.
|
||||
|
||||
## (c) Use as a Method (Challenge)
|
||||
|
||||
A custom callable often presents problems if used as a custom method.
|
||||
For example, try this:
|
||||
|
||||
```python
|
||||
class Stock:
|
||||
def __init__(self, name, shares, price):
|
||||
self.name = name
|
||||
self.shares = shares
|
||||
self.price = price
|
||||
|
||||
@property
|
||||
def cost(self):
|
||||
return self.shares * self.price
|
||||
|
||||
def sell(self, nshares:Integer):
|
||||
self.shares -= nshares
|
||||
sell = ValidatedFunction(sell) # Fails
|
||||
```
|
||||
|
||||
You'll find that the wrapped `sell()` fails miserably:
|
||||
|
||||
```python
|
||||
>>> s = Stock('GOOG', 100, 490.1)
|
||||
>>> s.sell(10)
|
||||
Traceback (most recent call last):
|
||||
File "<stdin>", line 1, in <module>
|
||||
File "validate.py", line 64, in __call__
|
||||
bound = self.signature.bind(*args, **kwargs)
|
||||
File "/usr/local/lib/python3.6/inspect.py", line 2933, in bind
|
||||
return args[0]._bind(args[1:], kwargs)
|
||||
File "/usr/local/lib/python3.6/inspect.py", line 2848, in _bind
|
||||
raise TypeError(msg) from None
|
||||
TypeError: missing a required argument: 'nshares'
|
||||
>>>
|
||||
```
|
||||
|
||||
Bonus: Figure out why it fails--but don't spend too much time fooling around with it.
|
||||
|
||||
\[ [Solution](soln6_5.md) | [Index](index.md) | [Exercise 6.4](ex6_4.md) | [Exercise 7.1](ex7_1.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