Have run into a problem on a "mature" project I work on (there are many years of history before I joined), there are a combination of factors that combine to trigger a KeyError when using copy.copy().

I don't want to write a massive essay here but hoping to give enough to set the context.

There's a class that's a kind of proxy, so there's some "magic" that could be present. The magic is detected by looking for a kind of memo annotation, so the __getattr__ starts with this:

    # Methods that make this class act like a proxy.
    def __getattr__(self, name):
        attr = getattr(self.__dict__['__subject'], name)

and that's what blows up. It happens for a user doing something we... ahem... don't expect. They just picked up the Py3-only version of the project and now they're getting the issue.

Nothing in the project defined a __reduce__ex__ function, but one is picked up from the base "object" type, so copy.copy generates some pickle information and passes it to copy._reconstruct as the state parameter. This stanza:

    if state is not None:
        ...
        if hasattr(y, '__setstate__'):
            y.__setstate__(state)

so our class's __getattr__ is called to look for __setstate__. But at this stage, the copy's instance has only been created, the operations that will fill in the details haven't happened yet, so we take a KeyError.

So apparently the attempt in the __getattr__ to go fishing in our own dict for something we set ourselves is unsafe. Is there a guideline for what you can / cannot expect to be safe to do? My naiive expectations would be that when __getattr__ is called, you can expect an instance to have been already initialized, but if I'm not reading the copy module wrong, that's not always true.

Is a better answer for this class to provide a __copy__ method to more precisely control how copying happens?

--
https://mail.python.org/mailman/listinfo/python-list

Reply via email to