renumbering chapters >= 19
This commit is contained in:
77
24-class-metaprog/metabunch/from3.6/bunch.py
Normal file
77
24-class-metaprog/metabunch/from3.6/bunch.py
Normal file
@@ -0,0 +1,77 @@
|
||||
"""
|
||||
The `MetaBunch` metaclass is a simplified version of the
|
||||
last example in the _How a Metaclass Creates a Class_ section
|
||||
of _Chapter 4: Object Oriented Python_ from
|
||||
[_Python in a Nutshell, 3rd edition_](https://learning.oreilly.com/library/view/python-in-a/9781491913833)
|
||||
by Alex Martelli, Anna Ravenscroft, and Steve Holden.
|
||||
|
||||
Here are a few tests. ``bunch_test.py`` has a few more.
|
||||
|
||||
# tag::BUNCH_POINT_DEMO_1[]
|
||||
>>> class Point(Bunch):
|
||||
... x = 0.0
|
||||
... y = 0.0
|
||||
... color = 'gray'
|
||||
...
|
||||
>>> Point(x=1.2, y=3, color='green')
|
||||
Point(x=1.2, y=3, color='green')
|
||||
>>> p = Point()
|
||||
>>> p.x, p.y, p.color
|
||||
(0.0, 0.0, 'gray')
|
||||
>>> p
|
||||
Point()
|
||||
|
||||
# end::BUNCH_POINT_DEMO_1[]
|
||||
|
||||
# tag::BUNCH_POINT_DEMO_2[]
|
||||
|
||||
>>> Point(x=1, y=2, z=3)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
AttributeError: 'Point' object has no attribute 'z'
|
||||
>>> p = Point(x=21)
|
||||
>>> p.y = 42
|
||||
>>> p
|
||||
Point(x=21, y=42)
|
||||
>>> p.flavor = 'banana'
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
AttributeError: 'Point' object has no attribute 'flavor'
|
||||
|
||||
# end::BUNCH_POINT_DEMO_2[]
|
||||
"""
|
||||
|
||||
# tag::METABUNCH[]
|
||||
class MetaBunch(type): # <1>
|
||||
def __new__(meta_cls, cls_name, bases, cls_dict): # <2>
|
||||
|
||||
defaults = {} # <3>
|
||||
|
||||
def __init__(self, **kwargs): # <4>
|
||||
for name, default in defaults.items(): # <5>
|
||||
setattr(self, name, kwargs.pop(name, default))
|
||||
if kwargs: # <6>
|
||||
setattr(self, *kwargs.popitem())
|
||||
|
||||
def __repr__(self): # <7>
|
||||
rep = ', '.join(f'{name}={value!r}'
|
||||
for name, default in defaults.items()
|
||||
if (value := getattr(self, name)) != default)
|
||||
return f'{cls_name}({rep})'
|
||||
|
||||
new_dict = dict(__slots__=[], __init__=__init__, __repr__=__repr__) # <8>
|
||||
|
||||
for name, value in cls_dict.items(): # <9>
|
||||
if name.startswith('__') and name.endswith('__'): # <10>
|
||||
if name in new_dict:
|
||||
raise AttributeError(f"Can't set {name!r} in {cls_name!r}")
|
||||
new_dict[name] = value
|
||||
else: # <11>
|
||||
new_dict['__slots__'].append(name)
|
||||
defaults[name] = value
|
||||
return super().__new__(meta_cls, cls_name, bases, new_dict) # <12>
|
||||
|
||||
|
||||
class Bunch(metaclass=MetaBunch): # <13>
|
||||
pass
|
||||
# end::METABUNCH[]
|
||||
59
24-class-metaprog/metabunch/from3.6/bunch_test.py
Normal file
59
24-class-metaprog/metabunch/from3.6/bunch_test.py
Normal file
@@ -0,0 +1,59 @@
|
||||
import pytest
|
||||
|
||||
from bunch import Bunch
|
||||
|
||||
class Point(Bunch):
|
||||
""" A point has x and y coordinates, defaulting to 0.0,
|
||||
and a color, defaulting to 'gray'—and nothing more,
|
||||
except what Python and the metaclass conspire to add,
|
||||
such as __init__ and __repr__
|
||||
"""
|
||||
x = 0.0
|
||||
y = 0.0
|
||||
color = 'gray'
|
||||
|
||||
|
||||
def test_init_defaults():
|
||||
p = Point()
|
||||
assert repr(p) == 'Point()'
|
||||
|
||||
|
||||
def test_init():
|
||||
p = Point(x=1.2, y=3.4, color='red')
|
||||
assert repr(p) == "Point(x=1.2, y=3.4, color='red')"
|
||||
|
||||
|
||||
def test_init_wrong_argument():
|
||||
with pytest.raises(AttributeError) as exc:
|
||||
p = Point(x=1.2, y=3.4, flavor='coffee')
|
||||
assert "no attribute 'flavor'" in str(exc.value)
|
||||
|
||||
|
||||
def test_slots():
|
||||
p = Point()
|
||||
with pytest.raises(AttributeError) as exc:
|
||||
p.z = 5.6
|
||||
assert "no attribute 'z'" in str(exc.value)
|
||||
|
||||
|
||||
def test_dunder_permitted():
|
||||
class Cat(Bunch):
|
||||
name = ''
|
||||
weight = 0
|
||||
|
||||
def __str__(self):
|
||||
return f'{self.name} ({self.weight} kg)'
|
||||
|
||||
cheshire = Cat(name='Cheshire')
|
||||
assert str(cheshire) == 'Cheshire (0 kg)'
|
||||
|
||||
|
||||
def test_dunder_forbidden():
|
||||
with pytest.raises(AttributeError) as exc:
|
||||
class Cat(Bunch):
|
||||
name = ''
|
||||
weight = 0
|
||||
|
||||
def __init__(self):
|
||||
pass
|
||||
assert "Can't set '__init__' in 'Cat'" in str(exc.value)
|
||||
Reference in New Issue
Block a user