Skip to content

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

Closed

Conversation

berkerpeksag
Copy link
Member

@berkerpeksag berkerpeksag commented Feb 21, 2017

  • When doing getattr lookups with a default of "None", it
    now uses an "is" comparison against None which is both
    faster and more correct
  • Used getattr() instead of hasattr()
  • Removed obsolete branches where it's impossible to be
    called in Python 3

Patch by Brandon Rhodes.

https://bugs.python.org/issue11572

@berkerpeksag berkerpeksag added the type-feature A feature request or enhancement label Feb 21, 2017
@berkerpeksag
Copy link
Member Author

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:
Copy link
Member

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.

Copy link
Member Author

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)
Copy link
Member

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.

Copy link
Member Author

@berkerpeksag berkerpeksag Jun 14, 2018

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.

Copy link
Member

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):
Copy link
Member

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.

Copy link
Member Author

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.

Copy link
Member

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)
Copy link
Member

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__.

Copy link
Member Author

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?

Copy link
Member

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__
Copy link
Member

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.

Copy link
Member Author

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:
Copy link
Member

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.

Copy link
Member Author

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?

Copy link
Member

@serhiy-storchaka serhiy-storchaka Jul 9, 2018

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.

@brettcannon
Copy link
Member

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, I have made the requested changes; please review again. That will trigger a bot to flag this pull request as ready for a follow-up review.

@berkerpeksag
Copy link
Member Author

Thanks for the review, Serhiy. I've addressed all of your review comments except the ones about x.__reduce_ex__.

Also, I still need to a NEWS entry.

@berkerpeksag
Copy link
Member Author

@serhiy-storchaka can you take a look at the latest version of this PR?

Copy link
Member

@serhiy-storchaka serhiy-storchaka left a 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:
Copy link
Member

@serhiy-storchaka serhiy-storchaka Jul 9, 2018

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):
Copy link
Member

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)
Copy link
Member

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)
Copy link
Member

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)
Copy link
Member

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.

@berkerpeksag berkerpeksag force-pushed the 11572-copy-improvements branch from 90d74b3 to 3b49327 Compare July 9, 2018 17:59
@berkerpeksag
Copy link
Member Author

@serhiy-storchaka thanks for the review! Reverted them in 3b49327. Do you think we need a NEWS entry after 3b49327?

@serhiy-storchaka
Copy link
Member

Thank you @berkerpeksag. I have no opinion about the NEWS entry.

@berkerpeksag
Copy link
Member Author

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.
@berkerpeksag berkerpeksag force-pushed the 11572-copy-improvements branch from 3b49327 to 0a88fde Compare July 9, 2018 19:31
@berkerpeksag berkerpeksag dismissed serhiy-storchaka’s stale review July 9, 2018 19:33

Addressed Serhiy's review comments.

@berkerpeksag
Copy link
Member Author

@serhiy-storchaka do you know what the problem with the build? I can't reproduce it locally.

Fixing C file whitespace ... 1 file:
  Modules/unicodedata_db.h
Fixing docs whitespace ... 0 files
Please fix the 1 file(s) with whitespace issues

It looks like something is wrong with the patchcheck script.

@berkerpeksag berkerpeksag deleted the 11572-copy-improvements branch July 9, 2018 20:14
akruis pushed a commit to akruis/cpython that referenced this pull request Jan 16, 2019
cframeobject.h and channelobject.h are now empty and obsolete.
They will be removed by the next commit.
akruis pushed a commit to akruis/cpython that referenced this pull request Jan 16, 2019
Move the headers to Include and Include/internal, fix the #include
directives and update Makefile.in.pre and PCbuild-files.
akruis pushed a commit to akruis/cpython that referenced this pull request Jan 16, 2019
…ols/msi

Remove the special handling of Stackless include files from the
msi-installer.
akruis pushed a commit to akruis/cpython that referenced this pull request Jan 16, 2019
Remove the Stackless source dir from the include-path.
akruis pushed a commit to akruis/cpython that referenced this pull request Jan 16, 2019
Remove the Stackless specific include-path.
akruis pushed a commit to akruis/cpython that referenced this pull request Jan 18, 2019
akruis pushed a commit to akruis/cpython that referenced this pull request Jan 18, 2019
…c headers

Move the definition of the macro SLP_SEH32 to stackless_struct.h
akruis pushed a commit to akruis/cpython that referenced this pull request Jan 18, 2019
Names of "public" header files use prefix "stackless", all other
use "slp". This is consistent with symbol names.
akruis pushed a commit to akruis/cpython that referenced this pull request Jan 18, 2019
jaraco pushed a commit that referenced this pull request Dec 2, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
awaiting changes skip news type-feature A feature request or enhancement
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants