\[ [Index](index.md) | [Exercise 7.5](ex7_5.md) | [Exercise 8.1](ex8_1.md) \]

# Exercise 7.6

*Objectives:*

- Metaclasses in action
- Explode your brain

*Files Modified:* `structure.py`, `validate.py`

## (a) The Final Frontier

In [Exercise 7.3](ex7_3.md), we made it possible to define type-checked structures as follows:

```python
from validate import String, PositiveInteger, PositiveFloat
from structure import Structure

class Stock(Structure):
    name = String()
    shares = PositiveInteger()
    price = PositiveFloat()

    @property
    def cost(self):
        return self.shares * self.price

    def sell(self, nshares: PositiveInteger):
        self.shares -= nshares
```

There are a lot of things going on under the covers.  However, one annoyance
concerns all of those type-name imports at the top (e.g., `String`, `PositiveInteger`, etc.).
That's just the kind of thing that might lead to a `from validate import *` statement.
One interesting thing about a metaclass is that it can be used to control
the process by which a class gets defined.  This includes managing the
environment of a class definition itself.  Let's tackle those imports.

The first step in managing all of the validator names is to collect
them.   Go to the file `validate.py` and modify the `Validator` base
class with this extra bit of code involving `__init_subclass__()` again:

```python
# validate.py

class Validator:
    ...

    # Collect all derived classes into a dict
    validators = { }
    @classmethod
    def __init_subclass__(cls):
        cls.validators[cls.__name__] = cls
```

That's not much, but it's creating a little namespace of all of the `Validator`
subclasses.  Take a look at it:

```python
>>> from validate import Validator
>>> Validator.validators
{'Float': <class 'validate.Float'>,
 'Integer': <class 'validate.Integer'>,
 'NonEmpty': <class 'validate.NonEmpty'>,
 'NonEmptyString': <class 'validate.NonEmptyString'>,
 'Positive': <class 'validate.Positive'>,
 'PositiveFloat': <class 'validate.PositiveFloat'>,
 'PositiveInteger': <class 'validate.PositiveInteger'>,
 'String': <class 'validate.String'>,
 'Typed': <class 'validate.Typed'>}
>>>
```

Now that you've done that, let's inject this namespace into namespace
of classes defined from `Structure`. Define the following metaclass:

```python
# structure.py
...

from validate import Validator
from collections import ChainMap

class StructureMeta(type):
    @classmethod
    def __prepare__(meta, clsname, bases):
        return ChainMap({}, Validator.validators)
        
    @staticmethod
    def __new__(meta, name, bases, methods):
        methods = methods.maps[0]
        return super().__new__(meta, name, bases, methods)

class Structure(metaclass=StructureMeta):
    ...
```

In this code, the `__prepare__()` method is making a special `ChainMap` mapping that consists
of an empty dictionary and a dictionary of all of the defined validators.  The empty dictionary
that's listed first is going to collect all of the definitions made inside the class body.
The `Validator.validators` dictionary is going to make all of the type definitions available 
to for use as descriptors or argument type annotations.

The `__new__()` method discards extra the validator dictionary and
passes the remaining definitions onto the type constructor.  It's
ingenious, but it lets you drop the annoying imports:

```python
# stock.py

from structure import Structure

class Stock(Structure):
    name = String()
    shares = PositiveInteger()
    price = PositiveFloat()

    @property
    def cost(self):
        return self.shares * self.price

    def sell(self, nshares: PositiveInteger):
        self.shares -= nshares
```

## (b) Stare in Amazement

Try running your `teststock.py` unit tests across this new file. Most of them should be
passing now.   For kicks, try your `Stock` class with some of the earlier code
for tableformatting and reading data.  It should all work.

```python
>>> from stock import Stock
>>> from reader import read_csv_as_instances
>>> portfolio = read_csv_as_instances('Data/portfolio.csv', Stock)
>>> portfolio
[Stock('AA',100,32.2), Stock('IBM',50,91.1), Stock('CAT',150,83.44), Stock('MSFT',200,51.23), Stock('GE',95,40.37), Stock('MSFT',50,65.1), Stock('IBM',100,70.44)]
>>> from tableformat import create_formatter, print_table
>>> formatter = create_formatter('text')
>>> print_table(portfolio, ['name','shares','price'], formatter)
      name     shares      price 
---------- ---------- ---------- 
        AA        100       32.2 
       IBM         50       91.1 
       CAT        150      83.44 
      MSFT        200      51.23 
        GE         95      40.37 
      MSFT         50       65.1 
       IBM        100      70.44 
>>> 
```

Again, marvel at the final `stock.py` file and observe how clean the
code looks.  Just try not think about everything that is happening
under the hood with the `Structure` base class.

\[ [Solution](soln7_6.md) | [Index](index.md) | [Exercise 7.5](ex7_5.md) | [Exercise 8.1](ex8_1.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/)