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