| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent 150af75 commit 507a574
8 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -269,6 +269,11 @@ are always available. They are listed here in alphabetical order. | |||
| 269 | 269 | Class methods can now wrap other :term:`descriptors <descriptor>` such as | |
| 270 | 270 | :func:`property`. | |
| 271 | 271 | ||
| 272 | + .. versionchanged:: 3.10 | ||
| 273 | + Class methods now inherit the method attributes (``__module__``, | ||
| 274 | + ``__name__``, ``__qualname__``, ``__doc__`` and ``__annotations__``) and | ||
| 275 | + have a new ``__wrapped__`` attribute. | ||
| 276 | + | ||
| 272 | 277 | .. function:: compile(source, filename, mode, flags=0, dont_inherit=False, optimize=-1) | |
| 273 | 278 | ||
| 274 | 279 | Compile the *source* into a code or AST object. Code objects can be executed | |
@@ -1632,6 +1637,11 @@ are always available. They are listed here in alphabetical order. | |||
| 1632 | 1637 | ||
| 1633 | 1638 | For more information on static methods, see :ref:`types`. | |
| 1634 | 1639 | ||
| 1640 | + .. versionchanged:: 3.10 | ||
| 1641 | + Static methods now inherit the method attributes (``__module__``, | ||
| 1642 | + ``__name__``, ``__qualname__``, ``__doc__`` and ``__annotations__``) and | ||
| 1643 | + have a new ``__wrapped__`` attribute. | ||
| 1644 | + | ||
| 1635 | 1645 | ||
| 1636 | 1646 | .. index:: | |
| 1637 | 1647 | single: string; str() (built-in function) | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -617,6 +617,12 @@ Other Language Changes | |||
| 617 | 617 | respectively. | |
| 618 | 618 | (Contributed by Joshua Bronson, Daniel Pope, and Justin Wang in :issue:`31861`.) | |
| 619 | 619 | ||
| 620 | + * Static methods (:func:`@staticmethod <staticmethod>`) and class methods | ||
| 621 | + (:func:`@classmethod <classmethod>`) now inherit the method attributes | ||
| 622 | + (``__module__``, ``__name__``, ``__qualname__``, ``__doc__``, | ||
| 623 | + ``__annotations__``) and have a new ``__wrapped__`` attribute. | ||
| 624 | + (Contributed by Victor Stinner in :issue:`43682`.) | ||
| 625 | + | ||
| 620 | 626 | ||
| 621 | 627 | New Modules | |
| 622 | 628 | =========== | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1,3 +1,4 @@ | |||
| 1 | + from test import support | ||
| 1 | 2 | import unittest | |
| 2 | 3 | ||
| 3 | 4 | def funcattrs(**kwds): | |
@@ -76,11 +77,28 @@ def foo(): return 42 | |||
| 76 | 77 | self.assertEqual(C.foo(), 42) | |
| 77 | 78 | self.assertEqual(C().foo(), 42) | |
| 78 | 79 | ||
| 79 | - def test_staticmethod_function(self): | ||
| 80 | - @staticmethod | ||
| 81 | - def notamethod(x): | ||
| 80 | + def check_wrapper_attrs(self, method_wrapper, format_str): | ||
| 81 | + def func(x): | ||
| 82 | 82 | return x | |
| 83 | - self.assertRaises(TypeError, notamethod, 1) | ||
| 83 | + wrapper = method_wrapper(func) | ||
| 84 | + | ||
| 85 | + self.assertIs(wrapper.__func__, func) | ||
| 86 | + self.assertIs(wrapper.__wrapped__, func) | ||
| 87 | + | ||
| 88 | + for attr in ('__module__', '__qualname__', '__name__', | ||
| 89 | + '__doc__', '__annotations__'): | ||
| 90 | + self.assertIs(getattr(wrapper, attr), | ||
| 91 | + getattr(func, attr)) | ||
| 92 | + | ||
| 93 | + self.assertEqual(repr(wrapper), format_str.format(func)) | ||
| 94 | + | ||
| 95 | + self.assertRaises(TypeError, wrapper, 1) | ||
| 96 | + | ||
| 97 | + def test_staticmethod(self): | ||
| 98 | + self.check_wrapper_attrs(staticmethod, '<staticmethod({!r})>') | ||
| 99 | + | ||
| 100 | + def test_classmethod(self): | ||
| 101 | + self.check_wrapper_attrs(classmethod, '<classmethod({!r})>') | ||
| 84 | 102 | ||
| 85 | 103 | def test_dotted(self): | |
| 86 | 104 | decorators = MiscDecorators() | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1545,7 +1545,9 @@ class D(C): | |||
| 1545 | 1545 | self.assertEqual(d.foo(1), (d, 1)) | |
| 1546 | 1546 | self.assertEqual(D.foo(d, 1), (d, 1)) | |
| 1547 | 1547 | # Test for a specific crash (SF bug 528132) | |
| 1548 | - def f(cls, arg): return (cls, arg) | ||
| 1548 | + def f(cls, arg): | ||
| 1549 | + "f docstring" | ||
| 1550 | + return (cls, arg) | ||
| 1549 | 1551 | ff = classmethod(f) | |
| 1550 | 1552 | self.assertEqual(ff.__get__(0, int)(42), (int, 42)) | |
| 1551 | 1553 | self.assertEqual(ff.__get__(0)(42), (int, 42)) | |
@@ -1571,10 +1573,16 @@ def f(cls, arg): return (cls, arg) | |||
| 1571 | 1573 | self.fail("classmethod shouldn't accept keyword args") | |
| 1572 | 1574 | ||
| 1573 | 1575 | cm = classmethod(f) | |
| 1574 | - self.assertEqual(cm.__dict__, {}) | ||
| 1576 | + cm_dict = {'__annotations__': {}, | ||
| 1577 | + '__doc__': "f docstring", | ||
| 1578 | + '__module__': __name__, | ||
| 1579 | + '__name__': 'f', | ||
| 1580 | + '__qualname__': f.__qualname__} | ||
| 1581 | + self.assertEqual(cm.__dict__, cm_dict) | ||
| 1582 | + | ||
| 1575 | 1583 | cm.x = 42 | |
| 1576 | 1584 | self.assertEqual(cm.x, 42) | |
| 1577 | - self.assertEqual(cm.__dict__, {"x" : 42}) | ||
| 1585 | + self.assertEqual(cm.__dict__, {"x" : 42, **cm_dict}) | ||
| 1578 | 1586 | del cm.x | |
| 1579 | 1587 | self.assertNotHasAttr(cm, "x") | |
| 1580 | 1588 | ||
@@ -1654,10 +1662,10 @@ class D(C): | |||
| 1654 | 1662 | self.assertEqual(d.foo(1), (d, 1)) | |
| 1655 | 1663 | self.assertEqual(D.foo(d, 1), (d, 1)) | |
| 1656 | 1664 | sm = staticmethod(None) | |
| 1657 | - self.assertEqual(sm.__dict__, {}) | ||
| 1665 | + self.assertEqual(sm.__dict__, {'__doc__': None}) | ||
| 1658 | 1666 | sm.x = 42 | |
| 1659 | 1667 | self.assertEqual(sm.x, 42) | |
| 1660 | - self.assertEqual(sm.__dict__, {"x" : 42}) | ||
| 1668 | + self.assertEqual(sm.__dict__, {"x" : 42, '__doc__': None}) | ||
| 1661 | 1669 | del sm.x | |
| 1662 | 1670 | self.assertNotHasAttr(sm, "x") | |
| 1663 | 1671 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1142,7 +1142,8 @@ def sm(x, y): | |||
| 1142 | 1142 | '''A static method''' | |
| 1143 | 1143 | ... | |
| 1144 | 1144 | self.assertEqual(self._get_summary_lines(X.__dict__['sm']), | |
| 1145 | - "<staticmethod object>") | ||
| 1145 | + 'sm(...)\n' | ||
| 1146 | + ' A static method\n') | ||
| 1146 | 1147 | self.assertEqual(self._get_summary_lines(X.sm), """\ | |
| 1147 | 1148 | sm(x, y) | |
| 1148 | 1149 | A static method | |
@@ -1162,7 +1163,8 @@ def cm(cls, x): | |||
| 1162 | 1163 | '''A class method''' | |
| 1163 | 1164 | ... | |
| 1164 | 1165 | self.assertEqual(self._get_summary_lines(X.__dict__['cm']), | |
| 1165 | - "<classmethod object>") | ||
| 1166 | + 'cm(...)\n' | ||
| 1167 | + ' A class method\n') | ||
| 1166 | 1168 | self.assertEqual(self._get_summary_lines(X.cm), """\ | |
| 1167 | 1169 | cm(x) method of builtins.type instance | |
| 1168 | 1170 | A class method | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -203,9 +203,9 @@ def test_descriptors(self): | |||
| 203 | 203 | class C: | |
| 204 | 204 | def foo(cls): pass | |
| 205 | 205 | x = staticmethod(C.foo) | |
| 206 | - self.assertTrue(repr(x).startswith('<staticmethod object at 0x')) | ||
| 206 | + self.assertEqual(repr(x), f'<staticmethod({C.foo!r})>') | ||
| 207 | 207 | x = classmethod(C.foo) | |
| 208 | - self.assertTrue(repr(x).startswith('<classmethod object at 0x')) | ||
| 208 | + self.assertEqual(repr(x), f'<classmethod({C.foo!r})>') | ||
| 209 | 209 | ||
| 210 | 210 | def test_unsortable(self): | |
| 211 | 211 | # Repr.repr() used to call sorted() on sets, frozensets and dicts | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,5 @@ | |||
| 1 | + Static methods (:func:`@staticmethod <staticmethod>`) and class methods | ||
| 2 | + (:func:`@classmethod <classmethod>`) now inherit the method attributes | ||
| 3 | + (``__module__``, ``__name__``, ``__qualname__``, ``__doc__``, | ||
| 4 | + ``__annotations__``) and have a new ``__wrapped__`` attribute. | ||
| 5 | + Patch by Victor Stinner. | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -639,7 +639,7 @@ static PyObject* | |||
| 639 | 639 | func_repr(PyFunctionObject *op) | |
| 640 | 640 | { | |
| 641 | 641 | return PyUnicode_FromFormat("<function %U at %p>", | |
| 642 | - op->func_qualname, op); | ||
| 642 | + op->func_qualname, op); | ||
| 643 | 643 | } | |
| 644 | 644 | ||
| 645 | 645 | static int | |
@@ -715,6 +715,50 @@ PyTypeObject PyFunction_Type = { | |||
| 715 | 715 | }; | |
| 716 | 716 | ||
| 717 | 717 | ||
| 718 | + static int | ||
| 719 | + functools_copy_attr(PyObject *wrapper, PyObject *wrapped, PyObject *name) | ||
| 720 | + { | ||
| 721 | + PyObject *value = PyObject_GetAttr(wrapped, name); | ||
| 722 | + if (value == NULL) { | ||
| 723 | + if (PyErr_ExceptionMatches(PyExc_AttributeError)) { | ||
| 724 | + PyErr_Clear(); | ||
| 725 | + return 0; | ||
| 726 | + } | ||
| 727 | + return -1; | ||
| 728 | + } | ||
| 729 | + | ||
| 730 | + int res = PyObject_SetAttr(wrapper, name, value); | ||
| 731 | + Py_DECREF(value); | ||
| 732 | + return res; | ||
| 733 | + } | ||
| 734 | + | ||
| 735 | + // Similar to functools.wraps(wrapper, wrapped) | ||
| 736 | + static int | ||
| 737 | + functools_wraps(PyObject *wrapper, PyObject *wrapped) | ||
| 738 | + { | ||
| 739 | + #define COPY_ATTR(ATTR) \ | ||
| 740 | + do { \ | ||
| 741 | + _Py_IDENTIFIER(ATTR); \ | ||
| 742 | + PyObject *attr = _PyUnicode_FromId(&PyId_ ## ATTR); \ | ||
| 743 | + if (attr == NULL) { \ | ||
| 744 | + return -1; \ | ||
| 745 | + } \ | ||
| 746 | + if (functools_copy_attr(wrapper, wrapped, attr) < 0) { \ | ||
| 747 | + return -1; \ | ||
| 748 | + } \ | ||
| 749 | + } while (0) \ | ||
| 750 | + | ||
| 751 | + COPY_ATTR(__module__); | ||
| 752 | + COPY_ATTR(__name__); | ||
| 753 | + COPY_ATTR(__qualname__); | ||
| 754 | + COPY_ATTR(__doc__); | ||
| 755 | + COPY_ATTR(__annotations__); | ||
| 756 | + return 0; | ||
| 757 | + | ||
| 758 | + #undef COPY_ATTR | ||
| 759 | + } | ||
| 760 | + | ||
| 761 | + | ||
| 718 | 762 | /* Class method object */ | |
| 719 | 763 | ||
| 720 | 764 | /* A class method receives the class as implicit first argument, | |
@@ -798,11 +842,16 @@ cm_init(PyObject *self, PyObject *args, PyObject *kwds) | |||
| 798 | 842 | return -1; | |
| 799 | 843 | Py_INCREF(callable); | |
| 800 | 844 | Py_XSETREF(cm->cm_callable, callable); | |
| 845 | + | ||
| 846 | + if (functools_wraps((PyObject *)cm, cm->cm_callable) < 0) { | ||
| 847 | + return -1; | ||
| 848 | + } | ||
| 801 | 849 | return 0; | |
| 802 | 850 | } | |
| 803 | 851 | ||
| 804 | 852 | static PyMemberDef cm_memberlist[] = { | |
| 805 | 853 | {"__func__", T_OBJECT, offsetof(classmethod, cm_callable), READONLY}, | |
| 854 | + {"__wrapped__", T_OBJECT, offsetof(classmethod, cm_callable), READONLY}, | ||
| 806 | 855 | {NULL} /* Sentinel */ | |
| 807 | 856 | }; | |
| 808 | 857 | ||
@@ -821,13 +870,17 @@ cm_get___isabstractmethod__(classmethod *cm, void *closure) | |||
| 821 | 870 | ||
| 822 | 871 | static PyGetSetDef cm_getsetlist[] = { | |
| 823 | 872 | {"__isabstractmethod__", | |
| 824 | - (getter)cm_get___isabstractmethod__, NULL, | ||
| 825 | - NULL, | ||
| 826 | - NULL}, | ||
| 873 | + (getter)cm_get___isabstractmethod__, NULL, NULL, NULL}, | ||
| 827 | 874 | {"__dict__", PyObject_GenericGetDict, PyObject_GenericSetDict, NULL, NULL}, | |
| 828 | 875 | {NULL} /* Sentinel */ | |
| 829 | 876 | }; | |
| 830 | 877 | ||
| 878 | + static PyObject* | ||
| 879 | + cm_repr(classmethod *cm) | ||
| 880 | + { | ||
| 881 | + return PyUnicode_FromFormat("<classmethod(%R)>", cm->cm_callable); | ||
| 882 | + } | ||
| 883 | + | ||
| 831 | 884 | PyDoc_STRVAR(classmethod_doc, | |
| 832 | 885 | "classmethod(function) -> method\n\ | |
| 833 | 886 | \n\ | |
@@ -860,7 +913,7 @@ PyTypeObject PyClassMethod_Type = { | |||
| 860 | 913 | 0, /* tp_getattr */ | |
| 861 | 914 | 0, /* tp_setattr */ | |
| 862 | 915 | 0, /* tp_as_async */ | |
| 863 | - 0, /* tp_repr */ | ||
| 916 | + (reprfunc)cm_repr, /* tp_repr */ | ||
| 864 | 917 | 0, /* tp_as_number */ | |
| 865 | 918 | 0, /* tp_as_sequence */ | |
| 866 | 919 | 0, /* tp_as_mapping */ | |
@@ -980,11 +1033,16 @@ sm_init(PyObject *self, PyObject *args, PyObject *kwds) | |||
| 980 | 1033 | return -1; | |
| 981 | 1034 | Py_INCREF(callable); | |
| 982 | 1035 | Py_XSETREF(sm->sm_callable, callable); | |
| 1036 | + | ||
| 1037 | + if (functools_wraps((PyObject *)sm, sm->sm_callable) < 0) { | ||
| 1038 | + return -1; | ||
| 1039 | + } | ||
| 983 | 1040 | return 0; | |
| 984 | 1041 | } | |
| 985 | 1042 | ||
| 986 | 1043 | static PyMemberDef sm_memberlist[] = { | |
| 987 | 1044 | {"__func__", T_OBJECT, offsetof(staticmethod, sm_callable), READONLY}, | |
| 1045 | + {"__wrapped__", T_OBJECT, offsetof(staticmethod, sm_callable), READONLY}, | ||
| 988 | 1046 | {NULL} /* Sentinel */ | |
| 989 | 1047 | }; | |
| 990 | 1048 | ||
@@ -1003,13 +1061,17 @@ sm_get___isabstractmethod__(staticmethod *sm, void *closure) | |||
| 1003 | 1061 | ||
| 1004 | 1062 | static PyGetSetDef sm_getsetlist[] = { | |
| 1005 | 1063 | {"__isabstractmethod__", | |
| 1006 | - (getter)sm_get___isabstractmethod__, NULL, | ||
| 1007 | - NULL, | ||
| 1008 | - NULL}, | ||
| 1064 | + (getter)sm_get___isabstractmethod__, NULL, NULL, NULL}, | ||
| 1009 | 1065 | {"__dict__", PyObject_GenericGetDict, PyObject_GenericSetDict, NULL, NULL}, | |
| 1010 | 1066 | {NULL} /* Sentinel */ | |
| 1011 | 1067 | }; | |
| 1012 | 1068 | ||
| 1069 | + static PyObject* | ||
| 1070 | + sm_repr(staticmethod *sm) | ||
| 1071 | + { | ||
| 1072 | + return PyUnicode_FromFormat("<staticmethod(%R)>", sm->sm_callable); | ||
| 1073 | + } | ||
| 1074 | + | ||
| 1013 | 1075 | PyDoc_STRVAR(staticmethod_doc, | |
| 1014 | 1076 | "staticmethod(function) -> method\n\ | |
| 1015 | 1077 | \n\ | |
@@ -1040,7 +1102,7 @@ PyTypeObject PyStaticMethod_Type = { | |||
| 1040 | 1102 | 0, /* tp_getattr */ | |
| 1041 | 1103 | 0, /* tp_setattr */ | |
| 1042 | 1104 | 0, /* tp_as_async */ | |
| 1043 | - 0, /* tp_repr */ | ||
| 1105 | + (reprfunc)sm_repr, /* tp_repr */ | ||
| 1044 | 1106 | 0, /* tp_as_number */ | |
| 1045 | 1107 | 0, /* tp_as_sequence */ | |
| 1046 | 1108 | 0, /* tp_as_mapping */ | |
| Back | FazBrowse Home | New Git URL |
0 commit comments