mirror of
https://github.com/python/cpython.git
synced 2025-07-25 12:14:38 +00:00

The error messages in `object.__new__` and `object.__init__` now aim to point the user more directly at the name of the class being instantiated in cases where they *haven't* been overridden (on the assumption that the actual problem is a missing `__new__` or `__init__` definition in the class body). When they *have* been overridden, the errors still report themselves as coming from object, on the assumption that the problem is with the call up to the base class in the method implementation, rather than with the way the constructor is being called.
648 lines
17 KiB
Python
648 lines
17 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 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 as x:
|
|
pass
|
|
else:
|
|
self.fail("attribute error for I.__init__ got masked")
|
|
|
|
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 self.x == other.x
|
|
def __hash__(self):
|
|
return self.x
|
|
class B(A):
|
|
pass
|
|
|
|
a1 = A(1)
|
|
a2 = A(2)
|
|
self.assertEqual(a1.f, a1.f)
|
|
self.assertNotEqual(a1.f, a2.f)
|
|
self.assertNotEqual(a1.f, a1.g)
|
|
self.assertEqual(a1.f, A(1).f)
|
|
self.assertEqual(hash(a1.f), hash(a1.f))
|
|
self.assertEqual(hash(a1.f), hash(A(1).f))
|
|
|
|
self.assertNotEqual(A.f, a1.f)
|
|
self.assertNotEqual(A.f, A.g)
|
|
self.assertEqual(B.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 testConstructorErrorMessages(self):
|
|
# bpo-31506: Improves the error message logic for object_new & object_init
|
|
|
|
# Class without any method overrides
|
|
class C:
|
|
pass
|
|
|
|
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, r'C\(\).__init__\(\) takes no arguments'):
|
|
C().__init__(42)
|
|
|
|
with self.assertRaisesRegex(TypeError, r'C\(\) takes no arguments'):
|
|
object.__new__(C, 42)
|
|
|
|
with self.assertRaisesRegex(TypeError, r'C\(\).__init__\(\) takes no arguments'):
|
|
object.__init__(C(), 42)
|
|
|
|
# Class with both `__init__` & `__new__` method overriden
|
|
class D:
|
|
def __new__(cls, *args, **kwargs):
|
|
super().__new__(cls, *args, **kwargs)
|
|
def __init__(self, *args, **kwargs):
|
|
super().__init__(*args, **kwargs)
|
|
|
|
with self.assertRaisesRegex(TypeError, r'object.__new__\(\) takes no argument'):
|
|
D(42)
|
|
|
|
with self.assertRaisesRegex(TypeError, r'object.__new__\(\) takes no argument'):
|
|
D.__new__(D, 42)
|
|
|
|
with self.assertRaisesRegex(TypeError, r'object.__new__\(\) takes no argument'):
|
|
object.__new__(D, 42)
|
|
|
|
# Class that only overrides __init__
|
|
class E:
|
|
def __init__(self, *args, **kwargs):
|
|
super().__init__(*args, **kwargs)
|
|
|
|
with self.assertRaisesRegex(TypeError, r'object.__init__\(\) takes no argument'):
|
|
E().__init__(42)
|
|
|
|
with self.assertRaisesRegex(TypeError, r'object.__init__\(\) takes no argument'):
|
|
object.__init__(E(), 42)
|
|
|
|
if __name__ == '__main__':
|
|
unittest.main()
|