3.8 KiB
[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, you created a series of Validator classes
for performing different kinds of type and value checks. For example:
>>> 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:
>>> 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:
# 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:
>>> 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:
>>> 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:
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:
>>> 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
... Copyright 2007-2023
. This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License