| doctests = """ |
| |
| Basic class construction. |
| |
| >>> class C: |
| ... def meth(self): print("Hello") |
| ... |
| >>> C.__class__ is type |
| True |
| >>> a = C() |
| >>> a.__class__ is C |
| True |
| >>> a.meth() |
| Hello |
| >>> |
| |
| Use *args notation for the bases. |
| |
| >>> class A: pass |
| >>> class B: pass |
| >>> bases = (A, B) |
| >>> class C(*bases): pass |
| >>> C.__bases__ == bases |
| True |
| >>> |
| |
| Use a trivial metaclass. |
| |
| >>> class M(type): |
| ... pass |
| ... |
| >>> class C(metaclass=M): |
| ... def meth(self): print("Hello") |
| ... |
| >>> C.__class__ is M |
| True |
| >>> a = C() |
| >>> a.__class__ is C |
| True |
| >>> a.meth() |
| Hello |
| >>> |
| |
| Use **kwds notation for the metaclass keyword. |
| |
| >>> kwds = {'metaclass': M} |
| >>> class C(**kwds): pass |
| ... |
| >>> C.__class__ is M |
| True |
| >>> a = C() |
| >>> a.__class__ is C |
| True |
| >>> |
| |
| Use a metaclass with a __prepare__ static method. |
| |
| >>> class M(type): |
| ... @staticmethod |
| ... def __prepare__(*args, **kwds): |
| ... print("Prepare called:", args, kwds) |
| ... return dict() |
| ... def __new__(cls, name, bases, namespace, **kwds): |
| ... print("New called:", kwds) |
| ... return type.__new__(cls, name, bases, namespace) |
| ... def __init__(cls, *args, **kwds): |
| ... pass |
| ... |
| >>> class C(metaclass=M): |
| ... def meth(self): print("Hello") |
| ... |
| Prepare called: ('C', ()) {} |
| New called: {} |
| >>> |
| |
| Also pass another keyword. |
| |
| >>> class C(object, metaclass=M, other="haha"): |
| ... pass |
| ... |
| Prepare called: ('C', (<class 'object' ...>,)) {'other': 'haha'} |
| New called: {'other': 'haha'} |
| >>> C.__class__ is M |
| True |
| >>> C.__bases__ == (object,) |
| True |
| >>> a = C() |
| >>> a.__class__ is C |
| True |
| >>> |
| |
| Check that build_class doesn't mutate the kwds dict. |
| |
| >>> kwds = {'metaclass': type} |
| >>> class C(**kwds): pass |
| ... |
| >>> kwds == {'metaclass': type} |
| True |
| >>> |
| |
| Use various combinations of explicit keywords and **kwds. |
| |
| >>> bases = (object,) |
| >>> kwds = {'metaclass': M, 'other': 'haha'} |
| >>> class C(*bases, **kwds): pass |
| ... |
| Prepare called: ('C', (<class 'object' ...>,)) {'other': 'haha'} |
| New called: {'other': 'haha'} |
| >>> C.__class__ is M |
| True |
| >>> C.__bases__ == (object,) |
| True |
| >>> class B: pass |
| >>> kwds = {'other': 'haha'} |
| >>> class C(B, metaclass=M, *bases, **kwds): pass |
| ... |
| Prepare called: ('C', (<class 'test.test_metaclass.B' ...>, <class 'object' ...>)) {'other': 'haha'} |
| New called: {'other': 'haha'} |
| >>> C.__class__ is M |
| True |
| >>> C.__bases__ == (B, object) |
| True |
| >>> |
| |
| Check for duplicate keywords. |
| |
| >>> class C(metaclass=type, metaclass=type): pass |
| ... |
| Traceback (most recent call last): |
| [...] |
| SyntaxError: keyword argument repeated |
| >>> |
| |
| Another way. |
| |
| >>> kwds = {'metaclass': type} |
| >>> class C(metaclass=type, **kwds): pass |
| ... |
| Traceback (most recent call last): |
| [...] |
| TypeError: __build_class__() got multiple values for keyword argument 'metaclass' |
| >>> |
| |
| Use a __prepare__ method that returns an instrumented dict. |
| |
| >>> class LoggingDict(dict): |
| ... def __setitem__(self, key, value): |
| ... print("d[%r] = %r" % (key, value)) |
| ... dict.__setitem__(self, key, value) |
| ... |
| >>> class Meta(type): |
| ... @staticmethod |
| ... def __prepare__(name, bases): |
| ... return LoggingDict() |
| ... |
| >>> class C(metaclass=Meta): |
| ... foo = 2+2 |
| ... foo = 42 |
| ... bar = 123 |
| ... |
| d['__module__'] = 'test.test_metaclass' |
| d['__qualname__'] = 'C' |
| d['foo'] = 4 |
| d['foo'] = 42 |
| d['bar'] = 123 |
| >>> |
| |
| Use a metaclass that doesn't derive from type. |
| |
| >>> def meta(name, bases, namespace, **kwds): |
| ... print("meta:", name, bases) |
| ... print("ns:", sorted(namespace.items())) |
| ... print("kw:", sorted(kwds.items())) |
| ... return namespace |
| ... |
| >>> class C(metaclass=meta): |
| ... a = 42 |
| ... b = 24 |
| ... |
| meta: C () |
| ns: [('__module__', 'test.test_metaclass'), ('__qualname__', 'C'), ('a', 42), ('b', 24)] |
| kw: [] |
| >>> type(C) is dict |
| True |
| >>> print(sorted(C.items())) |
| [('__module__', 'test.test_metaclass'), ('__qualname__', 'C'), ('a', 42), ('b', 24)] |
| >>> |
| |
| And again, with a __prepare__ attribute. |
| |
| >>> def prepare(name, bases, **kwds): |
| ... print("prepare:", name, bases, sorted(kwds.items())) |
| ... return LoggingDict() |
| ... |
| >>> meta.__prepare__ = prepare |
| >>> class C(metaclass=meta, other="booh"): |
| ... a = 1 |
| ... a = 2 |
| ... b = 3 |
| ... |
| prepare: C () [('other', 'booh')] |
| d['__module__'] = 'test.test_metaclass' |
| d['__qualname__'] = 'C' |
| d['a'] = 1 |
| d['a'] = 2 |
| d['b'] = 3 |
| meta: C () |
| ns: [('__module__', 'test.test_metaclass'), ('__qualname__', 'C'), ('a', 2), ('b', 3)] |
| kw: [('other', 'booh')] |
| >>> |
| |
| The default metaclass must define a __prepare__() method. |
| |
| >>> type.__prepare__() |
| {} |
| >>> |
| |
| Make sure it works with subclassing. |
| |
| >>> class M(type): |
| ... @classmethod |
| ... def __prepare__(cls, *args, **kwds): |
| ... d = super().__prepare__(*args, **kwds) |
| ... d["hello"] = 42 |
| ... return d |
| ... |
| >>> class C(metaclass=M): |
| ... print(hello) |
| ... |
| 42 |
| >>> print(C.hello) |
| 42 |
| >>> |
| |
| Test failures in looking up the __prepare__ method work. |
| >>> class ObscureException(Exception): |
| ... pass |
| >>> class FailDescr: |
| ... def __get__(self, instance, owner): |
| ... raise ObscureException |
| >>> class Meta(type): |
| ... __prepare__ = FailDescr() |
| >>> class X(metaclass=Meta): |
| ... pass |
| Traceback (most recent call last): |
| [...] |
| test.test_metaclass.ObscureException |
| |
| """ |
| |
| import sys |
| |
| # Trace function introduces __locals__ which causes various tests to fail. |
| if hasattr(sys, 'gettrace') and sys.gettrace(): |
| __test__ = {} |
| else: |
| __test__ = {'doctests' : doctests} |
| |
| def test_main(verbose=False): |
| from test import support |
| from test import test_metaclass |
| import doctest |
| support.run_doctest(test_metaclass, verbose, optionflags=doctest.ELLIPSIS) |
| |
| if __name__ == "__main__": |
| test_main(verbose=True) |