Skip to content

bpo-38293: Allow shallow and deep copying of property objects#16438

Merged
miss-islington merged 2 commits into
python:masterfrom
GudniNathan:master
Jan 12, 2020
Merged

bpo-38293: Allow shallow and deep copying of property objects#16438
miss-islington merged 2 commits into
python:masterfrom
GudniNathan:master

Conversation

@GudniNathan

@GudniNathan GudniNathan commented Sep 27, 2019

Copy link
Copy Markdown
Contributor

Copying property objects results in a TypeError. Steps to reproduce:

>>> import copy
>>> obj = property()
>>> copy.copy(obj)

This affects both shallow and deep copying.
My idea for a fix is to add property objects to the list of "atomic" objects in the copy module.
These already include types like functions and type objects.

I also added property objects to the unit tests test_copy_atomic and test_deepcopy_atomic. This is my first PR, and it's highly likely I've made some mistake, so please be kind :)

https://bugs.python.org/issue38293

Automerge-Triggered-By: @csabella

@eamanu eamanu left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch @GudniNatan i am not copy expert but I test it and run successfully.

I don't know if will need necessary a NEWS entry, IMO yes, but I would like hear some
core dev opinion.

@brandtbucher brandtbucher added the type-feature A feature request or enhancement label Sep 29, 2019

@brandtbucher brandtbucher left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @GudniNatan, and welcome to CPython. This looks good!

I think this should have a NEWS entry. It's a new feature (and there's precedent for it when copy/deepcopy support was added for other objects in the past). Creating one is super quick!

Maybe just something like:

Add :func:`copy.copy` and :func:`copy.deepcopy` support to :func:`property` objects.

Just out of curiosity, what was your use case for wanting to copy these?

@GudniNathan

Copy link
Copy Markdown
Contributor Author

I was experimenting with using property objects to validate data in dataclasses. Here is some code that should help illustrate:

from dataclasses import field, dataclass, asdict, fields


def string_check(x):
    if type(x) != str:
        raise ValueError("Should be a string.")
    return x


def validator_factory(field_name, validator):
    def fget(self):
        return self.__dict__[field_name]

    def fset(self, value):
        self.__dict__[field_name] = validator(value)

    return property(fget, fset)


@dataclass
class MyClass():
    my_value: str = validator_factory("my_value", string_check)


@dataclass
class ClassSchema():
    field_defaults: list


my_class_schema = ClassSchema(
    field_defaults=[f.default for f in fields(MyClass)]
)

print(my_class_schema)
print(asdict(my_class_schema))

The MyClass dataclass has an attribute my_value, and the idea is that if you set it to be anything other than a string, it will throw a ValueError.

We were also using a library (dataclasses-jsonschema) that has some similar functionality to ClassSchema, in that it stores the default values of our dataclasses. It will then try to convert those to dictionaries with the dataclasses asdict function. However, asdict will try to give you a dictionary that is independent of the instance you feed it. To do that it uses the deepcopy, and then you get the TypeError: can't pickle property objects

So basically calling asdict() on certain dataclass instances results in this weird error.

@gvanrossum gvanrossum left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have a nagging doubt. You can create a property from any callable, e.g. property(lambda self: None). The argument is not type-checked, so you can also create a property from a non-callable, e.g. property([1, 2, 3]). When we deep-copy such a property, shouldn't we deep-copy the list (i.e. the fget attribute)? After all, when we deep-copy a tuple containing a list, the list gets copied.

@GudniNatan For your use case, is deep-copyability required?

@arigo

arigo commented Oct 23, 2019

Copy link
Copy Markdown
Contributor

@gvanrossum following that argument, copy.copy(func) should not return its original object either, because func is mutable (i.e. a following func.foo = 42 should not appear on its copy). Of course I'm not suggesting that this should be changed; this is only a reminder that copy() and deepcopy() are best-effort hacks in the first place.

@gvanrossum

Copy link
Copy Markdown
Member

@arigo That's fair. Tuples are special, because they are meant to be robust data structures, and a tuple containing a list is a well-known (and used) corner case. I'll approve.

@miss-islington

Copy link
Copy Markdown
Contributor

Thanks @GudniNatan for the PR 🌮🎉.. I'm working now to backport this PR to: 3.7, 3.8.
🐍🍒⛏🤖

@miss-islington

Copy link
Copy Markdown
Contributor

Sorry @GudniNatan, I had trouble checking out the 3.8 backport branch.
Please backport using cherry_picker on command line.
cherry_picker 9f3fc6c5b4993f2b362263b494f84793a21aa073 3.8

