Skip to content
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

Meta-ticket: Support Python's __copy__ / __deepcopy__ protocol #13811

Open
cnassau opened this issue Dec 8, 2012 · 68 comments
Open

Meta-ticket: Support Python's __copy__ / __deepcopy__ protocol #13811

cnassau opened this issue Dec 8, 2012 · 68 comments

Comments

@cnassau
Copy link

cnassau commented Dec 8, 2012

Most Sage objects are immutable. Nevertheless, copy and deepcopy make copies (through pickling/unpickling) for them because we have not provided the classes with __copy__ methods (which should just return the object) and __deepcopy__ methods (which in many cases should just return the object).

sage: a = 0
sage: copy(a) is a
False

Another symptom (from the original ticket description): Copying of (immutable!) LazyFamily objects only works for families that can be pickled:

   sage: def unpicklableFamily():
   ...       x = 10
   ...       return LazyFamily([1,2,3], lambda n: x*n)
   sage: f = unpicklableFamily() 
   sage: copy(f)
   Traceback (most recent call last):
   ...
   ValueError: Cannot pickle code objects from closures

The case of __deepcopy__ of immutable container-like objects (like Family or Sequence) is a bit trickier: Is immutability intended to imply that the elements are immutable too? In this ticket, we make no such assumption.

Tickets:

Depends on #32454

CC: @tscrim @mjungmath @nbruin @kwankyu @williamstein @mwageringel @kcrisman

Component: pickling

Keywords: LazyFamily, copy, pickle

Author: Matthias Koeppe

Branch/Commit: u/mkoeppe/__copy___and___deepcopy___methods_for_all_immutable_sage_objects @ 2bd01ae

Issue created by migration from https://trac.sagemath.org/ticket/13811

@cnassau
Copy link
Author

cnassau commented Dec 8, 2012

Author: cnassau

@cnassau
Copy link
Author

cnassau commented Dec 8, 2012

Changed author from cnassau to Christian Nassau

@nthiery
Copy link
Contributor

nthiery commented Dec 13, 2012

comment:4

Hi Christian!

Families are (semantically) immutable objects. Why would we want to copy them?

Cheers,
Nicolas

@cnassau
Copy link
Author

cnassau commented Dec 13, 2012

comment:5

Replying to @nthiery:

Hi Christian!

Families are (semantically) immutable objects. Why would we want to copy them?

Good point... I stumbled across this issue while I was struggling to create a disjoint union of a dynamically generated family of LazyFamily objects, while the real problem was that I was using closures with reference to a loop variable. I eventually resolved this issue, and my solution does not requiry any copies now. So I agree that there's no need to create shallow copies.

I do think that user code can expect that copy(any_sage_object) does not throw an error. Maybe LazyFamily.__copy__(self) should just return self? This would be in accordance with

sage: copy(Integers()) is Integers()
True

Cheers,
Christian

@nthiery
Copy link
Contributor

nthiery commented Dec 13, 2012

comment:6

Replying to @cnassau:

I do think that user code can expect that copy(any_sage_object) does not throw an error. Maybe LazyFamily.__copy__(self) should just return self? This would be in accordance with

sage: copy(Integers()) is Integers()
True

Yes, it would make sense to have copy(x) return x for all immutable objects in Sage. I am not sure how to achieve this though: we do not (yet?) have a class for providing code for all immutable objects in Sage, and I would not want to force every relevant class to reimplement a trivial __copy__ method.

For the case at hand (families), maybe one could add a method Parent.copy returning self? As far as I can tell, parents are required to be immutable anyway. But one should double check this.

Cheers,
Nicolas

@cnassau
Copy link
Author

cnassau commented Jan 26, 2013

A patch for this problem

@jdemeyer
Copy link

comment:7

Attachment: lazyfamcopy.patch.gz

@jdemeyer jdemeyer modified the milestones: sage-5.11, sage-5.12 Aug 13, 2013
@sagetrac-vbraun-spam sagetrac-vbraun-spam mannequin modified the milestones: sage-6.1, sage-6.2 Jan 30, 2014
@sagetrac-vbraun-spam sagetrac-vbraun-spam mannequin modified the milestones: sage-6.2, sage-6.3 May 6, 2014
@sagetrac-vbraun-spam sagetrac-vbraun-spam mannequin modified the milestones: sage-6.3, sage-6.4 Aug 10, 2014
@videlec
Copy link
Contributor

videlec commented Feb 1, 2015

comment:11

Hi,

As Nicolas suggested in his comnent 6, __copy__ should be implemented as

def __copy__(self):
    return self

It is standard Python for immutable objects

sage: t = (1,2,3)
sage: copy(t) is t
True
sage: i = 1231491283r
sage: copy(i) is i
True 

