summaryrefslogtreecommitdiffstats
path: root/Lib
diff options
context:
space:
mode:
authorNick Coghlan <ncoghlan@gmail.com>2012-05-19 16:34:13 (GMT)
committerNick Coghlan <ncoghlan@gmail.com>2012-05-19 16:34:13 (GMT)
commit7fc570a51e6d8647d73e152721b2e72add72d134 (patch)
treee735131f83155d73d136ef2d3d8e3c7ed1d653b3 /Lib
parent7c5ba513b924692e534e372a135add15e70ac0cb (diff)
downloadcpython-7fc570a51e6d8647d73e152721b2e72add72d134.zip
cpython-7fc570a51e6d8647d73e152721b2e72add72d134.tar.gz
cpython-7fc570a51e6d8647d73e152721b2e72add72d134.tar.bz2
Close #14588: added a PEP 3115 compliant dynamic type creation mechanism
Diffstat (limited to 'Lib')
-rw-r--r--Lib/test/test_types.py251
-rw-r--r--Lib/types.py58
2 files changed, 308 insertions, 1 deletions
diff --git a/Lib/test/test_types.py b/Lib/test/test_types.py
index 9a2e0d4..51b594c 100644
--- a/Lib/test/test_types.py
+++ b/Lib/test/test_types.py
@@ -747,8 +747,257 @@ class MappingProxyTests(unittest.TestCase):
self.assertEqual(copy['key1'], 27)
+class ClassCreationTests(unittest.TestCase):
+
+ class Meta(type):
+ def __init__(cls, name, bases, ns, **kw):
+ super().__init__(name, bases, ns)
+ @staticmethod
+ def __new__(mcls, name, bases, ns, **kw):
+ return super().__new__(mcls, name, bases, ns)
+ @classmethod
+ def __prepare__(mcls, name, bases, **kw):
+ ns = super().__prepare__(name, bases)
+ ns["y"] = 1
+ ns.update(kw)
+ return ns
+
+ def test_new_class_basics(self):
+ C = types.new_class("C")
+ self.assertEqual(C.__name__, "C")
+ self.assertEqual(C.__bases__, (object,))
+
+ def test_new_class_subclass(self):
+ C = types.new_class("C", (int,))
+ self.assertTrue(issubclass(C, int))
+
+ def test_new_class_meta(self):
+ Meta = self.Meta
+ settings = {"metaclass": Meta, "z": 2}
+ # We do this twice to make sure the passed in dict isn't mutated
+ for i in range(2):
+ C = types.new_class("C" + str(i), (), settings)
+ self.assertIsInstance(C, Meta)
+ self.assertEqual(C.y, 1)
+ self.assertEqual(C.z, 2)
+
+ def test_new_class_exec_body(self):
+ Meta = self.Meta
+ def func(ns):
+ ns["x"] = 0
+ C = types.new_class("C", (), {"metaclass": Meta, "z": 2}, func)
+ self.assertIsInstance(C, Meta)
+ self.assertEqual(C.x, 0)
+ self.assertEqual(C.y, 1)
+ self.assertEqual(C.z, 2)
+
+ def test_new_class_exec_body(self):
+ #Test that keywords are passed to the metaclass:
+ def meta_func(name, bases, ns, **kw):
+ return name, bases, ns, kw
+ res = types.new_class("X",
+ (int, object),
+ dict(metaclass=meta_func, x=0))
+ self.assertEqual(res, ("X", (int, object), {}, {"x": 0}))
+
+ def test_new_class_defaults(self):
+ # Test defaults/keywords:
+ C = types.new_class("C", (), {}, None)
+ self.assertEqual(C.__name__, "C")
+ self.assertEqual(C.__bases__, (object,))
+
+ def test_new_class_meta_with_base(self):
+ Meta = self.Meta
+ def func(ns):
+ ns["x"] = 0
+ C = types.new_class(name="C",
+ bases=(int,),
+ kwds=dict(metaclass=Meta, z=2),
+ exec_body=func)
+ self.assertTrue(issubclass(C, int))
+ self.assertIsInstance(C, Meta)
+ self.assertEqual(C.x, 0)
+ self.assertEqual(C.y, 1)
+ self.assertEqual(C.z, 2)
+
+ # Many of the following tests are derived from test_descr.py
+ def test_prepare_class(self):
+ # Basic test of metaclass derivation
+ expected_ns = {}
+ class A(type):
+ def __new__(*args, **kwargs):
+ return type.__new__(*args, **kwargs)
+
+ def __prepare__(*args):
+ return expected_ns
+
+ B = types.new_class("B", (object,))
+ C = types.new_class("C", (object,), {"metaclass": A})
+
+ # The most derived metaclass of D is A rather than type.
+ meta, ns, kwds = types.prepare_class("D", (B, C), {"metaclass": type})
+ self.assertIs(meta, A)
+ self.assertIs(ns, expected_ns)
+ self.assertEqual(len(kwds), 0)
+
+ def test_metaclass_derivation(self):
+ # issue1294232: correct metaclass calculation
+ new_calls = [] # to check the order of __new__ calls
+ class AMeta(type):
+ def __new__(mcls, name, bases, ns):
+ new_calls.append('AMeta')
+ return super().__new__(mcls, name, bases, ns)
+ @classmethod
+ def __prepare__(mcls, name, bases):
+ return {}
+
+ class BMeta(AMeta):
+ def __new__(mcls, name, bases, ns):
+ new_calls.append('BMeta')
+ return super().__new__(mcls, name, bases, ns)
+ @classmethod
+ def __prepare__(mcls, name, bases):
+ ns = super().__prepare__(name, bases)
+ ns['BMeta_was_here'] = True
+ return ns
+
+ A = types.new_class("A", (), {"metaclass": AMeta})
+ self.assertEqual(new_calls, ['AMeta'])
+ new_calls.clear()
+
+ B = types.new_class("B", (), {"metaclass": BMeta})
+ # BMeta.__new__ calls AMeta.__new__ with super:
+ self.assertEqual(new_calls, ['BMeta', 'AMeta'])
+ new_calls.clear()
+
+ C = types.new_class("C", (A, B))
+ # The most derived metaclass is BMeta:
+ self.assertEqual(new_calls, ['BMeta', 'AMeta'])
+ new_calls.clear()
+ # BMeta.__prepare__ should've been called:
+ self.assertIn('BMeta_was_here', C.__dict__)
+
+ # The order of the bases shouldn't matter:
+ C2 = types.new_class("C2", (B, A))
+ self.assertEqual(new_calls, ['BMeta', 'AMeta'])
+ new_calls.clear()
+ self.assertIn('BMeta_was_here', C2.__dict__)
+
+ # Check correct metaclass calculation when a metaclass is declared:
+ D = types.new_class("D", (C,), {"metaclass": type})
+ self.assertEqual(new_calls, ['BMeta', 'AMeta'])
+ new_calls.clear()
+ self.assertIn('BMeta_was_here', D.__dict__)
+
+ E = types.new_class("E", (C,), {"metaclass": AMeta})
+ self.assertEqual(new_calls, ['BMeta', 'AMeta'])
+ new_calls.clear()
+ self.assertIn('BMeta_was_here', E.__dict__)
+
+ def test_metaclass_override_function(self):
+ # Special case: the given metaclass isn't a class,
+ # so there is no metaclass calculation.
+ class A(metaclass=self.Meta):
+ pass
+
+ marker = object()
+ def func(*args, **kwargs):
+ return marker
+
+ X = types.new_class("X", (), {"metaclass": func})
+ Y = types.new_class("Y", (object,), {"metaclass": func})
+ Z = types.new_class("Z", (A,), {"metaclass": func})
+ self.assertIs(marker, X)
+ self.assertIs(marker, Y)
+ self.assertIs(marker, Z)
+
+ def test_metaclass_override_callable(self):
+ # The given metaclass is a class,
+ # but not a descendant of type.
+ new_calls = [] # to check the order of __new__ calls
+ prepare_calls = [] # to track __prepare__ calls
+ class ANotMeta:
+ def __new__(mcls, *args, **kwargs):
+ new_calls.append('ANotMeta')
+ return super().__new__(mcls)
+ @classmethod
+ def __prepare__(mcls, name, bases):
+ prepare_calls.append('ANotMeta')
+ return {}
+
+ class BNotMeta(ANotMeta):
+ def __new__(mcls, *args, **kwargs):
+ new_calls.append('BNotMeta')
+ return super().__new__(mcls)
+ @classmethod
+ def __prepare__(mcls, name, bases):
+ prepare_calls.append('BNotMeta')
+ return super().__prepare__(name, bases)
+
+ A = types.new_class("A", (), {"metaclass": ANotMeta})
+ self.assertIs(ANotMeta, type(A))
+ self.assertEqual(prepare_calls, ['ANotMeta'])
+ prepare_calls.clear()
+ self.assertEqual(new_calls, ['ANotMeta'])
+ new_calls.clear()
+
+ B = types.new_class("B", (), {"metaclass": BNotMeta})
+ self.assertIs(BNotMeta, type(B))
+ self.assertEqual(prepare_calls, ['BNotMeta', 'ANotMeta'])
+ prepare_calls.clear()
+ self.assertEqual(new_calls, ['BNotMeta', 'ANotMeta'])
+ new_calls.clear()
+
+ C = types.new_class("C", (A, B))
+ self.assertIs(BNotMeta, type(C))
+ self.assertEqual(prepare_calls, ['BNotMeta', 'ANotMeta'])
+ prepare_calls.clear()
+ self.assertEqual(new_calls, ['BNotMeta', 'ANotMeta'])
+ new_calls.clear()
+
+ C2 = types.new_class("C2", (B, A))
+ self.assertIs(BNotMeta, type(C2))
+ self.assertEqual(prepare_calls, ['BNotMeta', 'ANotMeta'])
+ prepare_calls.clear()
+ self.assertEqual(new_calls, ['BNotMeta', 'ANotMeta'])
+ new_calls.clear()
+
+ # This is a TypeError, because of a metaclass conflict:
+ # BNotMeta is neither a subclass, nor a superclass of type
+ with self.assertRaises(TypeError):
+ D = types.new_class("D", (C,), {"metaclass": type})
+
+ E = types.new_class("E", (C,), {"metaclass": ANotMeta})
+ self.assertIs(BNotMeta, type(E))
+ self.assertEqual(prepare_calls, ['BNotMeta', 'ANotMeta'])
+ prepare_calls.clear()
+ self.assertEqual(new_calls, ['BNotMeta', 'ANotMeta'])
+ new_calls.clear()
+
+ F = types.new_class("F", (object(), C))
+ self.assertIs(BNotMeta, type(F))
+ self.assertEqual(prepare_calls, ['BNotMeta', 'ANotMeta'])
+ prepare_calls.clear()
+ self.assertEqual(new_calls, ['BNotMeta', 'ANotMeta'])
+ new_calls.clear()
+
+ F2 = types.new_class("F2", (C, object()))
+ self.assertIs(BNotMeta, type(F2))
+ self.assertEqual(prepare_calls, ['BNotMeta', 'ANotMeta'])
+ prepare_calls.clear()
+ self.assertEqual(new_calls, ['BNotMeta', 'ANotMeta'])
+ new_calls.clear()
+
+ # TypeError: BNotMeta is neither a
+ # subclass, nor a superclass of int
+ with self.assertRaises(TypeError):
+ X = types.new_class("X", (C, int()))
+ with self.assertRaises(TypeError):
+ X = types.new_class("X", (int(), C))
+
+
def test_main():
- run_unittest(TypesTests, MappingProxyTests)
+ run_unittest(TypesTests, MappingProxyTests, ClassCreationTests)
if __name__ == '__main__':
test_main()
diff --git a/Lib/types.py b/Lib/types.py
index 08cbb83..2bfcd9b 100644
--- a/Lib/types.py
+++ b/Lib/types.py
@@ -40,3 +40,61 @@ GetSetDescriptorType = type(FunctionType.__code__)
MemberDescriptorType = type(FunctionType.__globals__)
del sys, _f, _g, _C, # Not for export
+
+
+# Provide a PEP 3115 compliant mechanism for class creation
+def new_class(name, bases=(), kwds=None, exec_body=None):
+ """Create a class object dynamically using the appropriate metaclass."""
+ meta, ns, kwds = prepare_class(name, bases, kwds)
+ if exec_body is not None:
+ exec_body(ns)
+ return meta(name, bases, ns, **kwds)
+
+def prepare_class(name, bases=(), kwds=None):
+ """Call the __prepare__ method of the appropriate metaclass.
+
+ Returns (metaclass, namespace, kwds) as a 3-tuple
+
+ *metaclass* is the appropriate metaclass
+ *namespace* is the prepared class namespace
+ *kwds* is an updated copy of the passed in kwds argument with any
+ 'metaclass' entry removed. If no kwds argument is passed in, this will
+ be an empty dict.
+ """
+ if kwds is None:
+ kwds = {}
+ else:
+ kwds = dict(kwds) # Don't alter the provided mapping
+ if 'metaclass' in kwds:
+ meta = kwds.pop('metaclass')
+ else:
+ if bases:
+ meta = type(bases[0])
+ else:
+ meta = type
+ if isinstance(meta, type):
+ # when meta is a type, we first determine the most-derived metaclass
+ # instead of invoking the initial candidate directly
+ meta = _calculate_meta(meta, bases)
+ if hasattr(meta, '__prepare__'):
+ ns = meta.__prepare__(name, bases, **kwds)
+ else:
+ ns = {}
+ return meta, ns, kwds
+
+def _calculate_meta(meta, bases):
+ """Calculate the most derived metaclass."""
+ winner = meta
+ for base in bases:
+ base_meta = type(base)
+ if issubclass(winner, base_meta):
+ continue
+ if issubclass(base_meta, winner):
+ winner = base_meta
+ continue
+ # else:
+ raise TypeError("metaclass conflict: "
+ "the metaclass of a derived class "
+ "must be a (non-strict) subclass "
+ "of the metaclasses of all its bases")
+ return winner