@miss-islington miss-islington self-assigned this Jan 12, 2020
miss-islington pushed a commit to miss-islington/cpython that referenced this pull request Jan 12, 2020
…GH-16438)

Copying property objects results in a TypeError. Steps to reproduce:

```
>>> import copy
>>> obj = property()
>>> copy.copy(obj)
````

This affects both shallow and deep copying.
My idea for a fix is to add property objects to the list of "atomic" objects in the copy module.
These already include types like functions and type objects.

I also added property objects to the unit tests test_copy_atomic and test_deepcopy_atomic. This is my first PR, and it's highly likely I've made some mistake, so please be kind :)

https://bugs.python.org/issue38293
(cherry picked from commit 9f3fc6c)

Co-authored-by: Guðni Natan Gunnarsson <1493259+GudniNatan@users.noreply.github.com>
@bedevere-bot

Copy link
Copy Markdown

GH-17968 is a backport of this pull request to the 3.7 branch.

@miss-islington

Copy link
Copy Markdown
Contributor

Thanks @GudniNatan for the PR 🌮🎉.. I'm working now to backport this PR to: 3.8.
🐍🍒⛏🤖 I'm not a witch! I'm not a witch!

@bedevere-bot

Copy link
Copy Markdown

GH-17969 is a backport of this pull request to the 3.8 branch.

miss-islington pushed a commit to miss-islington/cpython that referenced this pull request Jan 12, 2020
…GH-16438)

Copying property objects results in a TypeError. Steps to reproduce:

```
>>> import copy
>>> obj = property()
>>> copy.copy(obj)
````

This affects both shallow and deep copying.
My idea for a fix is to add property objects to the list of "atomic" objects in the copy module.
These already include types like functions and type objects.

I also added property objects to the unit tests test_copy_atomic and test_deepcopy_atomic. This is my first PR, and it's highly likely I've made some mistake, so please be kind :)

https://bugs.python.org/issue38293
(cherry picked from commit 9f3fc6c)

Co-authored-by: Guðni Natan Gunnarsson <1493259+GudniNatan@users.noreply.github.com>
miss-islington added a commit that referenced this pull request Jan 12, 2020
Copying property objects results in a TypeError. Steps to reproduce:

```
>>> import copy
>>> obj = property()
>>> copy.copy(obj)
````

This affects both shallow and deep copying.
My idea for a fix is to add property objects to the list of "atomic" objects in the copy module.
These already include types like functions and type objects.

I also added property objects to the unit tests test_copy_atomic and test_deepcopy_atomic. This is my first PR, and it's highly likely I've made some mistake, so please be kind :)

https://bugs.python.org/issue38293
(cherry picked from commit 9f3fc6c)

Co-authored-by: Guðni Natan Gunnarsson <1493259+GudniNatan@users.noreply.github.com>
sthagen added a commit to sthagen/python-cpython that referenced this pull request Jan 12, 2020
bpo-38293: Allow shallow and deep copying of property objects (pythonGH-16438)
miss-islington added a commit that referenced this pull request Jan 12, 2020
Copying property objects results in a TypeError. Steps to reproduce:

```
>>> import copy
>>> obj = property()
>>> copy.copy(obj)
````

This affects both shallow and deep copying.
My idea for a fix is to add property objects to the list of "atomic" objects in the copy module.
These already include types like functions and type objects.

I also added property objects to the unit tests test_copy_atomic and test_deepcopy_atomic. This is my first PR, and it's highly likely I've made some mistake, so please be kind :)

https://bugs.python.org/issue38293
(cherry picked from commit 9f3fc6c)

Co-authored-by: Guðni Natan Gunnarsson <1493259+GudniNatan@users.noreply.github.com>
@brandtbucher

Copy link
Copy Markdown
Member

Congrats on your first CPython contribution @GudniNatan! 🍾

Looking forward to seeing more from you in the future.

shihai1991 pushed a commit to shihai1991/cpython that referenced this pull request Jan 31, 2020
…GH-16438)

Copying property objects results in a TypeError. Steps to reproduce:

```
>>> import copy
>>> obj = property()
>>> copy.copy(obj)
````

This affects both shallow and deep copying.  
My idea for a fix is to add property objects to the list of "atomic" objects in the copy module.
These already include types like functions and type objects.

I also added property objects to the unit tests test_copy_atomic and test_deepcopy_atomic. This is my first PR, and it's highly likely I've made some mistake, so please be kind :)


https://bugs.python.org/issue38293
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

type-feature A feature request or enhancement

Projects

None yet

Development

Successfully merging this pull request may close these issues.

9 participants