diff --git a/25-class-metaprog/evalsupport.py b/25-class-metaprog/evalsupport.py new file mode 100644 index 0000000..a7f3b5b --- /dev/null +++ b/25-class-metaprog/evalsupport.py @@ -0,0 +1,29 @@ +# tag::BEGINNING[] +print('<[100]> evalsupport module start') + +def deco_alpha(cls): + print('<[200]> deco_alpha') + + def inner_1(self): + print('<[300]> deco_alpha:inner_1') + + cls.method_y = inner_1 + return cls + +# end::BEGINNING[] +# tag::META_ALEPH[] +class MetaAleph(type): + print('<[400]> MetaAleph body') + + def __init__(cls, name, bases, dic): + print('<[500]> MetaAleph.__init__') + + def inner_2(self): + print('<[600]> MetaAleph.__init__:inner_2') + + cls.method_z = inner_2 + +# end::META_ALEPH[] +# tag::END[] +print('<[700]> evalsupport module end') +# end::END[] diff --git a/25-class-metaprog/evaltime.py b/25-class-metaprog/evaltime.py new file mode 100644 index 0000000..299098a --- /dev/null +++ b/25-class-metaprog/evaltime.py @@ -0,0 +1,49 @@ +from evalsupport import deco_alpha + +print('<[1]> evaltime module start') + + +class ClassOne(): + print('<[2]> ClassOne body') + + def __init__(self): + print('<[3]> ClassOne.__init__') + + def __del__(self): + print('<[4]> ClassOne.__del__') + + def method_x(self): + print('<[5]> ClassOne.method_x') + + class ClassTwo(object): + print('<[6]> ClassTwo body') + + +@deco_alpha +class ClassThree(): + print('<[7]> ClassThree body') + + def method_y(self): + print('<[8]> ClassThree.method_y') + + +class ClassFour(ClassThree): + print('<[9]> ClassFour body') + + def method_y(self): + print('<[10]> ClassFour.method_y') + + +if __name__ == '__main__': + print('<[11]> ClassOne tests', 30 * '.') + one = ClassOne() + one.method_x() + print('<[12]> ClassThree tests', 30 * '.') + three = ClassThree() + three.method_y() + print('<[13]> ClassFour tests', 30 * '.') + four = ClassFour() + four.method_y() + + +print('<[14]> evaltime module end') diff --git a/25-class-metaprog/evaltime_meta.py b/25-class-metaprog/evaltime_meta.py new file mode 100644 index 0000000..41ed098 --- /dev/null +++ b/25-class-metaprog/evaltime_meta.py @@ -0,0 +1,53 @@ +from evalsupport import deco_alpha +from evalsupport import MetaAleph + +print('<[1]> evaltime_meta module start') + + +@deco_alpha +class ClassThree(): + print('<[2]> ClassThree body') + + def method_y(self): + print('<[3]> ClassThree.method_y') + + +class ClassFour(ClassThree): + print('<[4]> ClassFour body') + + def method_y(self): + print('<[5]> ClassFour.method_y') + + +class ClassFive(metaclass=MetaAleph): + print('<[6]> ClassFive body') + + def __init__(self): + print('<[7]> ClassFive.__init__') + + def method_z(self): + print('<[8]> ClassFive.method_z') + + +class ClassSix(ClassFive): + print('<[9]> ClassSix body') + + def method_z(self): + print('<[10]> ClassSix.method_z') + + +if __name__ == '__main__': + print('<[11]> ClassThree tests', 30 * '.') + three = ClassThree() + three.method_y() + print('<[12]> ClassFour tests', 30 * '.') + four = ClassFour() + four.method_y() + print('<[13]> ClassFive tests', 30 * '.') + five = ClassFive() + five.method_z() + print('<[14]> ClassSix tests', 30 * '.') + six = ClassSix() + six.method_z() + +print('<[15]> evaltime_meta module end') diff --git a/25-class-metaprog/factories.py b/25-class-metaprog/factories.py new file mode 100644 index 0000000..fb2a9c5 --- /dev/null +++ b/25-class-metaprog/factories.py @@ -0,0 +1,61 @@ +""" +record_factory: create simple classes just for holding data fields + +# tag::RECORD_FACTORY_DEMO[] + >>> Dog = record_factory('Dog', 'name weight owner') # <1> + >>> rex = Dog('Rex', 30, 'Bob') + >>> rex # <2> + Dog(name='Rex', weight=30, owner='Bob') + >>> name, weight, _ = rex # <3> + >>> name, weight + ('Rex', 30) + >>> "{2}'s dog weighs {1}kg".format(*rex) # <4> + "Bob's dog weighs 30kg" + >>> rex.weight = 32 # <5> + >>> rex + Dog(name='Rex', weight=32, owner='Bob') + >>> Dog.__mro__ # <6> + (, ) + +# end::RECORD_FACTORY_DEMO[] + +The factory also accepts a list or tuple of identifiers: + + >>> Dog = record_factory('Dog', ['name', 'weight', 'owner']) + >>> Dog.__slots__ + ('name', 'weight', 'owner') + +""" + +# tag::RECORD_FACTORY[] +def record_factory(cls_name, field_names): + try: + field_names = field_names.replace(',', ' ').split() # <1> + except AttributeError: # no .replace or .split + pass # assume it's already a sequence of strings + field_names = tuple(field_names) # <2> + if not all(s.isidentifier() for s in field_names): + raise ValueError('field_names must all be valid identifiers') + + def __init__(self, *args, **kwargs): # <3> + attrs = dict(zip(self.__slots__, args)) + attrs.update(kwargs) + for name, value in attrs.items(): + setattr(self, name, value) + + def __iter__(self): # <4> + for name in self.__slots__: + yield getattr(self, name) + + def __repr__(self): # <5> + values = ', '.join('{}={!r}'.format(*i) for i + in zip(self.__slots__, self)) + return '{}({})'.format(self.__class__.__name__, values) + + cls_attrs = dict(__slots__ = field_names, # <6> + __init__ = __init__, + __iter__ = __iter__, + __repr__ = __repr__) + + return type(cls_name, (object,), cls_attrs) # <7> +# end::RECORD_FACTORY[] diff --git a/25-class-metaprog/tinyenums/microenum.py b/25-class-metaprog/tinyenums/microenum.py new file mode 100644 index 0000000..7010a9e --- /dev/null +++ b/25-class-metaprog/tinyenums/microenum.py @@ -0,0 +1,62 @@ +""" +Testing ``AutoFillDict``:: + + >>> adict = AutoFillDict() + >>> len(adict) + 0 + >>> adict['first'] + 0 + >>> adict + {'first': 0} + >>> adict['second'] + 1 + >>> adict['third'] + 2 + >>> len(adict) + 3 + >>> adict + {'first': 0, 'second': 1, 'third': 2} + >>> adict['__magic__'] + Traceback (most recent call last): + ... + KeyError: '__magic__' + +Testing ``MicroEnum``:: + + >>> class Flavor(MicroEnum): + ... cocoa + ... coconut + ... vanilla + >>> Flavor.cocoa, Flavor.vanilla + (0, 2) + >>> Flavor[1] + 'coconut' +""" + + +class AutoFillDict(dict): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.__next_value = 0 + + def __missing__(self, key): + if key.startswith('__') and key.endswith('__'): + raise KeyError(key) + self[key] = value = self.__next_value + self.__next_value += 1 + return value + + +class MicroEnumMeta(type): + def __prepare__(name, bases, **kwargs): + return AutoFillDict() + + def __getitem__(cls, key): + for k, v in cls.__dict__.items(): + if v == key: + return k + raise KeyError(key) + + +class MicroEnum(metaclass=MicroEnumMeta): + pass diff --git a/25-class-metaprog/tinyenums/microenum_demo.py b/25-class-metaprog/tinyenums/microenum_demo.py new file mode 100644 index 0000000..f3adc1d --- /dev/null +++ b/25-class-metaprog/tinyenums/microenum_demo.py @@ -0,0 +1,17 @@ +""" +Testing ``Flavor``:: + + >>> Flavor.cocoa, Flavor.coconut, Flavor.vanilla + (0, 1, 2) + >>> Flavor[1] + 'coconut' + +""" + +from microenum import MicroEnum + + +class Flavor(MicroEnum): + cocoa + coconut + vanilla diff --git a/25-class-metaprog/tinyenums/nanoenum.py b/25-class-metaprog/tinyenums/nanoenum.py new file mode 100644 index 0000000..36cee6b --- /dev/null +++ b/25-class-metaprog/tinyenums/nanoenum.py @@ -0,0 +1,39 @@ +""" +Testing ``KeyIsValueDict``:: + + >>> adict = KeyIsValueDict() + >>> len(adict) + 0 + >>> adict['first'] + 'first' + >>> adict + {'first': 'first'} + >>> adict['second'] + 'second' + >>> len(adict) + 2 + >>> adict + {'first': 'first', 'second': 'second'} + >>> adict['__magic__'] + Traceback (most recent call last): + ... + KeyError: '__magic__' +""" + + +class KeyIsValueDict(dict): + + def __missing__(self, key): + if key.startswith('__') and key.endswith('__'): + raise KeyError(key) + self[key] = key + return key + + +class NanoEnumMeta(type): + def __prepare__(name, bases, **kwargs): + return KeyIsValueDict() + + +class NanoEnum(metaclass=NanoEnumMeta): + pass diff --git a/25-class-metaprog/tinyenums/nanoenum_demo.py b/25-class-metaprog/tinyenums/nanoenum_demo.py new file mode 100644 index 0000000..80234c0 --- /dev/null +++ b/25-class-metaprog/tinyenums/nanoenum_demo.py @@ -0,0 +1,17 @@ +""" +Testing ``Flavor``:: + + >>> Flavor.coconut + 'coconut' + >>> Flavor.cocoa, Flavor.vanilla + ('cocoa', 'vanilla') + +""" + +from nanoenum import NanoEnum + + +class Flavor(NanoEnum): + cocoa + coconut + vanilla