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_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)) diff --git a/Lib/test/test_property.py b/Lib/test/test_property.py index 7f3813fc8cd15e..9c88ebbc7a24e7 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 @@ -215,6 +217,82 @@ 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): + p = property[int, str] + self.assertIsInstance(p, GenericAlias) + self.assertIs(p.__origin__, property) + self.assertEqual(p.__args__, (int, str)) + self.assertEqual(p.__parameters__, ()) + + 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__, {}) + # 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 + + 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): """This is a subclass of 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} };