0
0
mirror of https://github.com/python/cpython.git synced 2024-11-21 12:59:38 +01:00
cpython/Lib/test/test_metaclass.py
Serhiy Storchaka 153b3f7530
gh-118465: Add __firstlineno__ attribute to class (GH-118475)
It is set by compiler with the line number of the first line of
the class definition.
2024-05-06 12:02:37 +03:00

274 lines
6.5 KiB
Python

import doctest
import unittest
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['__firstlineno__'] = 1
d['foo'] = 4
d['foo'] = 42
d['bar'] = 123
d['__static_attributes__'] = ()
>>>
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: [('__firstlineno__', 1), ('__module__', 'test.test_metaclass'), ('__qualname__', 'C'), ('__static_attributes__', ()), ('a', 42), ('b', 24)]
kw: []
>>> type(C) is dict
True
>>> print(sorted(C.items()))
[('__firstlineno__', 1), ('__module__', 'test.test_metaclass'), ('__qualname__', 'C'), ('__static_attributes__', ()), ('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['__firstlineno__'] = 1
d['a'] = 1
d['a'] = 2
d['b'] = 3
d['__static_attributes__'] = ()
meta: C ()
ns: [('__firstlineno__', 1), ('__module__', 'test.test_metaclass'), ('__qualname__', 'C'), ('__static_attributes__', ()), ('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 load_tests(loader, tests, pattern):
tests.addTest(doctest.DocTestSuite())
return tests
if __name__ == "__main__":
unittest.main()