test_property.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371
  1. # Test case for property
  2. # more tests are in test_descr
  3. import sys
  4. import unittest
  5. from test import support
  6. class PropertyBase(Exception):
  7. pass
  8. class PropertyGet(PropertyBase):
  9. pass
  10. class PropertySet(PropertyBase):
  11. pass
  12. class PropertyDel(PropertyBase):
  13. pass
  14. class BaseClass(object):
  15. def __init__(self):
  16. self._spam = 5
  17. @property
  18. def spam(self):
  19. """BaseClass.getter"""
  20. return self._spam
  21. @spam.setter
  22. def spam(self, value):
  23. self._spam = value
  24. @spam.deleter
  25. def spam(self):
  26. del self._spam
  27. class SubClass(BaseClass):
  28. @BaseClass.spam.getter
  29. def spam(self):
  30. """SubClass.getter"""
  31. raise PropertyGet(self._spam)
  32. @spam.setter
  33. def spam(self, value):
  34. raise PropertySet(self._spam)
  35. @spam.deleter
  36. def spam(self):
  37. raise PropertyDel(self._spam)
  38. class PropertyDocBase(object):
  39. _spam = 1
  40. def _get_spam(self):
  41. return self._spam
  42. spam = property(_get_spam, doc="spam spam spam")
  43. class PropertyDocSub(PropertyDocBase):
  44. @PropertyDocBase.spam.getter
  45. def spam(self):
  46. """The decorator does not use this doc string"""
  47. return self._spam
  48. class PropertySubNewGetter(BaseClass):
  49. @BaseClass.spam.getter
  50. def spam(self):
  51. """new docstring"""
  52. return 5
  53. class PropertyNewGetter(object):
  54. @property
  55. def spam(self):
  56. """original docstring"""
  57. return 1
  58. @spam.getter
  59. def spam(self):
  60. """new docstring"""
  61. return 8
  62. class PropertyTests(unittest.TestCase):
  63. def test_property_decorator_baseclass(self):
  64. # see #1620
  65. base = BaseClass()
  66. self.assertEqual(base.spam, 5)
  67. self.assertEqual(base._spam, 5)
  68. base.spam = 10
  69. self.assertEqual(base.spam, 10)
  70. self.assertEqual(base._spam, 10)
  71. delattr(base, "spam")
  72. self.assertTrue(not hasattr(base, "spam"))
  73. self.assertTrue(not hasattr(base, "_spam"))
  74. base.spam = 20
  75. self.assertEqual(base.spam, 20)
  76. self.assertEqual(base._spam, 20)
  77. def test_property_decorator_subclass(self):
  78. # see #1620
  79. sub = SubClass()
  80. self.assertRaises(PropertyGet, getattr, sub, "spam")
  81. self.assertRaises(PropertySet, setattr, sub, "spam", None)
  82. self.assertRaises(PropertyDel, delattr, sub, "spam")
  83. @unittest.skipIf(sys.flags.optimize >= 2,
  84. "Docstrings are omitted with -O2 and above")
  85. def test_property_decorator_subclass_doc(self):
  86. sub = SubClass()
  87. self.assertEqual(sub.__class__.spam.__doc__, "SubClass.getter")
  88. @unittest.skipIf(sys.flags.optimize >= 2,
  89. "Docstrings are omitted with -O2 and above")
  90. def test_property_decorator_baseclass_doc(self):
  91. base = BaseClass()
  92. self.assertEqual(base.__class__.spam.__doc__, "BaseClass.getter")
  93. def test_property_decorator_doc(self):
  94. base = PropertyDocBase()
  95. sub = PropertyDocSub()
  96. self.assertEqual(base.__class__.spam.__doc__, "spam spam spam")
  97. self.assertEqual(sub.__class__.spam.__doc__, "spam spam spam")
  98. @unittest.skipIf(sys.flags.optimize >= 2,
  99. "Docstrings are omitted with -O2 and above")
  100. def test_property_getter_doc_override(self):
  101. newgettersub = PropertySubNewGetter()
  102. self.assertEqual(newgettersub.spam, 5)
  103. self.assertEqual(newgettersub.__class__.spam.__doc__, "new docstring")
  104. newgetter = PropertyNewGetter()
  105. self.assertEqual(newgetter.spam, 8)
  106. self.assertEqual(newgetter.__class__.spam.__doc__, "new docstring")
  107. def test_property___isabstractmethod__descriptor(self):
  108. for val in (True, False, [], [1], '', '1'):
  109. class C(object):
  110. def foo(self):
  111. pass
  112. foo.__isabstractmethod__ = val
  113. foo = property(foo)
  114. self.assertIs(C.foo.__isabstractmethod__, bool(val))
  115. # check that the property's __isabstractmethod__ descriptor does the
  116. # right thing when presented with a value that fails truth testing:
  117. class NotBool(object):
  118. def __bool__(self):
  119. raise ValueError()
  120. __len__ = __bool__
  121. with self.assertRaises(ValueError):
  122. class C(object):
  123. def foo(self):
  124. pass
  125. foo.__isabstractmethod__ = NotBool()
  126. foo = property(foo)
  127. C.foo.__isabstractmethod__
  128. @unittest.skipIf(sys.flags.optimize >= 2,
  129. "Docstrings are omitted with -O2 and above")
  130. def test_property_builtin_doc_writable(self):
  131. p = property(doc='basic')
  132. self.assertEqual(p.__doc__, 'basic')
  133. p.__doc__ = 'extended'
  134. self.assertEqual(p.__doc__, 'extended')
  135. @unittest.skipIf(sys.flags.optimize >= 2,
  136. "Docstrings are omitted with -O2 and above")
  137. def test_property_decorator_doc_writable(self):
  138. class PropertyWritableDoc(object):
  139. @property
  140. def spam(self):
  141. """Eggs"""
  142. return "eggs"
  143. sub = PropertyWritableDoc()
  144. self.assertEqual(sub.__class__.spam.__doc__, 'Eggs')
  145. sub.__class__.spam.__doc__ = 'Spam'
  146. self.assertEqual(sub.__class__.spam.__doc__, 'Spam')
  147. @support.refcount_test
  148. def test_refleaks_in___init__(self):
  149. gettotalrefcount = support.get_attribute(sys, 'gettotalrefcount')
  150. fake_prop = property('fget', 'fset', 'fdel', 'doc')
  151. refs_before = gettotalrefcount()
  152. for i in range(100):
  153. fake_prop.__init__('fget', 'fset', 'fdel', 'doc')
  154. self.assertAlmostEqual(gettotalrefcount() - refs_before, 0, delta=10)
  155. @unittest.skipIf(sys.flags.optimize >= 2,
  156. "Docstrings are omitted with -O2 and above")
  157. def test_class_property(self):
  158. class A:
  159. @classmethod
  160. @property
  161. def __doc__(cls):
  162. return 'A doc for %r' % cls.__name__
  163. self.assertEqual(A.__doc__, "A doc for 'A'")
  164. @unittest.skipIf(sys.flags.optimize >= 2,
  165. "Docstrings are omitted with -O2 and above")
  166. def test_class_property_override(self):
  167. class A:
  168. """First"""
  169. @classmethod
  170. @property
  171. def __doc__(cls):
  172. return 'Second'
  173. self.assertEqual(A.__doc__, 'Second')
  174. def test_property_set_name_incorrect_args(self):
  175. p = property()
  176. for i in (0, 1, 3):
  177. with self.assertRaisesRegex(
  178. TypeError,
  179. fr'^__set_name__\(\) takes 2 positional arguments but {i} were given$'
  180. ):
  181. p.__set_name__(*([0] * i))
  182. def test_property_setname_on_property_subclass(self):
  183. # https://github.com/python/cpython/issues/100942
  184. # Copy was setting the name field without first
  185. # verifying that the copy was an actual property
  186. # instance. As a result, the code below was
  187. # causing a segfault.
  188. class pro(property):
  189. def __new__(typ, *args, **kwargs):
  190. return "abcdef"
  191. class A:
  192. pass
  193. p = property.__new__(pro)
  194. p.__set_name__(A, 1)
  195. np = p.getter(lambda self: 1)
  196. # Issue 5890: subclasses of property do not preserve method __doc__ strings
  197. class PropertySub(property):
  198. """This is a subclass of property"""
  199. class PropertySubSlots(property):
  200. """This is a subclass of property that defines __slots__"""
  201. __slots__ = ()
  202. class PropertySubclassTests(unittest.TestCase):
  203. def test_slots_docstring_copy_exception(self):
  204. try:
  205. class Foo(object):
  206. @PropertySubSlots
  207. def spam(self):
  208. """Trying to copy this docstring will raise an exception"""
  209. return 1
  210. except AttributeError:
  211. pass
  212. else:
  213. raise Exception("AttributeError not raised")
  214. @unittest.skipIf(sys.flags.optimize >= 2,
  215. "Docstrings are omitted with -O2 and above")
  216. def test_docstring_copy(self):
  217. class Foo(object):
  218. @PropertySub
  219. def spam(self):
  220. """spam wrapped in property subclass"""
  221. return 1
  222. self.assertEqual(
  223. Foo.spam.__doc__,
  224. "spam wrapped in property subclass")
  225. @unittest.skipIf(sys.flags.optimize >= 2,
  226. "Docstrings are omitted with -O2 and above")
  227. def test_property_setter_copies_getter_docstring(self):
  228. class Foo(object):
  229. def __init__(self): self._spam = 1
  230. @PropertySub
  231. def spam(self):
  232. """spam wrapped in property subclass"""
  233. return self._spam
  234. @spam.setter
  235. def spam(self, value):
  236. """this docstring is ignored"""
  237. self._spam = value
  238. foo = Foo()
  239. self.assertEqual(foo.spam, 1)
  240. foo.spam = 2
  241. self.assertEqual(foo.spam, 2)
  242. self.assertEqual(
  243. Foo.spam.__doc__,
  244. "spam wrapped in property subclass")
  245. class FooSub(Foo):
  246. @Foo.spam.setter
  247. def spam(self, value):
  248. """another ignored docstring"""
  249. self._spam = 'eggs'
  250. foosub = FooSub()
  251. self.assertEqual(foosub.spam, 1)
  252. foosub.spam = 7
  253. self.assertEqual(foosub.spam, 'eggs')
  254. self.assertEqual(
  255. FooSub.spam.__doc__,
  256. "spam wrapped in property subclass")
  257. @unittest.skipIf(sys.flags.optimize >= 2,
  258. "Docstrings are omitted with -O2 and above")
  259. def test_property_new_getter_new_docstring(self):
  260. class Foo(object):
  261. @PropertySub
  262. def spam(self):
  263. """a docstring"""
  264. return 1
  265. @spam.getter
  266. def spam(self):
  267. """a new docstring"""
  268. return 2
  269. self.assertEqual(Foo.spam.__doc__, "a new docstring")
  270. class FooBase(object):
  271. @PropertySub
  272. def spam(self):
  273. """a docstring"""
  274. return 1
  275. class Foo2(FooBase):
  276. @FooBase.spam.getter
  277. def spam(self):
  278. """a new docstring"""
  279. return 2
  280. self.assertEqual(Foo.spam.__doc__, "a new docstring")
  281. class _PropertyUnreachableAttribute:
  282. msg_format = None
  283. obj = None
  284. cls = None
  285. def _format_exc_msg(self, msg):
  286. return self.msg_format.format(msg)
  287. @classmethod
  288. def setUpClass(cls):
  289. cls.obj = cls.cls()
  290. def test_get_property(self):
  291. with self.assertRaisesRegex(AttributeError, self._format_exc_msg("has no getter")):
  292. self.obj.foo
  293. def test_set_property(self):
  294. with self.assertRaisesRegex(AttributeError, self._format_exc_msg("has no setter")):
  295. self.obj.foo = None
  296. def test_del_property(self):
  297. with self.assertRaisesRegex(AttributeError, self._format_exc_msg("has no deleter")):
  298. del self.obj.foo
  299. class PropertyUnreachableAttributeWithName(_PropertyUnreachableAttribute, unittest.TestCase):
  300. msg_format = r"^property 'foo' of 'PropertyUnreachableAttributeWithName\.cls' object {}$"
  301. class cls:
  302. foo = property()
  303. class PropertyUnreachableAttributeNoName(_PropertyUnreachableAttribute, unittest.TestCase):
  304. msg_format = r"^property of 'PropertyUnreachableAttributeNoName\.cls' object {}$"
  305. class cls:
  306. pass
  307. cls.foo = property()
  308. if __name__ == '__main__':
  309. unittest.main()