| import types |
| import unittest |
| |
| |
| class Test(unittest.TestCase): |
| def test_init_subclass(self): |
| class A: |
| initialized = False |
| |
| def __init_subclass__(cls): |
| super().__init_subclass__() |
| cls.initialized = True |
| |
| class B(A): |
| pass |
| |
| self.assertFalse(A.initialized) |
| self.assertTrue(B.initialized) |
| |
| def test_init_subclass_dict(self): |
| class A(dict): |
| initialized = False |
| |
| def __init_subclass__(cls): |
| super().__init_subclass__() |
| cls.initialized = True |
| |
| class B(A): |
| pass |
| |
| self.assertFalse(A.initialized) |
| self.assertTrue(B.initialized) |
| |
| def test_init_subclass_kwargs(self): |
| class A: |
| def __init_subclass__(cls, **kwargs): |
| cls.kwargs = kwargs |
| |
| class B(A, x=3): |
| pass |
| |
| self.assertEqual(B.kwargs, dict(x=3)) |
| |
| def test_init_subclass_error(self): |
| class A: |
| def __init_subclass__(cls): |
| raise RuntimeError |
| |
| with self.assertRaises(RuntimeError): |
| class B(A): |
| pass |
| |
| def test_init_subclass_wrong(self): |
| class A: |
| def __init_subclass__(cls, whatever): |
| pass |
| |
| with self.assertRaises(TypeError): |
| class B(A): |
| pass |
| |
| def test_init_subclass_skipped(self): |
| class BaseWithInit: |
| def __init_subclass__(cls, **kwargs): |
| super().__init_subclass__(**kwargs) |
| cls.initialized = cls |
| |
| class BaseWithoutInit(BaseWithInit): |
| pass |
| |
| class A(BaseWithoutInit): |
| pass |
| |
| self.assertIs(A.initialized, A) |
| self.assertIs(BaseWithoutInit.initialized, BaseWithoutInit) |
| |
| def test_init_subclass_diamond(self): |
| class Base: |
| def __init_subclass__(cls, **kwargs): |
| super().__init_subclass__(**kwargs) |
| cls.calls = [] |
| |
| class Left(Base): |
| pass |
| |
| class Middle: |
| def __init_subclass__(cls, middle, **kwargs): |
| super().__init_subclass__(**kwargs) |
| cls.calls += [middle] |
| |
| class Right(Base): |
| def __init_subclass__(cls, right="right", **kwargs): |
| super().__init_subclass__(**kwargs) |
| cls.calls += [right] |
| |
| class A(Left, Middle, Right, middle="middle"): |
| pass |
| |
| self.assertEqual(A.calls, ["right", "middle"]) |
| self.assertEqual(Left.calls, []) |
| self.assertEqual(Right.calls, []) |
| |
| def test_set_name(self): |
| class Descriptor: |
| def __set_name__(self, owner, name): |
| self.owner = owner |
| self.name = name |
| |
| class A: |
| d = Descriptor() |
| |
| self.assertEqual(A.d.name, "d") |
| self.assertIs(A.d.owner, A) |
| |
| def test_set_name_metaclass(self): |
| class Meta(type): |
| def __new__(cls, name, bases, ns): |
| ret = super().__new__(cls, name, bases, ns) |
| self.assertEqual(ret.d.name, "d") |
| self.assertIs(ret.d.owner, ret) |
| return 0 |
| |
| class Descriptor: |
| def __set_name__(self, owner, name): |
| self.owner = owner |
| self.name = name |
| |
| class A(metaclass=Meta): |
| d = Descriptor() |
| self.assertEqual(A, 0) |
| |
| def test_set_name_error(self): |
| class Descriptor: |
| def __set_name__(self, owner, name): |
| 1/0 |
| |
| with self.assertRaises(RuntimeError) as cm: |
| class NotGoingToWork: |
| attr = Descriptor() |
| |
| exc = cm.exception |
| self.assertRegex(str(exc), r'\bNotGoingToWork\b') |
| self.assertRegex(str(exc), r'\battr\b') |
| self.assertRegex(str(exc), r'\bDescriptor\b') |
| self.assertIsInstance(exc.__cause__, ZeroDivisionError) |
| |
| def test_set_name_wrong(self): |
| class Descriptor: |
| def __set_name__(self): |
| pass |
| |
| with self.assertRaises(RuntimeError) as cm: |
| class NotGoingToWork: |
| attr = Descriptor() |
| |
| exc = cm.exception |
| self.assertRegex(str(exc), r'\bNotGoingToWork\b') |
| self.assertRegex(str(exc), r'\battr\b') |
| self.assertRegex(str(exc), r'\bDescriptor\b') |
| self.assertIsInstance(exc.__cause__, TypeError) |
| |
| def test_set_name_lookup(self): |
| resolved = [] |
| class NonDescriptor: |
| def __getattr__(self, name): |
| resolved.append(name) |
| |
| class A: |
| d = NonDescriptor() |
| |
| self.assertNotIn('__set_name__', resolved, |
| '__set_name__ is looked up in instance dict') |
| |
| def test_set_name_init_subclass(self): |
| class Descriptor: |
| def __set_name__(self, owner, name): |
| self.owner = owner |
| self.name = name |
| |
| class Meta(type): |
| def __new__(cls, name, bases, ns): |
| self = super().__new__(cls, name, bases, ns) |
| self.meta_owner = self.owner |
| self.meta_name = self.name |
| return self |
| |
| class A: |
| def __init_subclass__(cls): |
| cls.owner = cls.d.owner |
| cls.name = cls.d.name |
| |
| class B(A, metaclass=Meta): |
| d = Descriptor() |
| |
| self.assertIs(B.owner, B) |
| self.assertEqual(B.name, 'd') |
| self.assertIs(B.meta_owner, B) |
| self.assertEqual(B.name, 'd') |
| |
| def test_set_name_modifying_dict(self): |
| notified = [] |
| class Descriptor: |
| def __set_name__(self, owner, name): |
| setattr(owner, name + 'x', None) |
| notified.append(name) |
| |
| class A: |
| a = Descriptor() |
| b = Descriptor() |
| c = Descriptor() |
| d = Descriptor() |
| e = Descriptor() |
| |
| self.assertCountEqual(notified, ['a', 'b', 'c', 'd', 'e']) |
| |
| def test_errors(self): |
| class MyMeta(type): |
| pass |
| |
| with self.assertRaises(TypeError): |
| class MyClass(metaclass=MyMeta, otherarg=1): |
| pass |
| |
| with self.assertRaises(TypeError): |
| types.new_class("MyClass", (object,), |
| dict(metaclass=MyMeta, otherarg=1)) |
| types.prepare_class("MyClass", (object,), |
| dict(metaclass=MyMeta, otherarg=1)) |
| |
| class MyMeta(type): |
| def __init__(self, name, bases, namespace, otherarg): |
| super().__init__(name, bases, namespace) |
| |
| with self.assertRaises(TypeError): |
| class MyClass(metaclass=MyMeta, otherarg=1): |
| pass |
| |
| class MyMeta(type): |
| def __new__(cls, name, bases, namespace, otherarg): |
| return super().__new__(cls, name, bases, namespace) |
| |
| def __init__(self, name, bases, namespace, otherarg): |
| super().__init__(name, bases, namespace) |
| self.otherarg = otherarg |
| |
| class MyClass(metaclass=MyMeta, otherarg=1): |
| pass |
| |
| self.assertEqual(MyClass.otherarg, 1) |
| |
| def test_errors_changed_pep487(self): |
| # These tests failed before Python 3.6, PEP 487 |
| class MyMeta(type): |
| def __new__(cls, name, bases, namespace): |
| return super().__new__(cls, name=name, bases=bases, |
| dict=namespace) |
| |
| with self.assertRaises(TypeError): |
| class MyClass(metaclass=MyMeta): |
| pass |
| |
| class MyMeta(type): |
| def __new__(cls, name, bases, namespace, otherarg): |
| self = super().__new__(cls, name, bases, namespace) |
| self.otherarg = otherarg |
| return self |
| |
| class MyClass(metaclass=MyMeta, otherarg=1): |
| pass |
| |
| self.assertEqual(MyClass.otherarg, 1) |
| |
| def test_type(self): |
| t = type('NewClass', (object,), {}) |
| self.assertIsInstance(t, type) |
| self.assertEqual(t.__name__, 'NewClass') |
| |
| with self.assertRaises(TypeError): |
| type(name='NewClass', bases=(object,), dict={}) |
| |
| |
| if __name__ == "__main__": |
| unittest.main() |