-
-
Notifications
You must be signed in to change notification settings - Fork 1.8k
Add copy.replace
for 3.13
#12262
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add copy.replace
for 3.13
#12262
Changes from all commits
892c7b8
b13a641
c0bc3f9
f0a22fa
ef0758e
b6ed3d5
731e8b2
6cf408c
5a82666
58528bb
19a1f9f
294c7c0
94901c0
d4b52ee
bdf3f1d
dc40a89
59eb56c
a19f672
c2fd7ec
5348837
23f4d66
b404c60
c3fd56a
9c92081
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
from __future__ import annotations | ||
|
||
import copy | ||
import sys | ||
from typing_extensions import Self, assert_type | ||
|
||
|
||
class ReplaceableClass: | ||
def __init__(self, val: int) -> None: | ||
self.val = val | ||
|
||
def __replace__(self, val: int) -> Self: | ||
cpy = copy.copy(self) | ||
cpy.val = val | ||
return cpy | ||
|
||
|
||
if sys.version_info >= (3, 13): | ||
obj = ReplaceableClass(42) | ||
cpy = copy.replace(obj, val=23) | ||
assert_type(cpy, ReplaceableClass) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,8 +1,16 @@ | ||
from typing import Any, TypeVar | ||
import sys | ||
from typing import Any, Protocol, TypeVar | ||
from typing_extensions import ParamSpec, Self | ||
|
||
__all__ = ["Error", "copy", "deepcopy"] | ||
|
||
_T = TypeVar("_T") | ||
_SR = TypeVar("_SR", bound=_SupportsReplace[Any]) | ||
_P = ParamSpec("_P") | ||
|
||
class _SupportsReplace(Protocol[_P]): | ||
# In reality doesn't support args, but there's no other great way to express this. | ||
def __replace__(self, *args: _P.args, **kwargs: _P.kwargs) -> Self: ... | ||
|
||
# None in CPython but non-None in Jython | ||
PyStringMap: Any | ||
|
@@ -11,6 +19,10 @@ PyStringMap: Any | |
def deepcopy(x: _T, memo: dict[int, Any] | None = None, _nil: Any = []) -> _T: ... | ||
def copy(x: _T) -> _T: ... | ||
|
||
if sys.version_info >= (3, 13): | ||
__all__ += ["replace"] | ||
def replace(obj: _SR, /, **changes: Any) -> _SR: ... | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. See us mostly using There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, this is fine, it's so we don't have to repeat the whole list for every version. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh, I meant versus using There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Except it actually isn't in This may be an oversight, so consider opening an issue over on CPython for adding replace to There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. My bad, let me fix that. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
|
||
class Error(Exception): ... | ||
|
||
error = Error |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Technically speaking, it does support positionals, but those will be passed as keywords. What it does not support however is positional-only arguments.
On the other hand, you can just force people to use keyword-only arguments so that it matches https://docs.python.org/3.13/library/copy.html#object.__replace__. Or at least, make
self
a positional-only argument.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The only issue is that won't work that way you might think with either MyPy or Pyright. Take this example: https://pyright-play.net/?pythonVersion=3.13&strict=true&code=GYJw9gtgBALgngBwJYDsDmUkQWEMoAK4MYAxmADYA0UAginAFDOkUCGAzh1APoDKAVwQ48HAEoBTBO1ISAFETAlyFAJQAuRlG1QAJhOC8eIKTIk8ecjhIrAaAKnsBrAO5sQaDuroNVUALQAfFAAcmAoEt4AdDHMjKyc3ACS2BSaOnoGRibSbLIWVjZ2UO5oPACM3qgwfkGh4ZFQMVHM%2BoYwEhwwcjkybABGFI38QiIw4qZ5ErXBYRHRscxY0lAAvFAp0nKqjB1dcstqQA
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, callables with
*args: Any, **kwargs: Any
are treated specially in the spec.