266 lines
6.2 KiB
Python
266 lines
6.2 KiB
Python
|
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: metaclass
|
||
|
>>>
|
||
|
|
||
|
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
|
||
|
support.run_doctest(test_metaclass, verbose)
|
||
|
|
||
|
if __name__ == "__main__":
|
||
|
test_main(verbose=True)
|