mirror of
https://github.com/python/cpython.git
synced 2025-07-24 19:54:21 +00:00

Add _PyType_LookupRef and use incref before setting attribute on type Makes setting an attribute on a class and signaling type modified atomic Avoid adding re-entrancy exposing the type cache in an inconsistent state by decrefing after type is updated
906 lines
24 KiB
Python
906 lines
24 KiB
Python
"Test the functionality of Python classes implementing operators."
|
|
|
|
import unittest
|
|
|
|
testmeths = [
|
|
|
|
# Binary operations
|
|
"add",
|
|
"radd",
|
|
"sub",
|
|
"rsub",
|
|
"mul",
|
|
"rmul",
|
|
"matmul",
|
|
"rmatmul",
|
|
"truediv",
|
|
"rtruediv",
|
|
"floordiv",
|
|
"rfloordiv",
|
|
"mod",
|
|
"rmod",
|
|
"divmod",
|
|
"rdivmod",
|
|
"pow",
|
|
"rpow",
|
|
"rshift",
|
|
"rrshift",
|
|
"lshift",
|
|
"rlshift",
|
|
"and",
|
|
"rand",
|
|
"or",
|
|
"ror",
|
|
"xor",
|
|
"rxor",
|
|
|
|
# List/dict operations
|
|
"contains",
|
|
"getitem",
|
|
"setitem",
|
|
"delitem",
|
|
|
|
# Unary operations
|
|
"neg",
|
|
"pos",
|
|
"abs",
|
|
|
|
# generic operations
|
|
"init",
|
|
]
|
|
|
|
# These need to return something other than None
|
|
# "hash",
|
|
# "str",
|
|
# "repr",
|
|
# "int",
|
|
# "float",
|
|
|
|
# These are separate because they can influence the test of other methods.
|
|
# "getattr",
|
|
# "setattr",
|
|
# "delattr",
|
|
|
|
callLst = []
|
|
def trackCall(f):
|
|
def track(*args, **kwargs):
|
|
callLst.append((f.__name__, args))
|
|
return f(*args, **kwargs)
|
|
return track
|
|
|
|
statictests = """
|
|
@trackCall
|
|
def __hash__(self, *args):
|
|
return hash(id(self))
|
|
|
|
@trackCall
|
|
def __str__(self, *args):
|
|
return "AllTests"
|
|
|
|
@trackCall
|
|
def __repr__(self, *args):
|
|
return "AllTests"
|
|
|
|
@trackCall
|
|
def __int__(self, *args):
|
|
return 1
|
|
|
|
@trackCall
|
|
def __index__(self, *args):
|
|
return 1
|
|
|
|
@trackCall
|
|
def __float__(self, *args):
|
|
return 1.0
|
|
|
|
@trackCall
|
|
def __eq__(self, *args):
|
|
return True
|
|
|
|
@trackCall
|
|
def __ne__(self, *args):
|
|
return False
|
|
|
|
@trackCall
|
|
def __lt__(self, *args):
|
|
return False
|
|
|
|
@trackCall
|
|
def __le__(self, *args):
|
|
return True
|
|
|
|
@trackCall
|
|
def __gt__(self, *args):
|
|
return False
|
|
|
|
@trackCall
|
|
def __ge__(self, *args):
|
|
return True
|
|
"""
|
|
|
|
# Synthesize all the other AllTests methods from the names in testmeths.
|
|
|
|
method_template = """\
|
|
@trackCall
|
|
def __%s__(self, *args):
|
|
pass
|
|
"""
|
|
|
|
d = {}
|
|
exec(statictests, globals(), d)
|
|
for method in testmeths:
|
|
exec(method_template % method, globals(), d)
|
|
AllTests = type("AllTests", (object,), d)
|
|
del d, statictests, method, method_template
|
|
|
|
class ClassTests(unittest.TestCase):
|
|
def setUp(self):
|
|
callLst[:] = []
|
|
|
|
def assertCallStack(self, expected_calls):
|
|
actualCallList = callLst[:] # need to copy because the comparison below will add
|
|
# additional calls to callLst
|
|
if expected_calls != actualCallList:
|
|
self.fail("Expected call list:\n %s\ndoes not match actual call list\n %s" %
|
|
(expected_calls, actualCallList))
|
|
|
|
def testInit(self):
|
|
foo = AllTests()
|
|
self.assertCallStack([("__init__", (foo,))])
|
|
|
|
def testBinaryOps(self):
|
|
testme = AllTests()
|
|
# Binary operations
|
|
|
|
callLst[:] = []
|
|
testme + 1
|
|
self.assertCallStack([("__add__", (testme, 1))])
|
|
|
|
callLst[:] = []
|
|
1 + testme
|
|
self.assertCallStack([("__radd__", (testme, 1))])
|
|
|
|
callLst[:] = []
|
|
testme - 1
|
|
self.assertCallStack([("__sub__", (testme, 1))])
|
|
|
|
callLst[:] = []
|
|
1 - testme
|
|
self.assertCallStack([("__rsub__", (testme, 1))])
|
|
|
|
callLst[:] = []
|
|
testme * 1
|
|
self.assertCallStack([("__mul__", (testme, 1))])
|
|
|
|
callLst[:] = []
|
|
1 * testme
|
|
self.assertCallStack([("__rmul__", (testme, 1))])
|
|
|
|
callLst[:] = []
|
|
testme @ 1
|
|
self.assertCallStack([("__matmul__", (testme, 1))])
|
|
|
|
callLst[:] = []
|
|
1 @ testme
|
|
self.assertCallStack([("__rmatmul__", (testme, 1))])
|
|
|
|
callLst[:] = []
|
|
testme / 1
|
|
self.assertCallStack([("__truediv__", (testme, 1))])
|
|
|
|
|
|
callLst[:] = []
|
|
1 / testme
|
|
self.assertCallStack([("__rtruediv__", (testme, 1))])
|
|
|
|
callLst[:] = []
|
|
testme // 1
|
|
self.assertCallStack([("__floordiv__", (testme, 1))])
|
|
|
|
|
|
callLst[:] = []
|
|
1 // testme
|
|
self.assertCallStack([("__rfloordiv__", (testme, 1))])
|
|
|
|
callLst[:] = []
|
|
testme % 1
|
|
self.assertCallStack([("__mod__", (testme, 1))])
|
|
|
|
callLst[:] = []
|
|
1 % testme
|
|
self.assertCallStack([("__rmod__", (testme, 1))])
|
|
|
|
|
|
callLst[:] = []
|
|
divmod(testme,1)
|
|
self.assertCallStack([("__divmod__", (testme, 1))])
|
|
|
|
callLst[:] = []
|
|
divmod(1, testme)
|
|
self.assertCallStack([("__rdivmod__", (testme, 1))])
|
|
|
|
callLst[:] = []
|
|
testme ** 1
|
|
self.assertCallStack([("__pow__", (testme, 1))])
|
|
|
|
callLst[:] = []
|
|
1 ** testme
|
|
self.assertCallStack([("__rpow__", (testme, 1))])
|
|
|
|
callLst[:] = []
|
|
testme >> 1
|
|
self.assertCallStack([("__rshift__", (testme, 1))])
|
|
|
|
callLst[:] = []
|
|
1 >> testme
|
|
self.assertCallStack([("__rrshift__", (testme, 1))])
|
|
|
|
callLst[:] = []
|
|
testme << 1
|
|
self.assertCallStack([("__lshift__", (testme, 1))])
|
|
|
|
callLst[:] = []
|
|
1 << testme
|
|
self.assertCallStack([("__rlshift__", (testme, 1))])
|
|
|
|
callLst[:] = []
|
|
testme & 1
|
|
self.assertCallStack([("__and__", (testme, 1))])
|
|
|
|
callLst[:] = []
|
|
1 & testme
|
|
self.assertCallStack([("__rand__", (testme, 1))])
|
|
|
|
callLst[:] = []
|
|
testme | 1
|
|
self.assertCallStack([("__or__", (testme, 1))])
|
|
|
|
callLst[:] = []
|
|
1 | testme
|
|
self.assertCallStack([("__ror__", (testme, 1))])
|
|
|
|
callLst[:] = []
|
|
testme ^ 1
|
|
self.assertCallStack([("__xor__", (testme, 1))])
|
|
|
|
callLst[:] = []
|
|
1 ^ testme
|
|
self.assertCallStack([("__rxor__", (testme, 1))])
|
|
|
|
def testListAndDictOps(self):
|
|
testme = AllTests()
|
|
|
|
# List/dict operations
|
|
|
|
class Empty: pass
|
|
|
|
try:
|
|
1 in Empty()
|
|
self.fail('failed, should have raised TypeError')
|
|
except TypeError:
|
|
pass
|
|
|
|
callLst[:] = []
|
|
1 in testme
|
|
self.assertCallStack([('__contains__', (testme, 1))])
|
|
|
|
callLst[:] = []
|
|
testme[1]
|
|
self.assertCallStack([('__getitem__', (testme, 1))])
|
|
|
|
callLst[:] = []
|
|
testme[1] = 1
|
|
self.assertCallStack([('__setitem__', (testme, 1, 1))])
|
|
|
|
callLst[:] = []
|
|
del testme[1]
|
|
self.assertCallStack([('__delitem__', (testme, 1))])
|
|
|
|
callLst[:] = []
|
|
testme[:42]
|
|
self.assertCallStack([('__getitem__', (testme, slice(None, 42)))])
|
|
|
|
callLst[:] = []
|
|
testme[:42] = "The Answer"
|
|
self.assertCallStack([('__setitem__', (testme, slice(None, 42),
|
|
"The Answer"))])
|
|
|
|
callLst[:] = []
|
|
del testme[:42]
|
|
self.assertCallStack([('__delitem__', (testme, slice(None, 42)))])
|
|
|
|
callLst[:] = []
|
|
testme[2:1024:10]
|
|
self.assertCallStack([('__getitem__', (testme, slice(2, 1024, 10)))])
|
|
|
|
callLst[:] = []
|
|
testme[2:1024:10] = "A lot"
|
|
self.assertCallStack([('__setitem__', (testme, slice(2, 1024, 10),
|
|
"A lot"))])
|
|
callLst[:] = []
|
|
del testme[2:1024:10]
|
|
self.assertCallStack([('__delitem__', (testme, slice(2, 1024, 10)))])
|
|
|
|
callLst[:] = []
|
|
testme[:42, ..., :24:, 24, 100]
|
|
self.assertCallStack([('__getitem__', (testme, (slice(None, 42, None),
|
|
Ellipsis,
|
|
slice(None, 24, None),
|
|
24, 100)))])
|
|
callLst[:] = []
|
|
testme[:42, ..., :24:, 24, 100] = "Strange"
|
|
self.assertCallStack([('__setitem__', (testme, (slice(None, 42, None),
|
|
Ellipsis,
|
|
slice(None, 24, None),
|
|
24, 100), "Strange"))])
|
|
callLst[:] = []
|
|
del testme[:42, ..., :24:, 24, 100]
|
|
self.assertCallStack([('__delitem__', (testme, (slice(None, 42, None),
|
|
Ellipsis,
|
|
slice(None, 24, None),
|
|
24, 100)))])
|
|
|
|
def testUnaryOps(self):
|
|
testme = AllTests()
|
|
|
|
callLst[:] = []
|
|
-testme
|
|
self.assertCallStack([('__neg__', (testme,))])
|
|
callLst[:] = []
|
|
+testme
|
|
self.assertCallStack([('__pos__', (testme,))])
|
|
callLst[:] = []
|
|
abs(testme)
|
|
self.assertCallStack([('__abs__', (testme,))])
|
|
callLst[:] = []
|
|
int(testme)
|
|
self.assertCallStack([('__int__', (testme,))])
|
|
callLst[:] = []
|
|
float(testme)
|
|
self.assertCallStack([('__float__', (testme,))])
|
|
callLst[:] = []
|
|
oct(testme)
|
|
self.assertCallStack([('__index__', (testme,))])
|
|
callLst[:] = []
|
|
hex(testme)
|
|
self.assertCallStack([('__index__', (testme,))])
|
|
|
|
|
|
def testMisc(self):
|
|
testme = AllTests()
|
|
|
|
callLst[:] = []
|
|
hash(testme)
|
|
self.assertCallStack([('__hash__', (testme,))])
|
|
|
|
callLst[:] = []
|
|
repr(testme)
|
|
self.assertCallStack([('__repr__', (testme,))])
|
|
|
|
callLst[:] = []
|
|
str(testme)
|
|
self.assertCallStack([('__str__', (testme,))])
|
|
|
|
callLst[:] = []
|
|
testme == 1
|
|
self.assertCallStack([('__eq__', (testme, 1))])
|
|
|
|
callLst[:] = []
|
|
testme < 1
|
|
self.assertCallStack([('__lt__', (testme, 1))])
|
|
|
|
callLst[:] = []
|
|
testme > 1
|
|
self.assertCallStack([('__gt__', (testme, 1))])
|
|
|
|
callLst[:] = []
|
|
testme != 1
|
|
self.assertCallStack([('__ne__', (testme, 1))])
|
|
|
|
callLst[:] = []
|
|
1 == testme
|
|
self.assertCallStack([('__eq__', (1, testme))])
|
|
|
|
callLst[:] = []
|
|
1 < testme
|
|
self.assertCallStack([('__gt__', (1, testme))])
|
|
|
|
callLst[:] = []
|
|
1 > testme
|
|
self.assertCallStack([('__lt__', (1, testme))])
|
|
|
|
callLst[:] = []
|
|
1 != testme
|
|
self.assertCallStack([('__ne__', (1, testme))])
|
|
|
|
|
|
def testGetSetAndDel(self):
|
|
# Interfering tests
|
|
class ExtraTests(AllTests):
|
|
@trackCall
|
|
def __getattr__(self, *args):
|
|
return "SomeVal"
|
|
|
|
@trackCall
|
|
def __setattr__(self, *args):
|
|
pass
|
|
|
|
@trackCall
|
|
def __delattr__(self, *args):
|
|
pass
|
|
|
|
testme = ExtraTests()
|
|
|
|
callLst[:] = []
|
|
testme.spam
|
|
self.assertCallStack([('__getattr__', (testme, "spam"))])
|
|
|
|
callLst[:] = []
|
|
testme.eggs = "spam, spam, spam and ham"
|
|
self.assertCallStack([('__setattr__', (testme, "eggs",
|
|
"spam, spam, spam and ham"))])
|
|
|
|
callLst[:] = []
|
|
del testme.cardinal
|
|
self.assertCallStack([('__delattr__', (testme, "cardinal"))])
|
|
|
|
def testHasAttrString(self):
|
|
import sys
|
|
from test.support import import_helper
|
|
_testlimitedcapi = import_helper.import_module('_testlimitedcapi')
|
|
|
|
class A:
|
|
def __init__(self):
|
|
self.attr = 1
|
|
|
|
a = A()
|
|
self.assertEqual(_testlimitedcapi.object_hasattrstring(a, b"attr"), 1)
|
|
self.assertEqual(_testlimitedcapi.object_hasattrstring(a, b"noattr"), 0)
|
|
self.assertIsNone(sys.exception())
|
|
|
|
def testDel(self):
|
|
x = []
|
|
|
|
class DelTest:
|
|
def __del__(self):
|
|
x.append("crab people, crab people")
|
|
testme = DelTest()
|
|
del testme
|
|
import gc
|
|
gc.collect()
|
|
self.assertEqual(["crab people, crab people"], x)
|
|
|
|
def testBadTypeReturned(self):
|
|
# return values of some method are type-checked
|
|
class BadTypeClass:
|
|
def __int__(self):
|
|
return None
|
|
__float__ = __int__
|
|
__complex__ = __int__
|
|
__str__ = __int__
|
|
__repr__ = __int__
|
|
__bytes__ = __int__
|
|
__bool__ = __int__
|
|
__index__ = __int__
|
|
def index(x):
|
|
return [][x]
|
|
|
|
for f in [float, complex, str, repr, bytes, bin, oct, hex, bool, index]:
|
|
self.assertRaises(TypeError, f, BadTypeClass())
|
|
|
|
def testHashStuff(self):
|
|
# Test correct errors from hash() on objects with comparisons but
|
|
# no __hash__
|
|
|
|
class C0:
|
|
pass
|
|
|
|
hash(C0()) # This should work; the next two should raise TypeError
|
|
|
|
class C2:
|
|
def __eq__(self, other): return 1
|
|
|
|
self.assertRaises(TypeError, hash, C2())
|
|
|
|
|
|
def testSFBug532646(self):
|
|
# Test for SF bug 532646
|
|
|
|
class A:
|
|
pass
|
|
A.__call__ = A()
|
|
a = A()
|
|
|
|
try:
|
|
a() # This should not segfault
|
|
except RecursionError:
|
|
pass
|
|
else:
|
|
self.fail("Failed to raise RecursionError")
|
|
|
|
def testForExceptionsRaisedInInstanceGetattr2(self):
|
|
# Tests for exceptions raised in instance_getattr2().
|
|
|
|
def booh(self):
|
|
raise AttributeError("booh")
|
|
|
|
class A:
|
|
a = property(booh)
|
|
try:
|
|
A().a # Raised AttributeError: A instance has no attribute 'a'
|
|
except AttributeError as x:
|
|
if str(x) != "booh":
|
|
self.fail("attribute error for A().a got masked: %s" % x)
|
|
|
|
class E:
|
|
__eq__ = property(booh)
|
|
E() == E() # In debug mode, caused a C-level assert() to fail
|
|
|
|
class I:
|
|
__init__ = property(booh)
|
|
try:
|
|
# In debug mode, printed XXX undetected error and
|
|
# raises AttributeError
|
|
I()
|
|
except AttributeError:
|
|
pass
|
|
else:
|
|
self.fail("attribute error for I.__init__ got masked")
|
|
|
|
def assertNotOrderable(self, a, b):
|
|
with self.assertRaises(TypeError):
|
|
a < b
|
|
with self.assertRaises(TypeError):
|
|
a > b
|
|
with self.assertRaises(TypeError):
|
|
a <= b
|
|
with self.assertRaises(TypeError):
|
|
a >= b
|
|
|
|
def testHashComparisonOfMethods(self):
|
|
# Test comparison and hash of methods
|
|
class A:
|
|
def __init__(self, x):
|
|
self.x = x
|
|
def f(self):
|
|
pass
|
|
def g(self):
|
|
pass
|
|
def __eq__(self, other):
|
|
return True
|
|
def __hash__(self):
|
|
raise TypeError
|
|
class B(A):
|
|
pass
|
|
|
|
a1 = A(1)
|
|
a2 = A(1)
|
|
self.assertTrue(a1.f == a1.f)
|
|
self.assertFalse(a1.f != a1.f)
|
|
self.assertFalse(a1.f == a2.f)
|
|
self.assertTrue(a1.f != a2.f)
|
|
self.assertFalse(a1.f == a1.g)
|
|
self.assertTrue(a1.f != a1.g)
|
|
self.assertNotOrderable(a1.f, a1.f)
|
|
self.assertEqual(hash(a1.f), hash(a1.f))
|
|
|
|
self.assertFalse(A.f == a1.f)
|
|
self.assertTrue(A.f != a1.f)
|
|
self.assertFalse(A.f == A.g)
|
|
self.assertTrue(A.f != A.g)
|
|
self.assertTrue(B.f == A.f)
|
|
self.assertFalse(B.f != A.f)
|
|
self.assertNotOrderable(A.f, A.f)
|
|
self.assertEqual(hash(B.f), hash(A.f))
|
|
|
|
# the following triggers a SystemError in 2.4
|
|
a = A(hash(A.f)^(-1))
|
|
hash(a.f)
|
|
|
|
def testSetattrWrapperNameIntern(self):
|
|
# Issue #25794: __setattr__ should intern the attribute name
|
|
class A:
|
|
pass
|
|
|
|
def add(self, other):
|
|
return 'summa'
|
|
|
|
name = str(b'__add__', 'ascii') # shouldn't be optimized
|
|
self.assertIsNot(name, '__add__') # not interned
|
|
type.__setattr__(A, name, add)
|
|
self.assertEqual(A() + 1, 'summa')
|
|
|
|
name2 = str(b'__add__', 'ascii')
|
|
self.assertIsNot(name2, '__add__')
|
|
self.assertIsNot(name2, name)
|
|
type.__delattr__(A, name2)
|
|
with self.assertRaises(TypeError):
|
|
A() + 1
|
|
|
|
def testSetattrNonStringName(self):
|
|
class A:
|
|
pass
|
|
|
|
with self.assertRaises(TypeError):
|
|
type.__setattr__(A, b'x', None)
|
|
|
|
def testTypeAttributeAccessErrorMessages(self):
|
|
class A:
|
|
pass
|
|
|
|
error_msg = "type object 'A' has no attribute 'x'"
|
|
with self.assertRaisesRegex(AttributeError, error_msg):
|
|
A.x
|
|
with self.assertRaisesRegex(AttributeError, error_msg):
|
|
del A.x
|
|
|
|
def testObjectAttributeAccessErrorMessages(self):
|
|
class A:
|
|
pass
|
|
class B:
|
|
y = 0
|
|
__slots__ = ('z',)
|
|
class C:
|
|
__slots__ = ("y",)
|
|
|
|
def __setattr__(self, name, value) -> None:
|
|
if name == "z":
|
|
super().__setattr__("y", 1)
|
|
else:
|
|
super().__setattr__(name, value)
|
|
|
|
error_msg = "'A' object has no attribute 'x'"
|
|
with self.assertRaisesRegex(AttributeError, error_msg):
|
|
A().x
|
|
with self.assertRaisesRegex(AttributeError, error_msg):
|
|
del A().x
|
|
|
|
error_msg = "'B' object has no attribute 'x'"
|
|
with self.assertRaisesRegex(AttributeError, error_msg):
|
|
B().x
|
|
with self.assertRaisesRegex(AttributeError, error_msg):
|
|
del B().x
|
|
with self.assertRaisesRegex(
|
|
AttributeError,
|
|
"'B' object has no attribute 'x' and no __dict__ for setting new attributes"
|
|
):
|
|
B().x = 0
|
|
with self.assertRaisesRegex(
|
|
AttributeError,
|
|
"'C' object has no attribute 'x'"
|
|
):
|
|
C().x = 0
|
|
|
|
error_msg = "'B' object attribute 'y' is read-only"
|
|
with self.assertRaisesRegex(AttributeError, error_msg):
|
|
del B().y
|
|
with self.assertRaisesRegex(AttributeError, error_msg):
|
|
B().y = 0
|
|
|
|
error_msg = 'z'
|
|
with self.assertRaisesRegex(AttributeError, error_msg):
|
|
B().z
|
|
with self.assertRaisesRegex(AttributeError, error_msg):
|
|
del B().z
|
|
|
|
def testConstructorErrorMessages(self):
|
|
# bpo-31506: Improves the error message logic for object_new & object_init
|
|
|
|
# Class without any method overrides
|
|
class C:
|
|
pass
|
|
|
|
error_msg = r'C.__init__\(\) takes exactly one argument \(the instance to initialize\)'
|
|
|
|
with self.assertRaisesRegex(TypeError, r'C\(\) takes no arguments'):
|
|
C(42)
|
|
|
|
with self.assertRaisesRegex(TypeError, r'C\(\) takes no arguments'):
|
|
C.__new__(C, 42)
|
|
|
|
with self.assertRaisesRegex(TypeError, error_msg):
|
|
C().__init__(42)
|
|
|
|
with self.assertRaisesRegex(TypeError, r'C\(\) takes no arguments'):
|
|
object.__new__(C, 42)
|
|
|
|
with self.assertRaisesRegex(TypeError, error_msg):
|
|
object.__init__(C(), 42)
|
|
|
|
# Class with both `__init__` & `__new__` method overridden
|
|
class D:
|
|
def __new__(cls, *args, **kwargs):
|
|
super().__new__(cls, *args, **kwargs)
|
|
def __init__(self, *args, **kwargs):
|
|
super().__init__(*args, **kwargs)
|
|
|
|
error_msg = r'object.__new__\(\) takes exactly one argument \(the type to instantiate\)'
|
|
|
|
with self.assertRaisesRegex(TypeError, error_msg):
|
|
D(42)
|
|
|
|
with self.assertRaisesRegex(TypeError, error_msg):
|
|
D.__new__(D, 42)
|
|
|
|
with self.assertRaisesRegex(TypeError, error_msg):
|
|
object.__new__(D, 42)
|
|
|
|
# Class that only overrides __init__
|
|
class E:
|
|
def __init__(self, *args, **kwargs):
|
|
super().__init__(*args, **kwargs)
|
|
|
|
error_msg = r'object.__init__\(\) takes exactly one argument \(the instance to initialize\)'
|
|
|
|
with self.assertRaisesRegex(TypeError, error_msg):
|
|
E().__init__(42)
|
|
|
|
with self.assertRaisesRegex(TypeError, error_msg):
|
|
object.__init__(E(), 42)
|
|
|
|
def testClassWithExtCall(self):
|
|
class Meta(int):
|
|
def __init__(*args, **kwargs):
|
|
pass
|
|
|
|
def __new__(cls, name, bases, attrs, **kwargs):
|
|
return bases, kwargs
|
|
|
|
d = {'metaclass': Meta}
|
|
|
|
class A(**d): pass
|
|
self.assertEqual(A, ((), {}))
|
|
class A(0, 1, 2, 3, 4, 5, 6, 7, **d): pass
|
|
self.assertEqual(A, (tuple(range(8)), {}))
|
|
class A(0, *range(1, 8), **d, foo='bar'): pass
|
|
self.assertEqual(A, (tuple(range(8)), {'foo': 'bar'}))
|
|
|
|
def testClassCallRecursionLimit(self):
|
|
class C:
|
|
def __init__(self):
|
|
self.c = C()
|
|
|
|
with self.assertRaises(RecursionError):
|
|
C()
|
|
|
|
def add_one_level():
|
|
#Each call to C() consumes 2 levels, so offset by 1.
|
|
C()
|
|
|
|
with self.assertRaises(RecursionError):
|
|
add_one_level()
|
|
|
|
def testMetaclassCallOptimization(self):
|
|
calls = 0
|
|
|
|
class TypeMetaclass(type):
|
|
def __call__(cls, *args, **kwargs):
|
|
nonlocal calls
|
|
calls += 1
|
|
return type.__call__(cls, *args, **kwargs)
|
|
|
|
class Type(metaclass=TypeMetaclass):
|
|
def __init__(self, obj):
|
|
self._obj = obj
|
|
|
|
for i in range(100):
|
|
Type(i)
|
|
self.assertEqual(calls, 100)
|
|
|
|
|
|
from _testinternalcapi import has_inline_values
|
|
|
|
Py_TPFLAGS_MANAGED_DICT = (1 << 2)
|
|
|
|
class Plain:
|
|
pass
|
|
|
|
|
|
class WithAttrs:
|
|
|
|
def __init__(self):
|
|
self.a = 1
|
|
self.b = 2
|
|
self.c = 3
|
|
self.d = 4
|
|
|
|
|
|
class TestInlineValues(unittest.TestCase):
|
|
|
|
def test_flags(self):
|
|
self.assertEqual(Plain.__flags__ & Py_TPFLAGS_MANAGED_DICT, Py_TPFLAGS_MANAGED_DICT)
|
|
self.assertEqual(WithAttrs.__flags__ & Py_TPFLAGS_MANAGED_DICT, Py_TPFLAGS_MANAGED_DICT)
|
|
|
|
def test_has_inline_values(self):
|
|
c = Plain()
|
|
self.assertTrue(has_inline_values(c))
|
|
del c.__dict__
|
|
self.assertFalse(has_inline_values(c))
|
|
|
|
def test_instances(self):
|
|
self.assertTrue(has_inline_values(Plain()))
|
|
self.assertTrue(has_inline_values(WithAttrs()))
|
|
|
|
def test_inspect_dict(self):
|
|
for cls in (Plain, WithAttrs):
|
|
c = cls()
|
|
c.__dict__
|
|
self.assertTrue(has_inline_values(c))
|
|
|
|
def test_update_dict(self):
|
|
d = { "e": 5, "f": 6 }
|
|
for cls in (Plain, WithAttrs):
|
|
c = cls()
|
|
c.__dict__.update(d)
|
|
self.assertTrue(has_inline_values(c))
|
|
|
|
@staticmethod
|
|
def set_100(obj):
|
|
for i in range(100):
|
|
setattr(obj, f"a{i}", i)
|
|
|
|
def check_100(self, obj):
|
|
for i in range(100):
|
|
self.assertEqual(getattr(obj, f"a{i}"), i)
|
|
|
|
def test_many_attributes(self):
|
|
class C: pass
|
|
c = C()
|
|
self.assertTrue(has_inline_values(c))
|
|
self.set_100(c)
|
|
self.assertFalse(has_inline_values(c))
|
|
self.check_100(c)
|
|
c = C()
|
|
self.assertTrue(has_inline_values(c))
|
|
|
|
def test_many_attributes_with_dict(self):
|
|
class C: pass
|
|
c = C()
|
|
d = c.__dict__
|
|
self.assertTrue(has_inline_values(c))
|
|
self.set_100(c)
|
|
self.assertFalse(has_inline_values(c))
|
|
self.check_100(c)
|
|
|
|
def test_bug_117750(self):
|
|
"Aborted on 3.13a6"
|
|
class C:
|
|
def __init__(self):
|
|
self.__dict__.clear()
|
|
|
|
obj = C()
|
|
self.assertEqual(obj.__dict__, {})
|
|
obj.foo = None # Aborted here
|
|
self.assertEqual(obj.__dict__, {"foo":None})
|
|
|
|
def test_store_attr_deleted_dict(self):
|
|
class Foo:
|
|
pass
|
|
|
|
f = Foo()
|
|
del f.__dict__
|
|
f.a = 3
|
|
self.assertEqual(f.a, 3)
|
|
|
|
def test_store_attr_type_cache(self):
|
|
"""Verifies that the type cache doesn't provide a value which is
|
|
inconsistent from the dict."""
|
|
class X:
|
|
def __del__(inner_self):
|
|
v = C.a
|
|
self.assertEqual(v, C.__dict__['a'])
|
|
|
|
class C:
|
|
a = X()
|
|
|
|
# prime the cache
|
|
C.a
|
|
C.a
|
|
|
|
# destructor shouldn't be able to see inconsisent state
|
|
C.a = X()
|
|
C.a = X()
|
|
|
|
|
|
if __name__ == '__main__':
|
|
unittest.main()
|