Right leg
Right leg

Reputation: 16720

When does copy(foo) call foo.copy?

I'm designing a base class, and I want it to define a base behaviour for copy.copy. This behaviour consists in printing a warning in the console, and then copying the instance as if it had no __copy__ attribute.

When one defines a blank Foo class and copies an instance of it, the copy function returns a new instance of that class, as shown by the following session:

>>> class Foo: pass
...
>>> foo = Foo()
>>> foo2 = copy(foo)
>>> foo is foo2
False

Now if the Foo class defines a __copy__ instance method, the latter will be called when trying to pass an instance to copy:

>>> class Foo:
...     def __copy__(self):
...         print("Copying")
...
>>> foo = Foo()
>>> copy(foo)
Copying

So as I understand it, the flow of execution of the copy function would be:

  1. Try to access the object's __copy__ attribute
  2. If present, call it
  3. If absent, perform a generic copy

But now, I want to capture the copy function's accessing the __copy__ attribute, through defining a __getattr__ method, and then simulate the absence of this attribute, by raising an AttributeError:

>>> class Foo:
...     def __getattr__(self, attr):
...         if attr == '__copy__':
...             print("Accessing '__copy__'")
...             raise AttributeError
...

Then the __copy__ attribute does not seem to be accessed anymore:

>>> foo = Foo()

# Actual behaviour of copy(foo)
>>> copy(foo)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "C:\ProgramData\Miniconda3\lib\copy.py", line 96, in copy
    rv = reductor(4)
TypeError: 'NoneType' object is not callable

# Expected behaviour of copy(foo)
>>> foo.__copy__()
Accessing '__copy__'
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 5, in __getattr__
AttributeError

What am I missing in the execution flow of the copy function, with regards to the __copy__ attribute?

As far as I understand, given a foo object with no attribute bar, it should behave exactly the same, whether it has a __getattr__ method that fails on bar, or it doesn't define anything. Is this statement exact?

Upvotes: 1

Views: 66

Answers (1)

Jean-Fran&#231;ois Fabre
Jean-Fran&#231;ois Fabre

Reputation: 140186

Short answer seems to be "no".

Within the copy.copy method we find this (summarized)

cls = type(x)

...

copier = getattr(cls, "__copy__", None)

We see that the function uses getattr on the class, not the instance.

And there's no way to have __getattr__ called when getattr is called on the class (just because it's an instance method)

Demo (sorry, I would have prefered a working demo)

class Foo:
    def __getattr__(self,attr):
             if attr == '__copy__':
                     return "WORKED"

foo = Foo()
print(getattr(foo, "__copy__",None))
print(getattr(Foo, "__copy__",None))

this returns:

WORKED
None

So it's not possible to fool the original copy.copy module into believing there's a __copy__ attribute without creating a __copy__ method.

Upvotes: 1

Related Questions