Skip to content

Latest commit

 

History

History
145 lines (114 loc) · 3.66 KB

ex6_4.md

File metadata and controls

145 lines (114 loc) · 3.66 KB

[ Index | Exercise 6.3 | Exercise 6.5 ]

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:

>>> 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.

>>> 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, you wrote code that inspected the signature of the __init__() method to set the attribute names in a _fields class variable. For example:

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:

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

Stock.create_init()

The resulting class should work exactly the name way as before:

>>> 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, you experimented with namedtuple objects in the collections module. Just to refresh your memory, here is how they worked:

>>> 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:

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

[ Solution | Index | Exercise 6.3 | Exercise 6.5 ]


>>> Advanced Python Mastery
... A course by dabeaz
... Copyright 2007-2023

. This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License