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

BaseModel.copy produces copy that modifies the original #1383

Closed
physicsrob opened this issue Apr 10, 2020 · 4 comments · Fixed by #1394
Closed

BaseModel.copy produces copy that modifies the original #1383

physicsrob opened this issue Apr 10, 2020 · 4 comments · Fixed by #1394
Labels
bug V1 Bug related to Pydantic V1.X documentation

Comments

@physicsrob
Copy link

physicsrob commented Apr 10, 2020

Bug

When copying a model, changing the value of an attribute on the copy updates the value of the attribute on the original. This only happens if deep != True. If this is expected behavior, I would suggest the documentation needs to be updated, because it's different than the intuition one would have for shallow copies of standard python objects.

My expectation of a shallow copy: Modifying a mutable attribute on the child SHOULD modify the mutable attribute on the parent (the attribute should be the same object). Setting the value of an attribute on the child, however, SHOULD NOT set the value on the original.

Output of python -c "import pydantic.utils; print(pydantic.utils.version_info())":

             pydantic version: 1.4
            pydantic compiled: True
                 install path: /Users/rob/opt/anaconda3/envs/words_ml/lib/python3.8/site-packages/pydantic
               python version: 3.8.2 (default, Mar 25 2020, 11:22:43)  [Clang 4.0.1 (tags/RELEASE_401/final)]
                     platform: macOS-10.15.4-x86_64-i386-64bit
     optional deps. installed: []

Reproduction:

from pydantic import BaseModel
from typing import Optional, List

class X(BaseModel):
    val: int

x = X(val=1)

y = x.copy()
y.val= 2

print(f"{x.val =}") # x.val =2
print(f"{y.val =}") # y.val =2 -- setting the value on the copy also set the value on the original?


# My expectation would be that the behavior would be similar to standard python objects:
x = {'val': 1}
y = x.copy()
y['val'] = 2
print(f"{x['val'] =}") # x.val =1
print(f"{y['val'] =}") # y.val =2
@physicsrob physicsrob added the bug V1 Bug related to Pydantic V1.X label Apr 10, 2020
@StephenBrown2
Copy link
Contributor

copy() has a deep kwarg that defaults to False, detailed in the docs: https://pydantic-docs.helpmanual.io/usage/exporting_models/#modelcopy
The behavior is similar to standard python objects, where a normal copy merely duplicates the reference (a shallow copy), and a deepcopy duplicates the data with a new reference: https://docs.python.org/3/library/copy.html
Here is the code that pydantic uses: https://github.com/samuelcolvin/pydantic/blob/master/pydantic/main.py#L528-L529

@physicsrob
Copy link
Author

physicsrob commented Apr 13, 2020

I disagree that this is similar to normal python behavior. To be clear, I'm talking specifically about shallow copying behavior. I agree that a standard python shallow copy copies the "reference". The difference is that for a normal object when you update the "reference" of a copy you do not update the "reference" of the original. If you modify what the "reference points" to, then of course that will modify what the reference points to in the original.

This is standard python behavior:

class myobj:
    def __init__(self, val):
       self.val = val
x = myobj(1)
import copy
y = copy.copy(x)
y.val = 2

print(f"{y.val =}") # y.val =2
print(f"{x.val =}") # x.val =1

This is pydantic behavior:

from pydantic import BaseModel

class X(BaseModel):
    val: int

x = X(val=1)
y = x.copy()
y.val = 2

print(f"{y.val =}") # y.val =2
print(f"{x.val =}") # x.val =2

@samuelcolvin
Copy link
Member

I 100% agree this is weird, it's not what I would expect to happen, and it's a bug.

But it seems to have been inadvertently fixed in v1.5 (as yet unreleased).

To resolve this, I'm going to:

@physicsrob
Copy link
Author

Awesome! Thanks :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug V1 Bug related to Pydantic V1.X documentation
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants