2021-11-12 11:33:12 -03:00

79 lines
2.4 KiB
Python

"""
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: No slots left for: '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>
extra = ', '.join(kwargs)
raise AttributeError(f'No slots left for: {extra!r}')
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[]