\[ [Index](index.md) | [Exercise 6.3](ex6_3.md) | [Exercise 6.5](ex6_5.md) \]

# Exercise 6.4

*Objectives:*

- Learn to create code with `exec()`

## (a) Experiment with exec()

Define a fragment of Python code in a string and try running it:

```python
>>> code = '''
for i in range(n):
    print(i, end=' ')
'''
>>> n = 10
>>> exec(code)
0 1 2 3 4 5 6 7 8 9
>>>
```

That's interesting, but executing random code fragments is not
especially useful. A more interesting use of `exec()` is in making
code such as functions, methods, or classes.  Try this example in
which we make an `__init__()` function for a class.

```python
>>> class Stock:
        _fields = ('name', 'shares', 'price')

>>> argstr = ','.join(Stock._fields)
>>> code = f'def __init__(self, {argstr}):\n'
>>> for name in Stock._fields:
        code += f'    self.{name} = {name}\n'
>>> print(code)
def __init__(self, name,shares,price):
    self.name = name
    self.shares = shares
    self.price = price

>>> locs = { }
>>> exec(code, locs)
>>> Stock.__init__ = locs['__init__']

>>> # Now try the class
>>> s = Stock('GOOG', 100, 490.1)
>>> s.name
'GOOG'
>>> s.shares
100
>>> s.price
490.1
>>> 
```

In this example, an `__init__()` function is made directly from the `_fields` variable.  
There are no weird hacks involving a special `_init()` method or stack frames.

## (b) Creating an `__init__()` function

In [Exercise 6.3](ex6_3.md), you wrote code that inspected the
signature of the `__init__()` method to set the attribute names
in a `_fields` class variable.   For example:

```python
class Stock(Structure):
    def __init__(self, name, shares, price):
        self._init()

Stock.set_fields()
```

Instead of inspecting the `__init__()` method, write a class method
`create_init(cls)` that creates an `__init__()` method from the value of
`_fields`.  Use the `exec()` function to do this as shown above. 
Here's how a user will use it:

```python
class Stock(Structure):
    _fields = ('name', 'shares', 'price')

Stock.create_init()
```

The resulting class should work exactly the name 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 "<stdin>", line 1, in <module>
  File "structure.py", line 12, in __setattr__
    raise AttributeError('No attribute %s' % name)
AttributeError: No attribute share
>>> 
```

Modify the `Stock` class in progress to use the `create_init()` function as shown.  
Verify with your unit tests as before.

While you're at it, get rid of the `_init()` and `set_fields()`
methods on the `Structure` class--that approach was kind of weird.

## (c) Named Tuples

In [Exercise 2.1](ex2_1.md), you experimented with `namedtuple` objects
in the `collections` module.  Just to refresh your memory, here is how
they worked:

```python
>>> from collections import namedtuple
>>> Stock = namedtuple('Stock', ['name', 'shares', 'price'])
>>> s = Stock('GOOG', 100, 490.1)
>>> s.name
'GOOG'
>>> s.shares
100
>>> s[1]
100
>>>
```

Under the covers, the `namedtuple()` function is creating code as a string
and executing it using `exec()`.   Look at the code and marvel:

```python
>>> import inspect
>>> print(inspect.getsource(namedtuple))
... look at the output ...
>>>
```

\[ [Solution](soln6_4.md) | [Index](index.md) | [Exercise 6.3](ex6_3.md) | [Exercise 6.5](ex6_5.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/)
