\[ [Index](index.md) | [Exercise 6.2](ex6_2.md) | [Exercise 6.4](ex6_4.md) \] # Exercise 6.3 *Objectives:* - Learn how to inspect the internals of functions *Files Modified:* `structure.py` ## (a) Inspecting functions Define a simple function: ```python >>> def add(x,y): 'Adds two things' return x+y >>> ``` Do a `dir()` on the function to look at its attributes. ```python >>> dir(add) ... look at the result ... >>> ``` Get some basic information such as the function name, defining module name, and documentation string. ```python >>> add.__name__ 'add' >>> add.__module__ '__main__' >>> add.__doc__ 'Adds two things' >>> ``` The `__code__` attribute of a function has low-level information about the function implementation. See if you can look at this and determine the number of required arguments and names of local variables. ## (b) Using the inspect module Use the inspect module to get calling information about the function: ```python >>> import inspect >>> sig = inspect.signature(add) >>> sig >>> sig.parameters mappingproxy(OrderedDict([('x', ), ('y', )])) >>> tuple(sig.parameters) ('x', 'y') >>> ``` ## (c) Putting it Together In [Exercise 6.1](ex6_1.md), you created a class `Structure` that defined a generalized `__init__()`, `__setattr__()`, and `__repr__()` method. That class required a user to define a `_fields` class variable like this: ```python class Stock(Structure): _fields = ('name','shares','price') ``` The problem with this class is that the `__init__()` function didn't have a useful argument signature for the purposes of help and keyword argument passing. In [Exercise 6.2](ex6_2.md), you did a sneaky trick involving a special `self._init()` function. For example: ```python class Stock(Structure): _fields = ('name', 'shares', 'price') def __init__(self, name, shares, price): self._init() ... ``` This gave a useful signature, but now the class is just weird because the user has to provide both the `_fields` variable and the `__init__()` method. Your task is to eliminate the `_fields` variable using some function inspection techniques. First, notice that you can get the argument signature from `Stock` as follows: ```python >>> import inspect >>> sig = inspect.signature(Stock) >>> tuple(sig.parameters) ('name', 'shares', 'price') >>> ``` Perhaps you could set the `_fields` variable from the argument signature of `__init__()`. Add a class method `set_fields(cls)` to `Structure` that inspects the `__init__()` function, and sets the `_fields` variable appropriately. You should use your new function like this: ```python class Stock(Structure): def __init__(self, name, shares, price): self._init() ... Stock.set_fields() ``` The resulting class should work the same way as before: ```python >>> s = Stock(name='GOOG', shares=100, price=490.1) >>> s Stock('GOOG',100,490.1) >>> s.shares = 50 >>> s.share = 50 Traceback (most recent call last): File "", line 1, in File "structure.py", line 12, in __setattr__ raise AttributeError('No attribute %s' % name) AttributeError: No attribute share >>> ``` Verify the slightly modified `Stock` class with your unit tests again. There will still be failures, but nothing should change from the previous exercise. At this point, it's all still a bit "hacky", but you're making progress. You have a Stock structure class with a useful `__init__()` function, there is a useful representation string, and the `__setattr__()` method restricts the set of attribute names. The extra step of having to invoke `set_fields()` is a bit odd, but we'll get back to that. \[ [Solution](soln6_3.md) | [Index](index.md) | [Exercise 6.2](ex6_2.md) | [Exercise 6.4](ex6_4.md) \] ---- `>>>` Advanced Python Mastery `...` A course by [dabeaz](https://www.dabeaz.com) `...` Copyright 2007-2023 ![](https://i.creativecommons.org/l/by-sa/4.0/88x31.png). This work is licensed under a [Creative Commons Attribution-ShareAlike 4.0 International License](http://creativecommons.org/licenses/by-sa/4.0/)