Initial commit
This commit is contained in:
177
Exercises/ex7_4.md
Normal file
177
Exercises/ex7_4.md
Normal file
@@ -0,0 +1,177 @@
|
||||
\[ [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:
|
||||
|
||||
```python
|
||||
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.
|
||||
|
||||
```python
|
||||
>>> 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:
|
||||
|
||||
```python
|
||||
>>> methods = {
|
||||
'__init__' : __init__,
|
||||
'cost' : cost,
|
||||
'sell' : sell }
|
||||
|
||||
>>>
|
||||
```
|
||||
|
||||
Finally, create the `Stock` class object:
|
||||
|
||||
```python
|
||||
>>> 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:
|
||||
|
||||
```python
|
||||
# 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:
|
||||
|
||||
```python
|
||||
>>> 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:
|
||||
|
||||
```python
|
||||
# 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:
|
||||
|
||||
```python
|
||||
# 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:
|
||||
|
||||
```python
|
||||
_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](https://www.dabeaz.com)
|
||||
`...` Copyright 2007-2023
|
||||
|
||||
. This work is licensed under a [Creative Commons Attribution-ShareAlike 4.0 International License](http://creativecommons.org/licenses/by-sa/4.0/)
|
||||
Reference in New Issue
Block a user