Vincent

@mkoeppe
Copy link
Contributor

mkoeppe commented Sep 1, 2021

Changed author from Christian Nassau to none

@mkoeppe

This comment has been minimized.

@mkoeppe mkoeppe changed the title LazyFamily cannot be copied if it can't be pickled __copy__ and __deepcopy__ methods for all immutable Sage objects Sep 1, 2021
@mkoeppe mkoeppe modified the milestones: sage-6.4, sage-9.5 Sep 1, 2021
@mkoeppe

This comment has been minimized.

@mkoeppe
Copy link
Contributor

mkoeppe commented Sep 2, 2021

comment:48

For the pattern in comment:45, with #29101 ("Refined protocol for _element_constructor_ ") we would generally be able to replace copy(ray) by ray.parent()(ray, mutable=True, copy=True); but perhaps we should define a shorthand for that.

Decision time...

@mkoeppe
Copy link
Contributor

mkoeppe commented Sep 2, 2021

comment:49

Replying to @mkoeppe:

There are some doctest failures now from bad code like this in src/sage/geometry/cone.py:

                    # rays has immutable elements
                    rays = [copy(ray) for ray in rays]

                    for i, ray in enumerate(rays):
                        rays[i][0] = pm * (ray[0].abs() + 1)

This pattern -- expecting that ray.__copy__ (and thus copy(ray)) make a mutable copy of an immutable object -- appears to come from a decision made many many years ago in Sage (#111, #6522, #15132, ...). This was probably a good idea at the time, but I think it is not compatible with current Python practices.

@mkoeppe
Copy link
Contributor

mkoeppe commented Sep 2, 2021

Dependencies: #32454

@kcrisman
Copy link
Member

kcrisman commented Sep 2, 2021

comment:52

This pattern -- expecting that ray.__copy__ (and thus copy(ray)) make a mutable copy of an immutable object -- appears to come from a decision made many many years ago in Sage (#111, #6522, #15132, ...). This was probably a good idea at the time, but I think it is not compatible with current Python practices.

That may indeed be the case. As you say, at the time the goal was standardized Sage command structure, but if there is a more (modern) Pythonic way to handle this, #6522 alone shows how much gnashing of teeth surrounded some of these changes at the time, so more power to you as long as any syntax changes would be documented.

One should also be sure that things which would "naturally" be seen as copyable are, but other than Sequence which I am not as familiar with, it seems like e.g. vectors can still be copied unless asked not to be, so seems fine to me.

@mkoeppe
Copy link
Contributor

mkoeppe commented Sep 3, 2021

comment:53

Regarding the pair of methods v.mutable(), v.immutable() proposed in #32353: v.immutable() is clear, of course, and shorter than v.copy(mutable=False). But I think it is unclear what v.mutable() is supposed to be if v is already mutable. Does it return a copy? Then the method's name should say so that it is obvious to users / readers of the code that mutating the result is not only allowed but also safe.
So a pair v.mutable_copy(), v.immutable_copy() would be fine, although I'm not sure if it's much better than v.copy(mutable=True), v.copy(mutable=False)

@mkoeppe mkoeppe changed the title __copy__ and __deepcopy__ methods for all immutable Sage objects Meta-ticket: __copy__ and __deepcopy__ methods for all immutable Sage objects Sep 3, 2021
@mkoeppe

This comment has been minimized.

@mkoeppe

This comment has been minimized.

@mkoeppe
Copy link
Contributor

mkoeppe commented Sep 4, 2021

comment:57

Some more data points on other libraries:

  • numpy's ndarrays (and subclasses) have a copy method with an optional parameter that controls the memory layout of the copy; the __copy__ method is equivalent to one of these choices.
  • sympy defines copy methods but no deepcopy, __copy__, or __deepcopy__ methods

@mkoeppe

This comment has been minimized.

@mkoeppe

This comment has been minimized.

@mkoeppe

This comment has been minimized.

@mkoeppe

This comment has been minimized.

@mkoeppe mkoeppe changed the title Meta-ticket: __copy__ and __deepcopy__ methods for all immutable Sage objects Meta-ticket: Support Python's __copy__ / __deepcopy__ protocol Sep 6, 2021
@mkoeppe mkoeppe modified the milestones: sage-9.5, sage-9.6 Dec 14, 2021
@mkoeppe mkoeppe modified the milestones: sage-9.6, sage-9.7 Mar 5, 2022
@mkoeppe mkoeppe modified the milestones: sage-9.7, sage-9.8 Aug 31, 2022
@mkoeppe mkoeppe removed this from the sage-9.8 milestone Jan 29, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

9 participants