Skip to content

Latest commit

 

History

History
177 lines (133 loc) · 4.02 KB

ex7_4.md

File metadata and controls

177 lines (133 loc) · 4.02 KB

[ Index | Exercise 7.3 | Exercise 7.5 ]

Exercise 7.4

Objectives:

  • Learn about the low-level steps involved in creating a class

Files Modified: validate.py, structure.py

In this exercise, we look at the mechanics of how classes are actually created.

(a) Class creation

Recall, from earlier exercises, we defined a simple class Stock that looked like this:

class Stock:
    def __init__(self,name,shares,price):
        self.name = name
        self.shares = shares
        self.price = price
    def cost(self):
        return self.shares*self.price
    def sell(self,nshares):
        self.shares -= nshares

What we're going to do here is create the class manually. Start out by just defining the methods as normal Python functions.

>>> def __init__(self,name,shares,price):
        self.name = name
        self.shares = shares
        self.price = price

>>> def cost(self):
        return self.shares*self.price

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

>>>

Next, make a methods dictionary:

>>> methods = {
         '__init__' : __init__,
         'cost' : cost,
         'sell' : sell } 

>>>

Finally, create the Stock class object:

>>> Stock = type('Stock',(object,),methods)
>>> s = Stock('GOOG',100,490.10)
>>> s.name
'GOOG'
>>> s.cost()
49010.0
>>> s.sell(25)
>>> s.shares
75
>>>

Congratulations, you just created a class. A class is really nothing more than a name, a tuple of base classes, and a dictionary holding all of the class contents. type() is a constructor that creates a class for you if you supply these three parts.

(b) Typed structures

In the structure.py file, define the following function:

# structure.py

...
def typed_structure(clsname, **validators):
    cls = type(clsname, (Structure,), validators)
    return cls

This function is somewhat similar to the namedtuple() function in that it creates a class. Try it out:

>>> from validate import String, PositiveInteger, PositiveFloat
>>> from structure import typed_structure
>>> Stock = typed_structure('Stock', name=String(), shares=PositiveInteger(), price=PositiveFloat())
>>> s = Stock('GOOG', 100, 490.1)
>>> s.name
'GOOG'
>>> s
Stock('GOOG', 100, 490.1)
>>>

You might find the seams of your head starting to pull apart about now.

(c) Making a lot of classes

There are other situations where direct usage of the type() constructor might be advantageous. Consider this bit of code:

# validate.py
...

class Typed(Validator):
    expected_type = object
    @classmethod
    def check(cls, value):
        if not isinstance(value, cls.expected_type):
            raise TypeError(f'expected {cls.expected_type}')
        super().check(value)

class Integer(Typed):
    expected_type = int

class Float(Typed):
    expected_type = float

class String(Typed):
    expected_type = str
...

Wow is the last part of that annoying and repetitive. Change it to use a table of desired type classes like this:

# validate.py
...

_typed_classes = [
    ('Integer', int),
    ('Float', float),
    ('String', str) ]

globals().update((name, type(name, (Typed,), {'expected_type':ty}))
                 for name, ty in _typed_classes)

Now, if you want to have more type classes, you just add them to the table:

_typed_classes = [
    ('Integer', int),
    ('Float', float),
    ('Complex', complex),
    ('Decimal', decimal.Decimal),
    ('List', list),
    ('Bool', bool),
    ('String', str) ]

Admit it, that's kind of cool and saves a lot of typing (at the keyboard).

[ Solution | Index | Exercise 7.3 | Exercise 7.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