renumbering chapters >= 19

This commit is contained in:
Luciano Ramalho
2021-09-10 12:34:39 -03:00
parent cbd13885fc
commit 4ae4096c4c
154 changed files with 7 additions and 1134 deletions

View File

@@ -0,0 +1,70 @@
import warnings
class metaMetaBunch(type):
"""
metaclass for new and improved "Bunch": implicitly defines
__slots__, __init__ and __repr__ from variables bound in class scope.
An instance of metaMetaBunch (a class whose metaclass is metaMetaBunch)
defines only class-scope variables (and possibly special methods, but
NOT __init__ and __repr__!). metaMetaBunch removes those variables from
class scope, snuggles them instead as items in a class-scope dict named
__dflts__, and puts in the class a __slots__ listing those variables'
names, an __init__ that takes as optional keyword arguments each of
them (using the values in __dflts__ as defaults for missing ones), and
a __repr__ that shows the repr of each attribute that differs from its
default value (the output of __repr__ can be passed to __eval__ to make
an equal instance, as per the usual convention in the matter).
"""
def __new__(cls, classname, bases, classdict):
""" Everything needs to be done in __new__, since type.__new__ is
where __slots__ are taken into account.
"""
# define as local functions the __init__ and __repr__ that we'll
# use in the new class
def __init__(self, **kw):
""" Simplistic __init__: first set all attributes to default
values, then override those explicitly passed in kw.
"""
for k in self.__dflts__: setattr(self, k, self.__dflts__[k])
for k in kw: setattr(self, k, kw[k])
def __repr__(self):
""" Clever __repr__: show only attributes that differ from the
respective default values, for compactness.
"""
rep = [ '%s=%r' % (k, getattr(self, k)) for k in self.__dflts__
if getattr(self, k) != self.__dflts__[k]
]
return '%s(%s)' % (classname, ', '.join(rep))
# build the newdict that we'll use as class-dict for the new class
newdict = { '__slots__':[], '__dflts__':{},
'__init__':__init__, '__repr__':__repr__, }
for k in classdict:
if k.startswith('__'):
# special methods &c: copy to newdict, warn about conflicts
if k in newdict:
warnings.warn("Can't set attr %r in bunch-class %r" % (
k, classname))
else:
newdict[k] = classdict[k]
else:
# class variables, store name in __slots__ and name and
# value as an item in __dflts__
newdict['__slots__'].append(k)
newdict['__dflts__'][k] = classdict[k]
# finally delegate the rest of the work to type.__new__
return type.__new__(cls, classname, bases, newdict)
class MetaBunch(metaclass=metaMetaBunch):
""" For convenience: inheriting from MetaBunch can be used to get
the new metaclass (same as defining __metaclass__ yourself).
"""
__metaclass__ = metaMetaBunch

View File

@@ -0,0 +1,38 @@
import pytest
from bunch import MetaBunch
class Point(MetaBunch):
""" 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)