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

Improve object copying #535

Open
hakonanes opened this issue Dec 15, 2024 · 2 comments
Open

Improve object copying #535

hakonanes opened this issue Dec 15, 2024 · 2 comments

Comments

@hakonanes
Copy link
Member

Looking beyond #533, I think it may be better to avoid deepcopy() altogether and rather add a copy() method, like this

def copy(self) -> Self:
    return self.__class__(self)

We can update the creation method to allow another instance as the first parameter, and only copy the things we need.

We can deprecate deepcopy() in favor of copy().

Benefits:

  • More control
  • New copy via Vector3d(Vector3d) or Vector3d.copy()

What do you think, @viljarjf?

Originally posted by @hakonanes in #533 (comment)

@CSSFrancis?

@viljarjf
Copy link
Contributor

Sounds like a good idea to me!

Looks like this is almost entirely supported already, apart from copying the data. Most constructors already correctly handle the first parameter being an instance of itself, but the data is shared rather than copied. This is in line with how regular (not deep) copying is intended to work in python, as per https://docs.python.org/3/library/copy.html, and means your example code already works as it should for most orix objects!

If the constructor instead should deepcopy (which makes more sense, I think), then it is not too much work to add e.g. .copy() to the numpy arrays. We could also get more control over the copy behaviour by overriding the dunder methods for copy and deepcopy, instead of changing any existing code.

@viljarjf
Copy link
Contributor

viljarjf commented Dec 16, 2024

Something like:

class A:
    def __init__(self, arg_1 = None):
        if isinstance(arg_1, A):
            A.__init__(
                self, 
                arg_1.arg_1.copy(), 
            )
            return
        self.arg_1 = arg_1
        ...

feels like a decent design too, and gives control over copying by how we pass the parameters to the recursive constructor call.

A more complete example:

import numpy as np

class Foo:
    def __init__(self, a: np.ndarray = None, b: np.ndarray = None) -> None:
        if isinstance(a, Foo):
            Foo.__init__(self, a.a.copy(), a.b.copy())
            return
        self.a = a
        self.b = b

class Bar(Foo):
    def __init__(self, a: np.ndarray = None, b: np.ndarray = None, c: np.ndarray = None) -> None:
        if isinstance(a, Bar):
            Bar.__init__(self, a.a.copy(), a.b.copy(), a.c.copy())
            return
        super().__init__(a, b)
        self.c = c

# Check that the copying works
test_1 = Foo(np.random.random(3), np.random.random(3))
test_2 = Foo(test_1)
assert test_1 is not test_2
assert test_1.a is not test_2.a
assert np.array_equal(test_1.a, test_2.a)

# Check that copying works for subclasses
test_3 = Bar(np.random.random(3), np.random.random(3), np.random.random(4))
test_4 = Bar(test_3)
assert test_3 is not test_4
assert test_3.c is not test_4.c
assert np.array_equal(test_3.c, test_4.c)

# Check that subclasses can be cast to parent
test_5 = Foo(test_3)
assert test_3 is not test_5
assert test_3.a is not test_5.a
assert np.array_equal(test_3.a, test_5.a)
assert not hasattr(test_5, "c") and not isinstance(test_5, Bar)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants