(root)/
Python-3.12.0/
Lib/
test/
test_property.py
       1  # Test case for property
       2  # more tests are in test_descr
       3  
       4  import sys
       5  import unittest
       6  from test import support
       7  
       8  class ESC[4;38;5;81mPropertyBase(ESC[4;38;5;149mException):
       9      pass
      10  
      11  class ESC[4;38;5;81mPropertyGet(ESC[4;38;5;149mPropertyBase):
      12      pass
      13  
      14  class ESC[4;38;5;81mPropertySet(ESC[4;38;5;149mPropertyBase):
      15      pass
      16  
      17  class ESC[4;38;5;81mPropertyDel(ESC[4;38;5;149mPropertyBase):
      18      pass
      19  
      20  class ESC[4;38;5;81mBaseClass(ESC[4;38;5;149mobject):
      21      def __init__(self):
      22          self._spam = 5
      23  
      24      @property
      25      def spam(self):
      26          """BaseClass.getter"""
      27          return self._spam
      28  
      29      @spam.setter
      30      def spam(self, value):
      31          self._spam = value
      32  
      33      @spam.deleter
      34      def spam(self):
      35          del self._spam
      36  
      37  class ESC[4;38;5;81mSubClass(ESC[4;38;5;149mBaseClass):
      38  
      39      @BaseClass.spam.getter
      40      def spam(self):
      41          """SubClass.getter"""
      42          raise PropertyGet(self._spam)
      43  
      44      @spam.setter
      45      def spam(self, value):
      46          raise PropertySet(self._spam)
      47  
      48      @spam.deleter
      49      def spam(self):
      50          raise PropertyDel(self._spam)
      51  
      52  class ESC[4;38;5;81mPropertyDocBase(ESC[4;38;5;149mobject):
      53      _spam = 1
      54      def _get_spam(self):
      55          return self._spam
      56      spam = property(_get_spam, doc="spam spam spam")
      57  
      58  class ESC[4;38;5;81mPropertyDocSub(ESC[4;38;5;149mPropertyDocBase):
      59      @PropertyDocBase.spam.getter
      60      def spam(self):
      61          """The decorator does not use this doc string"""
      62          return self._spam
      63  
      64  class ESC[4;38;5;81mPropertySubNewGetter(ESC[4;38;5;149mBaseClass):
      65      @BaseClass.spam.getter
      66      def spam(self):
      67          """new docstring"""
      68          return 5
      69  
      70  class ESC[4;38;5;81mPropertyNewGetter(ESC[4;38;5;149mobject):
      71      @property
      72      def spam(self):
      73          """original docstring"""
      74          return 1
      75      @spam.getter
      76      def spam(self):
      77          """new docstring"""
      78          return 8
      79  
      80  class ESC[4;38;5;81mPropertyTests(ESC[4;38;5;149munittestESC[4;38;5;149m.ESC[4;38;5;149mTestCase):
      81      def test_property_decorator_baseclass(self):
      82          # see #1620
      83          base = BaseClass()
      84          self.assertEqual(base.spam, 5)
      85          self.assertEqual(base._spam, 5)
      86          base.spam = 10
      87          self.assertEqual(base.spam, 10)
      88          self.assertEqual(base._spam, 10)
      89          delattr(base, "spam")
      90          self.assertTrue(not hasattr(base, "spam"))
      91          self.assertTrue(not hasattr(base, "_spam"))
      92          base.spam = 20
      93          self.assertEqual(base.spam, 20)
      94          self.assertEqual(base._spam, 20)
      95  
      96      def test_property_decorator_subclass(self):
      97          # see #1620
      98          sub = SubClass()
      99          self.assertRaises(PropertyGet, getattr, sub, "spam")
     100          self.assertRaises(PropertySet, setattr, sub, "spam", None)
     101          self.assertRaises(PropertyDel, delattr, sub, "spam")
     102  
     103      @unittest.skipIf(sys.flags.optimize >= 2,
     104                       "Docstrings are omitted with -O2 and above")
     105      def test_property_decorator_subclass_doc(self):
     106          sub = SubClass()
     107          self.assertEqual(sub.__class__.spam.__doc__, "SubClass.getter")
     108  
     109      @unittest.skipIf(sys.flags.optimize >= 2,
     110                       "Docstrings are omitted with -O2 and above")
     111      def test_property_decorator_baseclass_doc(self):
     112          base = BaseClass()
     113          self.assertEqual(base.__class__.spam.__doc__, "BaseClass.getter")
     114  
     115      def test_property_decorator_doc(self):
     116          base = PropertyDocBase()
     117          sub = PropertyDocSub()
     118          self.assertEqual(base.__class__.spam.__doc__, "spam spam spam")
     119          self.assertEqual(sub.__class__.spam.__doc__, "spam spam spam")
     120  
     121      @unittest.skipIf(sys.flags.optimize >= 2,
     122                       "Docstrings are omitted with -O2 and above")
     123      def test_property_getter_doc_override(self):
     124          newgettersub = PropertySubNewGetter()
     125          self.assertEqual(newgettersub.spam, 5)
     126          self.assertEqual(newgettersub.__class__.spam.__doc__, "new docstring")
     127          newgetter = PropertyNewGetter()
     128          self.assertEqual(newgetter.spam, 8)
     129          self.assertEqual(newgetter.__class__.spam.__doc__, "new docstring")
     130  
     131      def test_property___isabstractmethod__descriptor(self):
     132          for val in (True, False, [], [1], '', '1'):
     133              class ESC[4;38;5;81mC(ESC[4;38;5;149mobject):
     134                  def foo(self):
     135                      pass
     136                  foo.__isabstractmethod__ = val
     137                  foo = property(foo)
     138              self.assertIs(C.foo.__isabstractmethod__, bool(val))
     139  
     140          # check that the property's __isabstractmethod__ descriptor does the
     141          # right thing when presented with a value that fails truth testing:
     142          class ESC[4;38;5;81mNotBool(ESC[4;38;5;149mobject):
     143              def __bool__(self):
     144                  raise ValueError()
     145              __len__ = __bool__
     146          with self.assertRaises(ValueError):
     147              class ESC[4;38;5;81mC(ESC[4;38;5;149mobject):
     148                  def foo(self):
     149                      pass
     150                  foo.__isabstractmethod__ = NotBool()
     151                  foo = property(foo)
     152              C.foo.__isabstractmethod__
     153  
     154      @unittest.skipIf(sys.flags.optimize >= 2,
     155                       "Docstrings are omitted with -O2 and above")
     156      def test_property_builtin_doc_writable(self):
     157          p = property(doc='basic')
     158          self.assertEqual(p.__doc__, 'basic')
     159          p.__doc__ = 'extended'
     160          self.assertEqual(p.__doc__, 'extended')
     161  
     162      @unittest.skipIf(sys.flags.optimize >= 2,
     163                       "Docstrings are omitted with -O2 and above")
     164      def test_property_decorator_doc_writable(self):
     165          class ESC[4;38;5;81mPropertyWritableDoc(ESC[4;38;5;149mobject):
     166  
     167              @property
     168              def spam(self):
     169                  """Eggs"""
     170                  return "eggs"
     171  
     172          sub = PropertyWritableDoc()
     173          self.assertEqual(sub.__class__.spam.__doc__, 'Eggs')
     174          sub.__class__.spam.__doc__ = 'Spam'
     175          self.assertEqual(sub.__class__.spam.__doc__, 'Spam')
     176  
     177      @support.refcount_test
     178      def test_refleaks_in___init__(self):
     179          gettotalrefcount = support.get_attribute(sys, 'gettotalrefcount')
     180          fake_prop = property('fget', 'fset', 'fdel', 'doc')
     181          refs_before = gettotalrefcount()
     182          for i in range(100):
     183              fake_prop.__init__('fget', 'fset', 'fdel', 'doc')
     184          self.assertAlmostEqual(gettotalrefcount() - refs_before, 0, delta=10)
     185  
     186      @unittest.skipIf(sys.flags.optimize >= 2,
     187                       "Docstrings are omitted with -O2 and above")
     188      def test_class_property(self):
     189          class ESC[4;38;5;81mA:
     190              @classmethod
     191              @property
     192              def __doc__(cls):
     193                  return 'A doc for %r' % cls.__name__
     194          self.assertEqual(A.__doc__, "A doc for 'A'")
     195  
     196      @unittest.skipIf(sys.flags.optimize >= 2,
     197                       "Docstrings are omitted with -O2 and above")
     198      def test_class_property_override(self):
     199          class ESC[4;38;5;81mA:
     200              """First"""
     201              @classmethod
     202              @property
     203              def __doc__(cls):
     204                  return 'Second'
     205          self.assertEqual(A.__doc__, 'Second')
     206  
     207      def test_property_set_name_incorrect_args(self):
     208          p = property()
     209  
     210          for i in (0, 1, 3):
     211              with self.assertRaisesRegex(
     212                  TypeError,
     213                  fr'^__set_name__\(\) takes 2 positional arguments but {i} were given$'
     214              ):
     215                  p.__set_name__(*([0] * i))
     216  
     217      def test_property_setname_on_property_subclass(self):
     218          # https://github.com/python/cpython/issues/100942
     219          # Copy was setting the name field without first
     220          # verifying that the copy was an actual property
     221          # instance.  As a result, the code below was
     222          # causing a segfault.
     223  
     224          class ESC[4;38;5;81mpro(ESC[4;38;5;149mproperty):
     225              def __new__(typ, *args, **kwargs):
     226                  return "abcdef"
     227  
     228          class ESC[4;38;5;81mA:
     229              pass
     230  
     231          p = property.__new__(pro)
     232          p.__set_name__(A, 1)
     233          np = p.getter(lambda self: 1)
     234  
     235  # Issue 5890: subclasses of property do not preserve method __doc__ strings
     236  class ESC[4;38;5;81mPropertySub(ESC[4;38;5;149mproperty):
     237      """This is a subclass of property"""
     238  
     239  class ESC[4;38;5;81mPropertySubWoDoc(ESC[4;38;5;149mproperty):
     240      pass
     241  
     242  class ESC[4;38;5;81mPropertySubSlots(ESC[4;38;5;149mproperty):
     243      """This is a subclass of property that defines __slots__"""
     244      __slots__ = ()
     245  
     246  class ESC[4;38;5;81mPropertySubclassTests(ESC[4;38;5;149munittestESC[4;38;5;149m.ESC[4;38;5;149mTestCase):
     247  
     248      def test_slots_docstring_copy_exception(self):
     249          # A special case error that we preserve despite the GH-98963 behavior
     250          # that would otherwise silently ignore this error.
     251          # This came from commit b18500d39d791c879e9904ebac293402b4a7cd34
     252          # as part of https://bugs.python.org/issue5890 which allowed docs to
     253          # be set via property subclasses in the first place.
     254          with self.assertRaises(AttributeError):
     255              class ESC[4;38;5;81mFoo(ESC[4;38;5;149mobject):
     256                  @PropertySubSlots
     257                  def spam(self):
     258                      """Trying to copy this docstring will raise an exception"""
     259                      return 1
     260  
     261      def test_property_with_slots_no_docstring(self):
     262          # https://github.com/python/cpython/issues/98963#issuecomment-1574413319
     263          class ESC[4;38;5;81mslotted_prop(ESC[4;38;5;149mproperty):
     264              __slots__ = ("foo",)
     265  
     266          p = slotted_prop()  # no AttributeError
     267          self.assertIsNone(getattr(p, "__doc__", None))
     268  
     269          def undocumented_getter():
     270              return 4
     271  
     272          p = slotted_prop(undocumented_getter)  # New in 3.12: no AttributeError
     273          self.assertIsNone(getattr(p, "__doc__", None))
     274  
     275      @unittest.skipIf(sys.flags.optimize >= 2,
     276                       "Docstrings are omitted with -O2 and above")
     277      def test_property_with_slots_docstring_silently_dropped(self):
     278          # https://github.com/python/cpython/issues/98963#issuecomment-1574413319
     279          class ESC[4;38;5;81mslotted_prop(ESC[4;38;5;149mproperty):
     280              __slots__ = ("foo",)
     281  
     282          p = slotted_prop(doc="what's up")  # no AttributeError
     283          self.assertIsNone(p.__doc__)
     284  
     285          def documented_getter():
     286              """getter doc."""
     287              return 4
     288  
     289          # Historical behavior: A docstring from a getter always raises.
     290          # (matches test_slots_docstring_copy_exception above).
     291          with self.assertRaises(AttributeError):
     292              p = slotted_prop(documented_getter)
     293  
     294      @unittest.skipIf(sys.flags.optimize >= 2,
     295                       "Docstrings are omitted with -O2 and above")
     296      def test_property_with_slots_and_doc_slot_docstring_present(self):
     297          # https://github.com/python/cpython/issues/98963#issuecomment-1574413319
     298          class ESC[4;38;5;81mslotted_prop(ESC[4;38;5;149mproperty):
     299              __slots__ = ("foo", "__doc__")
     300  
     301          p = slotted_prop(doc="what's up")
     302          self.assertEqual("what's up", p.__doc__)  # new in 3.12: This gets set.
     303  
     304          def documented_getter():
     305              """what's up getter doc?"""
     306              return 4
     307  
     308          p = slotted_prop(documented_getter)
     309          self.assertEqual("what's up getter doc?", p.__doc__)
     310  
     311      @unittest.skipIf(sys.flags.optimize >= 2,
     312                       "Docstrings are omitted with -O2 and above")
     313      def test_issue41287(self):
     314  
     315          self.assertEqual(PropertySub.__doc__, "This is a subclass of property",
     316                           "Docstring of `property` subclass is ignored")
     317  
     318          doc = PropertySub(None, None, None, "issue 41287 is fixed").__doc__
     319          self.assertEqual(doc, "issue 41287 is fixed",
     320                           "Subclasses of `property` ignores `doc` constructor argument")
     321  
     322          def getter(x):
     323              """Getter docstring"""
     324  
     325          def getter_wo_doc(x):
     326              pass
     327  
     328          for ps in property, PropertySub, PropertySubWoDoc:
     329              doc = ps(getter, None, None, "issue 41287 is fixed").__doc__
     330              self.assertEqual(doc, "issue 41287 is fixed",
     331                               "Getter overrides explicit property docstring (%s)" % ps.__name__)
     332  
     333              doc = ps(getter, None, None, None).__doc__
     334              self.assertEqual(doc, "Getter docstring", "Getter docstring is not picked-up (%s)" % ps.__name__)
     335  
     336              doc = ps(getter_wo_doc, None, None, "issue 41287 is fixed").__doc__
     337              self.assertEqual(doc, "issue 41287 is fixed",
     338                               "Getter overrides explicit property docstring (%s)" % ps.__name__)
     339  
     340              doc = ps(getter_wo_doc, None, None, None).__doc__
     341              self.assertIsNone(doc, "Property class doc appears in instance __doc__ (%s)" % ps.__name__)
     342  
     343      @unittest.skipIf(sys.flags.optimize >= 2,
     344                       "Docstrings are omitted with -O2 and above")
     345      def test_docstring_copy(self):
     346          class ESC[4;38;5;81mFoo(ESC[4;38;5;149mobject):
     347              @PropertySub
     348              def spam(self):
     349                  """spam wrapped in property subclass"""
     350                  return 1
     351          self.assertEqual(
     352              Foo.spam.__doc__,
     353              "spam wrapped in property subclass")
     354  
     355      @unittest.skipIf(sys.flags.optimize >= 2,
     356                       "Docstrings are omitted with -O2 and above")
     357      def test_docstring_copy2(self):
     358          """
     359          Property tries to provide the best docstring it finds for its instances.
     360          If a user-provided docstring is available, it is preserved on copies.
     361          If no docstring is available during property creation, the property
     362          will utilize the docstring from the getter if available.
     363          """
     364          def getter1(self):
     365              return 1
     366          def getter2(self):
     367              """doc 2"""
     368              return 2
     369          def getter3(self):
     370              """doc 3"""
     371              return 3
     372  
     373          # Case-1: user-provided doc is preserved in copies
     374          #         of property with undocumented getter
     375          p = property(getter1, None, None, "doc-A")
     376  
     377          p2 = p.getter(getter2)
     378          self.assertEqual(p.__doc__, "doc-A")
     379          self.assertEqual(p2.__doc__, "doc-A")
     380  
     381          # Case-2: user-provided doc is preserved in copies
     382          #         of property with documented getter
     383          p = property(getter2, None, None, "doc-A")
     384  
     385          p2 = p.getter(getter3)
     386          self.assertEqual(p.__doc__, "doc-A")
     387          self.assertEqual(p2.__doc__, "doc-A")
     388  
     389          # Case-3: with no user-provided doc new getter doc
     390          #         takes precendence
     391          p = property(getter2, None, None, None)
     392  
     393          p2 = p.getter(getter3)
     394          self.assertEqual(p.__doc__, "doc 2")
     395          self.assertEqual(p2.__doc__, "doc 3")
     396  
     397          # Case-4: A user-provided doc is assigned after property construction
     398          #         with documented getter. The doc IS NOT preserved.
     399          #         It's an odd behaviour, but it's a strange enough
     400          #         use case with no easy solution.
     401          p = property(getter2, None, None, None)
     402          p.__doc__ = "user"
     403          p2 = p.getter(getter3)
     404          self.assertEqual(p.__doc__, "user")
     405          self.assertEqual(p2.__doc__, "doc 3")
     406  
     407          # Case-5: A user-provided doc is assigned after property construction
     408          #         with UNdocumented getter. The doc IS preserved.
     409          p = property(getter1, None, None, None)
     410          p.__doc__ = "user"
     411          p2 = p.getter(getter2)
     412          self.assertEqual(p.__doc__, "user")
     413          self.assertEqual(p2.__doc__, "user")
     414  
     415      @unittest.skipIf(sys.flags.optimize >= 2,
     416                       "Docstrings are omitted with -O2 and above")
     417      def test_property_setter_copies_getter_docstring(self):
     418          class ESC[4;38;5;81mFoo(ESC[4;38;5;149mobject):
     419              def __init__(self): self._spam = 1
     420              @PropertySub
     421              def spam(self):
     422                  """spam wrapped in property subclass"""
     423                  return self._spam
     424              @spam.setter
     425              def spam(self, value):
     426                  """this docstring is ignored"""
     427                  self._spam = value
     428          foo = Foo()
     429          self.assertEqual(foo.spam, 1)
     430          foo.spam = 2
     431          self.assertEqual(foo.spam, 2)
     432          self.assertEqual(
     433              Foo.spam.__doc__,
     434              "spam wrapped in property subclass")
     435          class ESC[4;38;5;81mFooSub(ESC[4;38;5;149mFoo):
     436              @Foo.spam.setter
     437              def spam(self, value):
     438                  """another ignored docstring"""
     439                  self._spam = 'eggs'
     440          foosub = FooSub()
     441          self.assertEqual(foosub.spam, 1)
     442          foosub.spam = 7
     443          self.assertEqual(foosub.spam, 'eggs')
     444          self.assertEqual(
     445              FooSub.spam.__doc__,
     446              "spam wrapped in property subclass")
     447  
     448      @unittest.skipIf(sys.flags.optimize >= 2,
     449                       "Docstrings are omitted with -O2 and above")
     450      def test_property_new_getter_new_docstring(self):
     451  
     452          class ESC[4;38;5;81mFoo(ESC[4;38;5;149mobject):
     453              @PropertySub
     454              def spam(self):
     455                  """a docstring"""
     456                  return 1
     457              @spam.getter
     458              def spam(self):
     459                  """a new docstring"""
     460                  return 2
     461          self.assertEqual(Foo.spam.__doc__, "a new docstring")
     462          class ESC[4;38;5;81mFooBase(ESC[4;38;5;149mobject):
     463              @PropertySub
     464              def spam(self):
     465                  """a docstring"""
     466                  return 1
     467          class ESC[4;38;5;81mFoo2(ESC[4;38;5;149mFooBase):
     468              @FooBase.spam.getter
     469              def spam(self):
     470                  """a new docstring"""
     471                  return 2
     472          self.assertEqual(Foo.spam.__doc__, "a new docstring")
     473  
     474  
     475  class ESC[4;38;5;81m_PropertyUnreachableAttribute:
     476      msg_format = None
     477      obj = None
     478      cls = None
     479  
     480      def _format_exc_msg(self, msg):
     481          return self.msg_format.format(msg)
     482  
     483      @classmethod
     484      def setUpClass(cls):
     485          cls.obj = cls.cls()
     486  
     487      def test_get_property(self):
     488          with self.assertRaisesRegex(AttributeError, self._format_exc_msg("has no getter")):
     489              self.obj.foo
     490  
     491      def test_set_property(self):
     492          with self.assertRaisesRegex(AttributeError, self._format_exc_msg("has no setter")):
     493              self.obj.foo = None
     494  
     495      def test_del_property(self):
     496          with self.assertRaisesRegex(AttributeError, self._format_exc_msg("has no deleter")):
     497              del self.obj.foo
     498  
     499  
     500  class ESC[4;38;5;81mPropertyUnreachableAttributeWithName(ESC[4;38;5;149m_PropertyUnreachableAttribute, ESC[4;38;5;149munittestESC[4;38;5;149m.ESC[4;38;5;149mTestCase):
     501      msg_format = r"^property 'foo' of 'PropertyUnreachableAttributeWithName\.cls' object {}$"
     502  
     503      class ESC[4;38;5;81mcls:
     504          foo = property()
     505  
     506  
     507  class ESC[4;38;5;81mPropertyUnreachableAttributeNoName(ESC[4;38;5;149m_PropertyUnreachableAttribute, ESC[4;38;5;149munittestESC[4;38;5;149m.ESC[4;38;5;149mTestCase):
     508      msg_format = r"^property of 'PropertyUnreachableAttributeNoName\.cls' object {}$"
     509  
     510      class ESC[4;38;5;81mcls:
     511          pass
     512  
     513      cls.foo = property()
     514  
     515  
     516  if __name__ == '__main__':
     517      unittest.main()