4.0 KiB
[Index](index.md) | [Exercise 7.3](ex7_3.md) | [Exercise 7.5](ex7_5.md)
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](soln7_4.md) | [Index](index.md) | [Exercise 7.3](ex7_3.md) | [Exercise 7.5](ex7_5.md)
>>> Advanced Python Mastery
... A course by dabeaz
... Copyright 2007-2023
. This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License