From 40292650001c4226ed700393098a4480e46ffdf5 Mon Sep 17 00:00:00 2001 From: sobolevn Date: Thu, 23 Dec 2021 12:54:38 +0300 Subject: [PATCH 1/6] bpo-46162: make `property` generic --- Lib/test/test_property.py | 4 ++++ .../next/Library/2021-12-23-12-53-34.bpo-46162.bE3xrz.rst | 1 + Objects/descrobject.c | 1 + 3 files changed, 6 insertions(+) create mode 100644 Misc/NEWS.d/next/Library/2021-12-23-12-53-34.bpo-46162.bE3xrz.rst diff --git a/Lib/test/test_property.py b/Lib/test/test_property.py index 7f3813fc8cd15e..25d24cf54ffbf7 100644 --- a/Lib/test/test_property.py +++ b/Lib/test/test_property.py @@ -4,6 +4,7 @@ import sys import unittest from test import support +from types import GenericAlias class PropertyBase(Exception): pass @@ -214,6 +215,9 @@ def test_property_set_name_incorrect_args(self): ): p.__set_name__(*([0] * i)) + def test_property___class_getitem__(self): + self.assertIsInstance(property[int, str], GenericAlias) + # Issue 5890: subclasses of property do not preserve method __doc__ strings class PropertySub(property): diff --git a/Misc/NEWS.d/next/Library/2021-12-23-12-53-34.bpo-46162.bE3xrz.rst b/Misc/NEWS.d/next/Library/2021-12-23-12-53-34.bpo-46162.bE3xrz.rst new file mode 100644 index 00000000000000..7ef43f05da8125 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2021-12-23-12-53-34.bpo-46162.bE3xrz.rst @@ -0,0 +1 @@ +Make :class:`property` generic. diff --git a/Objects/descrobject.c b/Objects/descrobject.c index 946ea6aa80319a..174187fabd64c2 100644 --- a/Objects/descrobject.c +++ b/Objects/descrobject.c @@ -1566,6 +1566,7 @@ static PyMethodDef property_methods[] = { {"setter", property_setter, METH_O, setter_doc}, {"deleter", property_deleter, METH_O, deleter_doc}, {"__set_name__", property_set_name, METH_VARARGS, set_name_doc}, + {"__class_getitem__", Py_GenericAlias, METH_O|METH_CLASS, PyDoc_STR("See PEP 585")}, {0} }; From 4b94f77d5add763e550b34d8f8d7f317ff9608be Mon Sep 17 00:00:00 2001 From: sobolevn Date: Thu, 23 Dec 2021 16:06:19 +0300 Subject: [PATCH 2/6] Improve test --- Lib/test/test_property.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/Lib/test/test_property.py b/Lib/test/test_property.py index 25d24cf54ffbf7..e79fdb922fa9d3 100644 --- a/Lib/test/test_property.py +++ b/Lib/test/test_property.py @@ -216,7 +216,18 @@ def test_property_set_name_incorrect_args(self): p.__set_name__(*([0] * i)) def test_property___class_getitem__(self): - self.assertIsInstance(property[int, str], GenericAlias) + p = property[int, str] + self.assertIsInstance(p, GenericAlias) + self.assertIs(p.__origin__, property) + self.assertEqual(p.__args__, (int, str)) + self.assertEqual(p.__parameters__, ()) + + from typing import TypeVar + G = TypeVar('G') + S = TypeVar('S') + p1 = property[G, S] + self.assertEqual(p1.__args__, (G, S)) + self.assertEqual(p1.__parameters__, (G, S)) # Issue 5890: subclasses of property do not preserve method __doc__ strings From b682749dabfbbc30ce1e3c4889ce8ba27aeee744 Mon Sep 17 00:00:00 2001 From: sobolevn Date: Thu, 23 Dec 2021 16:09:09 +0300 Subject: [PATCH 3/6] Improve test --- Lib/test/test_property.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/test_property.py b/Lib/test/test_property.py index e79fdb922fa9d3..677c45514a528f 100644 --- a/Lib/test/test_property.py +++ b/Lib/test/test_property.py @@ -4,7 +4,6 @@ import sys import unittest from test import support -from types import GenericAlias class PropertyBase(Exception): pass @@ -216,6 +215,7 @@ def test_property_set_name_incorrect_args(self): p.__set_name__(*([0] * i)) def test_property___class_getitem__(self): + from types import GenericAlias p = property[int, str] self.assertIsInstance(p, GenericAlias) self.assertIs(p.__origin__, property) From 77aa07a26f37f93ddb8ff99573719786a24767a7 Mon Sep 17 00:00:00 2001 From: sobolevn Date: Thu, 23 Dec 2021 18:39:52 +0300 Subject: [PATCH 4/6] Add `property` to `test_genericalias` --- Lib/test/test_genericalias.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Lib/test/test_genericalias.py b/Lib/test/test_genericalias.py index 706cc5ea1af2f0..fc43d2aa2c5617 100644 --- a/Lib/test/test_genericalias.py +++ b/Lib/test/test_genericalias.py @@ -83,7 +83,9 @@ class BaseTest(unittest.TestCase): WeakSet, ReferenceType, ref, ShareableList, MPSimpleQueue, Future, _WorkItem, - Morsel] + Morsel, + property, + ] if ctypes is not None: generic_types.extend((ctypes.Array, ctypes.LibraryLoader)) From 4be39360fdb92528ac354502046a5d5bdb96251c Mon Sep 17 00:00:00 2001 From: sobolevn Date: Fri, 24 Dec 2021 09:16:08 +0300 Subject: [PATCH 5/6] More tests --- Lib/test/ann_module7.py | 5 +++ Lib/test/test_property.py | 64 +++++++++++++++++++++++++++++++++++++-- 2 files changed, 67 insertions(+), 2 deletions(-) create mode 100644 Lib/test/ann_module7.py diff --git a/Lib/test/ann_module7.py b/Lib/test/ann_module7.py new file mode 100644 index 00000000000000..294917bf60c775 --- /dev/null +++ b/Lib/test/ann_module7.py @@ -0,0 +1,5 @@ +# Test that generic `property` works with future import. + +from __future__ import annotations + +p: property[int, str] = property() diff --git a/Lib/test/test_property.py b/Lib/test/test_property.py index 677c45514a528f..48c782170aef6c 100644 --- a/Lib/test/test_property.py +++ b/Lib/test/test_property.py @@ -4,6 +4,8 @@ import sys import unittest from test import support +from types import GenericAlias +from typing import TypeVar, get_type_hints class PropertyBase(Exception): pass @@ -214,21 +216,79 @@ def test_property_set_name_incorrect_args(self): ): p.__set_name__(*([0] * i)) + +class Getter: + @property + def a(self) -> int: + pass + +class GetterSetter: + @property + def a(self) -> int: + pass + @a.setter + def a(self, arg: str) -> None: + pass + +class GetterSetterDeleter: + @property + def a(self) -> int: + pass + @a.setter + def a(self, arg: str) -> None: + pass + @a.deleter + def a(self) -> None: + pass + + +class GenericPropertyTests(unittest.TestCase): def test_property___class_getitem__(self): - from types import GenericAlias p = property[int, str] self.assertIsInstance(p, GenericAlias) self.assertIs(p.__origin__, property) self.assertEqual(p.__args__, (int, str)) self.assertEqual(p.__parameters__, ()) - from typing import TypeVar G = TypeVar('G') S = TypeVar('S') p1 = property[G, S] self.assertEqual(p1.__args__, (G, S)) self.assertEqual(p1.__parameters__, (G, S)) + # The number of type arguments is not limited: + p2 = property[int, str, bool] + self.assertEqual(p2.__args__, (int, str, bool)) + p3 = property[str] + self.assertEqual(p3.__args__, (str,)) + + def test_property_and_get_type_hints(self): + # Properties should not be present in `get_type_hints` + # or `__annotations__` of a class. + for klass in (Getter, GetterSetter, GetterSetterDeleter): + with self.subTest(klass=klass): + self.assertEqual(get_type_hints(klass), {}) + self.assertEqual(klass.__annotations__, {}) + + def test_property_inner_annotations(self): + from inspect import getattr_static + + p1 = getattr_static(Getter, 'a') + self.assertEqual(get_type_hints(p1.fget), {'return': int}) + + p2 = getattr_static(GetterSetter, 'a') + self.assertEqual(get_type_hints(p2.fget), {'return': int}) + self.assertEqual(get_type_hints(p2.fset), {'arg': str, 'return': type(None)}) + + p3 = getattr_static(GetterSetterDeleter, 'a') + self.assertEqual(get_type_hints(p3.fget), {'return': int}) + self.assertEqual(get_type_hints(p3.fset), {'arg': str, 'return': type(None)}) + self.assertEqual(get_type_hints(p3.fdel), {'return': type(None)}) + + def test_property_and_future_import(self): + from test import ann_module7 + self.assertEqual(get_type_hints(ann_module7), {'p': property[int, str]}) + # Issue 5890: subclasses of property do not preserve method __doc__ strings class PropertySub(property): From f636cf8494d40cee51379e845e4689ab19637d4b Mon Sep 17 00:00:00 2001 From: sobolevn Date: Fri, 24 Dec 2021 10:11:46 +0300 Subject: [PATCH 6/6] Ensure that you cannot get type hints of a property --- Lib/test/test_property.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Lib/test/test_property.py b/Lib/test/test_property.py index 48c782170aef6c..9c88ebbc7a24e7 100644 --- a/Lib/test/test_property.py +++ b/Lib/test/test_property.py @@ -269,6 +269,9 @@ def test_property_and_get_type_hints(self): with self.subTest(klass=klass): self.assertEqual(get_type_hints(klass), {}) self.assertEqual(klass.__annotations__, {}) + # Getting hints of `property` itself is not allowed + with self.assertRaises(TypeError): + get_type_hints(klass.a) def test_property_inner_annotations(self): from inspect import getattr_static