-
-
Notifications
You must be signed in to change notification settings - Fork 31.8k
bpo-11572: Make several minor improvements to copy module #207
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
Conversation
Another possible cleanup opportunity (added in https://hg.python.org/cpython/rev/c4715c9f442f) try:
issc = issubclass(cls, type)
except TypeError: # cls is not a class
issc = False
if issc:
# treat it as a regular class:
return _copy_immutable(x) |
Lib/copy.py
Outdated
@@ -217,6 +203,8 @@ def _deepcopy_list(x, memo, deepcopy=deepcopy): | |||
d[list] = _deepcopy_list | |||
|
|||
def _deepcopy_tuple(x, memo, deepcopy=deepcopy): | |||
if not x: |
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.
Empty tuple is a singleton. The first deepcopied empty tuple will be memoized, all other occurrences will be retrieved from the memo dict. I amn't sure that this change adds performance gain rather than regression.
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.
Ok, removed the "optimization" :)
Lib/copy.py
Outdated
@@ -278,8 +266,9 @@ def _reconstruct(x, memo, func, args, | |||
if state is not None: | |||
if deep: | |||
state = deepcopy(state, memo) | |||
if hasattr(y, '__setstate__'): | |||
y.__setstate__(state) | |||
setstate = getattr(y, '__setstate__', None) |
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.
This is a new feature. Setting __setstate__
to None
triggers the default behavior. It should be documented in Misc/NEWS. I don't know whether it is worth to document it in the module documentation and What's New. Python implementation of pickle does the same, but C implementation doesn't. If change C implementation of pickle, this should be documented as new feature.
See also related bpo-26579 about other way to call default __setstate__
implementation.
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.
Added a test for this behavior: test_copy_setstate_none
TODOs:
- Document this behavior in the NEWS entry.
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.
I'm not sure this is a desired behavior. If it is, the same behavior should have both pickle implementations. I suggest to defer this change to a separate issue.
except TypeError: # t is not a class (old Boost; see SF #502085) | ||
issc = False | ||
if issc: | ||
if issubclass(t, type): |
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.
There is similar code in Python implementation of pickle. It should be synchronized.
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.
Did you mean another module or is there another Python implementation of the pickle module? (Or GitHub shows your comment in a wrong place?) I've changed both try...except TypeError
snippets in Lib/copy.py
and Lib/pickle.py
.
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.
Seems I thought that we still in copy.py
.
return copier(x) | ||
|
||
reductor = dispatch_table.get(cls) | ||
if reductor: | ||
if reductor is not None: | ||
rv = reductor(x) | ||
else: | ||
reductor = getattr(x, "__reduce_ex__", None) |
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.
__reduce_ex__
could be absent in old style classes in Python 2. New style classes inherit it from object
. Therefore this can be just x.__reduce_ex__
.
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.
Changing this line to x.__reduce_ex__
makes test_copy_cant
fail:
======================================================================
ERROR: test_copy_cant (test.test_copy.TestCopy)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/home/berker/projects/cpython/master/Lib/test/test_copy.py", line 91, in test_copy_cant
self.assertRaises(copy.Error, copy.copy, x)
File "/home/berker/projects/cpython/master/Lib/unittest/case.py", line 743, in assertRaises
return context.handle('assertRaises', args, kwargs)
File "/home/berker/projects/cpython/master/Lib/unittest/case.py", line 178, in handle
callable_obj(*args, **kwargs)
File "/home/berker/projects/cpython/master/Lib/copy.py", line 91, in copy
reductor = x.__reduce_ex__
File "/home/berker/projects/cpython/master/Lib/test/test_copy.py", line 88, in __getattribute__
raise AttributeError(name)
AttributeError: __reduce_ex__
The test was added since the initial check-in of the module, so perhaps we can this leave as-is and add a note describing the behavior?
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.
Lets defer this change to a separate issue.
It changes the behavior for the case when __reduce__
is defined, but __reduce_ex__
is set to None. If this change is intentional, it is worth to the behavior of pickle (both implementations) the same change. Currently the Python implementation will call __reduce__
in this case, but the C implementation will raise a TypeError. If both __reduce__
and __reduce_ex__
are set to None, the current Python implementation will raise a PicklingError. It would be better if all implementation will have the same behavior in corner cases like this.
Lib/copy.py
Outdated
rv = reductor(x) | ||
else: | ||
reductor = getattr(x, "__reduce_ex__", None) | ||
if reductor: | ||
if reductor is not None: | ||
# note that object.__reduce_ex__ calls __reduce__ |
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.
There is small behavior change. Currently if set __reduce_ex__
to None
, copy()
falls back to __reduce__
. Patched code raises Error
.
This change looks not bad to me.
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.
Added a test for this behavior.
@@ -84,22 +84,19 @@ def copy(x): | |||
return _copy_immutable(x) | |||
|
|||
copier = getattr(cls, "__copy__", None) | |||
if copier: | |||
if copier is not None: |
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.
Do you know that if copier is not None
is slower than if copier
?
But the change LGTM.
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.
I didn't know that. How much slower than if foo
?
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.
Not too much. But I think that having only None special is better that depending on the boolean value of the method. LGTM.
To try and help move older pull requests forward, we are going through and backfilling 'awaiting' labels on pull requests that are lacking the label. Based on the current reviews, the best we can tell in an automated fashion is that a core developer requested changes to be made to this pull request. If/when the requested changes have been made, please leave a comment that says, |
569fdda
to
90d74b3
Compare
Thanks for the review, Serhiy. I've addressed all of your review comments except the ones about Also, I still need to a NEWS entry. |
@serhiy-storchaka can you take a look at the latest version of this PR? |
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 PR contains not just cleanup, but leads to some user-visible behavior changes. They are not consistent with pickle implementations, and it is not obvious that they are purposed. I suggest to revert these changes, and open a separate issue for discussing about the meaning of setting None to special methods like __reduce_ex__
, __getstate__
and __setstate__
.
@@ -84,22 +84,19 @@ def copy(x): | |||
return _copy_immutable(x) | |||
|
|||
copier = getattr(cls, "__copy__", None) | |||
if copier: | |||
if copier is not None: |
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.
Not too much. But I think that having only None special is better that depending on the boolean value of the method. LGTM.
except TypeError: # t is not a class (old Boost; see SF #502085) | ||
issc = False | ||
if issc: | ||
if issubclass(t, type): |
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.
Seems I thought that we still in copy.py
.
return copier(x) | ||
|
||
reductor = dispatch_table.get(cls) | ||
if reductor: | ||
if reductor is not None: | ||
rv = reductor(x) | ||
else: | ||
reductor = getattr(x, "__reduce_ex__", None) |
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.
Lets defer this change to a separate issue.
It changes the behavior for the case when __reduce__
is defined, but __reduce_ex__
is set to None. If this change is intentional, it is worth to the behavior of pickle (both implementations) the same change. Currently the Python implementation will call __reduce__
in this case, but the C implementation will raise a TypeError. If both __reduce__
and __reduce_ex__
are set to None, the current Python implementation will raise a PicklingError. It would be better if all implementation will have the same behavior in corner cases like this.
Lib/copy.py
Outdated
@@ -278,8 +266,9 @@ def _reconstruct(x, memo, func, args, | |||
if state is not None: | |||
if deep: | |||
state = deepcopy(state, memo) | |||
if hasattr(y, '__setstate__'): | |||
y.__setstate__(state) | |||
setstate = getattr(y, '__setstate__', None) |
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.
I'm not sure this is a desired behavior. If it is, the same behavior should have both pickle implementations. I suggest to defer this change to a separate issue.
Lib/copy.py
Outdated
else: | ||
raise Error( | ||
"un(deep)copyable object of type %s" % cls) | ||
raise Error( "un(deep)copyable object of type %s" % cls) |
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 same as for copy(). I suggest to defer reduction of the __reduce__
branch to a separate issue.
90d74b3
to
3b49327
Compare
@serhiy-storchaka thanks for the review! Reverted them in 3b49327. Do you think we need a NEWS entry after 3b49327? |
Thank you @berkerpeksag. I have no opinion about the NEWS entry. |
Ok, I'm not going to add a NEWS entry since it's just a cleanup PR. |
* When doing getattr lookups with a default of "None", it now uses an "is" comparison against None which is more correct * Removed outdated code Patch by Brandon Rhodes.
3b49327
to
0a88fde
Compare
Addressed Serhiy's review comments.
@serhiy-storchaka do you know what the problem with the build? I can't reproduce it locally.
It looks like something is wrong with the patchcheck script. |
cframeobject.h and channelobject.h are now empty and obsolete. They will be removed by the next commit.
Move the headers to Include and Include/internal, fix the #include directives and update Makefile.in.pre and PCbuild-files.
…ols/msi Remove the special handling of Stackless include files from the msi-installer.
Remove the Stackless source dir from the include-path.
Remove the Stackless specific include-path.
…c headers Move the definition of the macro SLP_SEH32 to stackless_struct.h
Names of "public" header files use prefix "stackless", all other use "slp". This is consistent with symbol names.
now uses an "is" comparison against None which is both
faster and more correct
called in Python 3
Patch by Brandon Rhodes.
https://bugs.python.org/issue11572