| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -2262,6 +2262,12 @@ Changes in the Python API | |||
| 2262 | 2262 | returned by :meth:`zipfile.ZipFile.open` was changed from ``'r'`` to ``'rb'``. | |
| 2263 | 2263 | (Contributed by Serhiy Storchaka in :gh:`115961`.) | |
| 2264 | 2264 | ||
| 2265 | + * :class:`functools.partial` now emits a :exc:`FutureWarning` when it is | ||
| 2266 | + used as a method. | ||
| 2267 | + Its behavior will be changed in future Python versions. | ||
| 2268 | + Wrap it in :func:`staticmethod` if you want to preserve the old behavior. | ||
| 2269 | + (Contributed by Serhiy Storchaka in :gh:`121027`.) | ||
| 2270 | + | ||
| 2265 | 2271 | .. _pep667-porting-notes-py: | |
| 2266 | 2272 | ||
| 2267 | 2273 | * Calling :func:`locals` in an :term:`optimized scope` now produces an | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -311,6 +311,16 @@ def __repr__(self): | |||
| 311 | 311 | args.extend(f"{k}={v!r}" for (k, v) in self.keywords.items()) | |
| 312 | 312 | return f"{module}.{qualname}({', '.join(args)})" | |
| 313 | 313 | ||
| 314 | + def __get__(self, obj, objtype=None): | ||
| 315 | + if obj is None: | ||
| 316 | + return self | ||
| 317 | + import warnings | ||
| 318 | + warnings.warn('functools.partial will be a method descriptor in ' | ||
| 319 | + 'future Python versions; wrap it in staticmethod() ' | ||
| 320 | + 'if you want to preserve the old behavior', | ||
| 321 | + FutureWarning, 2) | ||
| 322 | + return self | ||
| 323 | + | ||
| 314 | 324 | def __reduce__(self): | |
| 315 | 325 | return type(self), (self.func,), (self.func, self.args, | |
| 316 | 326 | self.keywords or None, self.__dict__ or None) | |
@@ -392,7 +402,7 @@ def _method(cls_or_self, /, *args, **keywords): | |||
| 392 | 402 | def __get__(self, obj, cls=None): | |
| 393 | 403 | get = getattr(self.func, "__get__", None) | |
| 394 | 404 | result = None | |
| 395 | - if get is not None: | ||
| 405 | + if get is not None and not isinstance(self.func, partial): | ||
| 396 | 406 | new_func = get(obj, cls) | |
| 397 | 407 | if new_func is not self.func: | |
| 398 | 408 | # Assume __get__ returning something new indicates the | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -2550,6 +2550,10 @@ def _signature_from_callable(obj, *, | |||
| 2550 | 2550 | new_params = (first_wrapped_param,) + sig_params | |
| 2551 | 2551 | return sig.replace(parameters=new_params) | |
| 2552 | 2552 | ||
| 2553 | + if isinstance(obj, functools.partial): | ||
| 2554 | + wrapped_sig = _get_signature_of(obj.func) | ||
| 2555 | + return _signature_get_partial(wrapped_sig, obj) | ||
| 2556 | + | ||
| 2553 | 2557 | if isfunction(obj) or _signature_is_functionlike(obj): | |
| 2554 | 2558 | # If it's a pure Python function, or an object that is duck type | |
| 2555 | 2559 | # of a Python function (Cython functions, for instance), then: | |
@@ -2561,10 +2565,6 @@ def _signature_from_callable(obj, *, | |||
| 2561 | 2565 | return _signature_from_builtin(sigcls, obj, | |
| 2562 | 2566 | skip_bound_arg=skip_bound_arg) | |
| 2563 | 2567 | ||
| 2564 | - if isinstance(obj, functools.partial): | ||
| 2565 | - wrapped_sig = _get_signature_of(obj.func) | ||
| 2566 | - return _signature_get_partial(wrapped_sig, obj) | ||
| 2567 | - | ||
| 2568 | 2568 | if isinstance(obj, type): | |
| 2569 | 2569 | # obj is a class or a metaclass | |
| 2570 | 2570 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -395,6 +395,23 @@ def __getitem__(self, key): | |||
| 395 | 395 | f = self.partial(object) | |
| 396 | 396 | self.assertRaises(TypeError, f.__setstate__, BadSequence()) | |
| 397 | 397 | ||
| 398 | + def test_partial_as_method(self): | ||
| 399 | + class A: | ||
| 400 | + meth = self.partial(capture, 1, a=2) | ||
| 401 | + cmeth = classmethod(self.partial(capture, 1, a=2)) | ||
| 402 | + smeth = staticmethod(self.partial(capture, 1, a=2)) | ||
| 403 | + | ||
| 404 | + a = A() | ||
| 405 | + self.assertEqual(A.meth(3, b=4), ((1, 3), {'a': 2, 'b': 4})) | ||
| 406 | + self.assertEqual(A.cmeth(3, b=4), ((1, A, 3), {'a': 2, 'b': 4})) | ||
| 407 | + self.assertEqual(A.smeth(3, b=4), ((1, 3), {'a': 2, 'b': 4})) | ||
| 408 | + with self.assertWarns(FutureWarning) as w: | ||
| 409 | + self.assertEqual(a.meth(3, b=4), ((1, 3), {'a': 2, 'b': 4})) | ||
| 410 | + self.assertEqual(w.filename, __file__) | ||
| 411 | + self.assertEqual(a.cmeth(3, b=4), ((1, A, 3), {'a': 2, 'b': 4})) | ||
| 412 | + self.assertEqual(a.smeth(3, b=4), ((1, 3), {'a': 2, 'b': 4})) | ||
| 413 | + | ||
| 414 | + | ||
| 398 | 415 | @unittest.skipUnless(c_functools, 'requires the C _functools module') | |
| 399 | 416 | class TestPartialC(TestPartial, unittest.TestCase): | |
| 400 | 417 | if c_functools: | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -3873,10 +3873,12 @@ class C(metaclass=CM): | |||
| 3873 | 3873 | def __init__(self, b): | |
| 3874 | 3874 | pass | |
| 3875 | 3875 | ||
| 3876 | - self.assertEqual(C(1), (2, 1)) | ||
| 3877 | - self.assertEqual(self.signature(C), | ||
| 3878 | - ((('a', ..., ..., "positional_or_keyword"),), | ||
| 3879 | - ...)) | ||
| 3876 | + with self.assertWarns(FutureWarning): | ||
| 3877 | + self.assertEqual(C(1), (2, 1)) | ||
| 3878 | + with self.assertWarns(FutureWarning): | ||
| 3879 | + self.assertEqual(self.signature(C), | ||
| 3880 | + ((('a', ..., ..., "positional_or_keyword"),), | ||
| 3881 | + ...)) | ||
| 3880 | 3882 | ||
| 3881 | 3883 | with self.subTest('partialmethod'): | |
| 3882 | 3884 | class CM(type): | |
@@ -4024,10 +4026,12 @@ class C: | |||
| 4024 | 4026 | class C: | |
| 4025 | 4027 | __init__ = functools.partial(lambda x, a: None, 2) | |
| 4026 | 4028 | ||
| 4027 | - C(1) # does not raise | ||
| 4028 | - self.assertEqual(self.signature(C), | ||
| 4029 | - ((('a', ..., ..., "positional_or_keyword"),), | ||
| 4030 | - ...)) | ||
| 4029 | + with self.assertWarns(FutureWarning): | ||
| 4030 | + C(1) # does not raise | ||
| 4031 | + with self.assertWarns(FutureWarning): | ||
| 4032 | + self.assertEqual(self.signature(C), | ||
| 4033 | + ((('a', ..., ..., "positional_or_keyword"),), | ||
| 4034 | + ...)) | ||
| 4031 | 4035 | ||
| 4032 | 4036 | with self.subTest('partialmethod'): | |
| 4033 | 4037 | class C: | |
@@ -4282,10 +4286,13 @@ class C: | |||
| 4282 | 4286 | class C: | |
| 4283 | 4287 | __call__ = functools.partial(lambda x, a: (x, a), 2) | |
| 4284 | 4288 | ||
| 4285 | - self.assertEqual(C()(1), (2, 1)) | ||
| 4286 | - self.assertEqual(self.signature(C()), | ||
| 4287 | - ((('a', ..., ..., "positional_or_keyword"),), | ||
| 4288 | - ...)) | ||
| 4289 | + c = C() | ||
| 4290 | + with self.assertWarns(FutureWarning): | ||
| 4291 | + self.assertEqual(c(1), (2, 1)) | ||
| 4292 | + with self.assertWarns(FutureWarning): | ||
| 4293 | + self.assertEqual(self.signature(c), | ||
| 4294 | + ((('a', ..., ..., "positional_or_keyword"),), | ||
| 4295 | + ...)) | ||
| 4289 | 4296 | ||
| 4290 | 4297 | with self.subTest('partialmethod'): | |
| 4291 | 4298 | class C: | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,2 @@ | |||
| 1 | + Add a future warning in :meth:`!functools.partial.__get__`. In future Python | ||
| 2 | + versions :class:`functools.partial` will be a method descriptor. | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -197,6 +197,21 @@ partial_dealloc(partialobject *pto) | |||
| 197 | 197 | Py_DECREF(tp); | |
| 198 | 198 | } | |
| 199 | 199 | ||
| 200 | + static PyObject * | ||
| 201 | + partial_descr_get(PyObject *self, PyObject *obj, PyObject *type) | ||
| 202 | + { | ||
| 203 | + if (obj == Py_None || obj == NULL) { | ||
| 204 | + return Py_NewRef(self); | ||
| 205 | + } | ||
| 206 | + if (PyErr_WarnEx(PyExc_FutureWarning, | ||
| 207 | + "functools.partial will be a method descriptor in " | ||
| 208 | + "future Python versions; wrap it in staticmethod() " | ||
| 209 | + "if you want to preserve the old behavior", 1) < 0) | ||
| 210 | + { | ||
| 211 | + return NULL; | ||
| 212 | + } | ||
| 213 | + return Py_NewRef(self); | ||
| 214 | + } | ||
| 200 | 215 | ||
| 201 | 216 | /* Merging keyword arguments using the vectorcall convention is messy, so | |
| 202 | 217 | * if we would need to do that, we stop using vectorcall and fall back | |
@@ -514,6 +529,7 @@ static PyType_Slot partial_type_slots[] = { | |||
| 514 | 529 | {Py_tp_methods, partial_methods}, | |
| 515 | 530 | {Py_tp_members, partial_memberlist}, | |
| 516 | 531 | {Py_tp_getset, partial_getsetlist}, | |
| 532 | + {Py_tp_descr_get, (descrgetfunc)partial_descr_get}, | ||
| 517 | 533 | {Py_tp_new, partial_new}, | |
| 518 | 534 | {Py_tp_free, PyObject_GC_Del}, | |
| 519 | 535 | {0, 0} | |
| Back | FazBrowse Home | New Git URL |
0 commit comments