• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1import doctest
2import unittest
3
4
5doctests = """
6
7Basic class construction.
8
9    >>> class C:
10    ...     def meth(self): print("Hello")
11    ...
12    >>> C.__class__ is type
13    True
14    >>> a = C()
15    >>> a.__class__ is C
16    True
17    >>> a.meth()
18    Hello
19    >>>
20
21Use *args notation for the bases.
22
23    >>> class A: pass
24    >>> class B: pass
25    >>> bases = (A, B)
26    >>> class C(*bases): pass
27    >>> C.__bases__ == bases
28    True
29    >>>
30
31Use a trivial metaclass.
32
33    >>> class M(type):
34    ...     pass
35    ...
36    >>> class C(metaclass=M):
37    ...    def meth(self): print("Hello")
38    ...
39    >>> C.__class__ is M
40    True
41    >>> a = C()
42    >>> a.__class__ is C
43    True
44    >>> a.meth()
45    Hello
46    >>>
47
48Use **kwds notation for the metaclass keyword.
49
50    >>> kwds = {'metaclass': M}
51    >>> class C(**kwds): pass
52    ...
53    >>> C.__class__ is M
54    True
55    >>> a = C()
56    >>> a.__class__ is C
57    True
58    >>>
59
60Use a metaclass with a __prepare__ static method.
61
62    >>> class M(type):
63    ...    @staticmethod
64    ...    def __prepare__(*args, **kwds):
65    ...        print("Prepare called:", args, kwds)
66    ...        return dict()
67    ...    def __new__(cls, name, bases, namespace, **kwds):
68    ...        print("New called:", kwds)
69    ...        return type.__new__(cls, name, bases, namespace)
70    ...    def __init__(cls, *args, **kwds):
71    ...        pass
72    ...
73    >>> class C(metaclass=M):
74    ...     def meth(self): print("Hello")
75    ...
76    Prepare called: ('C', ()) {}
77    New called: {}
78    >>>
79
80Also pass another keyword.
81
82    >>> class C(object, metaclass=M, other="haha"):
83    ...     pass
84    ...
85    Prepare called: ('C', (<class 'object'>,)) {'other': 'haha'}
86    New called: {'other': 'haha'}
87    >>> C.__class__ is M
88    True
89    >>> C.__bases__ == (object,)
90    True
91    >>> a = C()
92    >>> a.__class__ is C
93    True
94    >>>
95
96Check that build_class doesn't mutate the kwds dict.
97
98    >>> kwds = {'metaclass': type}
99    >>> class C(**kwds): pass
100    ...
101    >>> kwds == {'metaclass': type}
102    True
103    >>>
104
105Use various combinations of explicit keywords and **kwds.
106
107    >>> bases = (object,)
108    >>> kwds = {'metaclass': M, 'other': 'haha'}
109    >>> class C(*bases, **kwds): pass
110    ...
111    Prepare called: ('C', (<class 'object'>,)) {'other': 'haha'}
112    New called: {'other': 'haha'}
113    >>> C.__class__ is M
114    True
115    >>> C.__bases__ == (object,)
116    True
117    >>> class B: pass
118    >>> kwds = {'other': 'haha'}
119    >>> class C(B, metaclass=M, *bases, **kwds): pass
120    ...
121    Prepare called: ('C', (<class 'test.test_metaclass.B'>, <class 'object'>)) {'other': 'haha'}
122    New called: {'other': 'haha'}
123    >>> C.__class__ is M
124    True
125    >>> C.__bases__ == (B, object)
126    True
127    >>>
128
129Check for duplicate keywords.
130
131    >>> class C(metaclass=type, metaclass=type): pass
132    ...
133    Traceback (most recent call last):
134    [...]
135    SyntaxError: keyword argument repeated: metaclass
136    >>>
137
138Another way.
139
140    >>> kwds = {'metaclass': type}
141    >>> class C(metaclass=type, **kwds): pass
142    ...
143    Traceback (most recent call last):
144    [...]
145    TypeError: __build_class__() got multiple values for keyword argument 'metaclass'
146    >>>
147
148Use a __prepare__ method that returns an instrumented dict.
149
150    >>> class LoggingDict(dict):
151    ...     def __setitem__(self, key, value):
152    ...         print("d[%r] = %r" % (key, value))
153    ...         dict.__setitem__(self, key, value)
154    ...
155    >>> class Meta(type):
156    ...    @staticmethod
157    ...    def __prepare__(name, bases):
158    ...        return LoggingDict()
159    ...
160    >>> class C(metaclass=Meta):
161    ...     foo = 2+2
162    ...     foo = 42
163    ...     bar = 123
164    ...
165    d['__module__'] = 'test.test_metaclass'
166    d['__qualname__'] = 'C'
167    d['__firstlineno__'] = 1
168    d['foo'] = 4
169    d['foo'] = 42
170    d['bar'] = 123
171    d['__static_attributes__'] = ()
172    >>>
173
174Use a metaclass that doesn't derive from type.
175
176    >>> def meta(name, bases, namespace, **kwds):
177    ...     print("meta:", name, bases)
178    ...     print("ns:", sorted(namespace.items()))
179    ...     print("kw:", sorted(kwds.items()))
180    ...     return namespace
181    ...
182    >>> class C(metaclass=meta):
183    ...     a = 42
184    ...     b = 24
185    ...
186    meta: C ()
187    ns: [('__firstlineno__', 1), ('__module__', 'test.test_metaclass'), ('__qualname__', 'C'), ('__static_attributes__', ()), ('a', 42), ('b', 24)]
188    kw: []
189    >>> type(C) is dict
190    True
191    >>> print(sorted(C.items()))
192    [('__firstlineno__', 1), ('__module__', 'test.test_metaclass'), ('__qualname__', 'C'), ('__static_attributes__', ()), ('a', 42), ('b', 24)]
193    >>>
194
195And again, with a __prepare__ attribute.
196
197    >>> def prepare(name, bases, **kwds):
198    ...     print("prepare:", name, bases, sorted(kwds.items()))
199    ...     return LoggingDict()
200    ...
201    >>> meta.__prepare__ = prepare
202    >>> class C(metaclass=meta, other="booh"):
203    ...    a = 1
204    ...    a = 2
205    ...    b = 3
206    ...
207    prepare: C () [('other', 'booh')]
208    d['__module__'] = 'test.test_metaclass'
209    d['__qualname__'] = 'C'
210    d['__firstlineno__'] = 1
211    d['a'] = 1
212    d['a'] = 2
213    d['b'] = 3
214    d['__static_attributes__'] = ()
215    meta: C ()
216    ns: [('__firstlineno__', 1), ('__module__', 'test.test_metaclass'), ('__qualname__', 'C'), ('__static_attributes__', ()), ('a', 2), ('b', 3)]
217    kw: [('other', 'booh')]
218    >>>
219
220The default metaclass must define a __prepare__() method.
221
222    >>> type.__prepare__()
223    {}
224    >>>
225
226Make sure it works with subclassing.
227
228    >>> class M(type):
229    ...     @classmethod
230    ...     def __prepare__(cls, *args, **kwds):
231    ...         d = super().__prepare__(*args, **kwds)
232    ...         d["hello"] = 42
233    ...         return d
234    ...
235    >>> class C(metaclass=M):
236    ...     print(hello)
237    ...
238    42
239    >>> print(C.hello)
240    42
241    >>>
242
243Test failures in looking up the __prepare__ method work.
244    >>> class ObscureException(Exception):
245    ...     pass
246    >>> class FailDescr:
247    ...     def __get__(self, instance, owner):
248    ...        raise ObscureException
249    >>> class Meta(type):
250    ...     __prepare__ = FailDescr()
251    >>> class X(metaclass=Meta):
252    ...     pass
253    Traceback (most recent call last):
254    [...]
255    test.test_metaclass.ObscureException
256
257"""
258
259import sys
260
261# Trace function introduces __locals__ which causes various tests to fail.
262if hasattr(sys, 'gettrace') and sys.gettrace():
263    __test__ = {}
264else:
265    __test__ = {'doctests' : doctests}
266
267def load_tests(loader, tests, pattern):
268    tests.addTest(doctest.DocTestSuite())
269    return tests
270
271
272if __name__ == "__main__":
273    unittest.main()
274