Skip to content

copy and deepcopy functionality for metaclasses #10903

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
wants to merge 1 commit into from

Conversation

elibixby
Copy link

@elibixby elibixby commented Dec 4, 2018

This change allows metaclasses to provide the same copying/deepcopying functionality for class objects that class objects can provide for instance objects. As a concrete example of where this is useful, consider the following:

class Meta(type):
  def an_object(cls):
     return cls._a_stateful_member

class Foo(object, metaclass=Meta):
   _a_stateful_member = object()

If I take the above code, and I run:

>>>Foo.an_object is copy.deepcopy(Foo).an_object
True

This is already somewhat surprising. Compare it to the analogous code for an instance:

class Foo(object):
   def __init__(self):
      self.bar = object()

foo = Foo()

Which gives us

>>>foo.bar is copy.deepcopy(foo).bar
False

Even accepting this as the default state of affairs, it is quite upsetting that we cannot make a __deepcopy__ implementation for our metaclass that will override this default behavior in the case that we have a classmember that we need to copy. Imagine e.g:

class Meta(type):
  def an_object(cls):
     return cls._a_stateful_member

  def __deepcopy__(cls, memo):
     return type(cls.__name__,
                        cls.__bases__,
                        copy.deepcopy(dict(cls.__dict__)))

I think one could reasonably expect this implementation to fix our initial problem. However, since class objects are explicitly ignored by the deepcopy and copy implementations, it does not.

Since __reduce_ex__, __reduce__, __deepcopy__ and __copy__ implementations have no effect on copy currently I think it's reasonable to assume there will be no implementations of these methods on existing metaclasses. To preserve the null behavior on class objects without metaclasses that implement these methods I've added an explicit check for class objects if these methods are all missing.

This change allows metaclasses to provide the same copying/deepcopying functionality for class objects that class objects can provide for instance objects. As a concrete example of where this is useful, consider the following:

```
class Meta(type):
  def an_object(cls):
     return cls._a_stateful_member

class Foo(object, metaclass=Meta):
   _a_stateful_member = object()
```
If I take the above code, and I run:
```
>>>Foo.an_object is copy.deepcopy(Foo).an_object
True
```
This is already somewhat surprising. Compare it to the analogous code for an instance:
```
class Foo(object):
   def __init__(self):
      self.bar = object()

foo = Foo()
```
Which gives us
```
>>>foo.bar is copy.deepcopy(foo).bar
False
```
Even accepting this as the default state of affairs, it is quite upsetting that we cannot make a `__deepcopy__` implementation for our metaclass that will override this default behavior in the case that we have a classmember that we need to copy. Imagine e.g:

```
class Meta(type):
  def an_object(cls):
     return cls._a_stateful_member

  def __deepcopy__(cls, memo):
     return type(cls.__name__,
                        cls.__bases__,
                        copy.deepcopy(dict(cls.__dict__)))
```

I think one could reasonably expect this implementation to fix our initial problem. However, since class objects are explicitly ignored by the deepcopy and copy implementations, it does not.

Since `__reduce_ex__`, `__reduce__`, `__deepcopy__` and `__copy__` implementations have no effect on copy currently I think it's reasonable to assume there will be no implementations of these methods on existing metaclasses. To preserve the null behavior on class objects without metaclasses that implement these methods I've added an explicit check for class objects if these methods are all missing.
@the-knights-who-say-ni
Copy link

Hello, and thanks for your contribution!

I'm a bot set up to make sure that the project can legally accept your contribution by verifying you have signed the PSF contributor agreement (CLA).

Our records indicate we have not received your CLA. For legal reasons we need you to sign this before we can look at your contribution. Please follow the steps outlined in the CPython devguide to rectify this issue.

If you have recently signed the CLA, please wait at least one business day
before our records are updated.

You can check yourself to see if the CLA has been received.

Thanks again for your contribution, we look forward to reviewing it!

@JulienPalard
Copy link
Member

@elibixby thanks for trying to enhance Python! Could you please fill an issue on https://bugs.python.org or link to an existing one about this subject, so this can be discussed? b.p.o often gets better attention that PRs for discussions.

Also please look at the CI, you mixed spaces and tabs which is a no-go in Python.

@csabella
Copy link
Contributor

@elibixby, please address the review comments from @JulienPalard. Thank you!

@csabella
Copy link
Contributor

I'm going to close this due to inactivity.

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

Successfully merging this pull request may close these issues.

6 participants