GrahamDumpleton / wrapt

A Python module for decorators, wrappers and monkey patching.
BSD 2-Clause "Simplified" License
2.06k stars 231 forks source link

Odd behavior when making `__wrapped__` a property #121

Open riga opened 6 years ago

riga commented 6 years ago

Hi Graham,

I just found some odd behavior when making __wrapped__ a property. The error shows up only when using the compiled C module though. When I delete the .so, the behavior is as expected. Consider this small example:

import wrapt

class Wrapper(wrapt.ObjectProxy):

    def __init__(self):
        self._self_data = None

        super(Wrapper, self).__init__([4, 5, 6, 7])

        self.__wrapped__ = [1, 2, 3]

    @property
    def __wrapped__(self):
        return self._self_data

    @__wrapped__.setter
    def __wrapped__(self, data):
        self._self_data = data

    def __repr__(self):
        return repr(self.__wrapped__)

w = Wrapper()
print("repr: {!r}".format(w))
print("len : {}".format(len(w)))

It should print

repr: [1, 2, 3]
len : 3

but with the compiled version, it outputs

repr: [1, 2, 3]
len : 4

Can you reproduce this?

Thanks!

GrahamDumpleton commented 6 years ago

What are you wanting to do? You shouldn't be trying to replace __wrapped__ with a property. If you explain what the original problem is that you are trying to solve then I may be able to explain how to do it.

riga commented 6 years ago

In our high-energy physics experiments, we're dealing with large amounts data and very complex objects that describe them. For one of our interface tools we are playing around a bit with your ObjectProxy and tried to split the object we want to wrap into two objects (_self_data and _self_trace) that is evaluated dynamically. Here is a minimal example (_self_data could be a static, large O(10GB) numpy / tensorflow object, memory map, etc., whereas _self_trace is small and dynamic):

import wrapt

class LargeDataProxy(wrapt.ObjectProxy):

    def __init__(self, data, trace):
        self._self_data = None
        self._self_trace = trace
        super(LargeDataProxy, self).__init__(data)

    @property
    def __wrapped__(self):
        return self._self_data.__getitem__(self._self_trace)

    @__wrapped__.setter
    def __wrapped__(self, data):
        self._self_data = data

    def __repr__(self):
        return repr(self.__wrapped__)

    def set_trace(self, trace):
        self._self_trace = trace

data = LargeDataProxy(list(range(100)), slice(None))

# loop through data in chunks of size 5
for i in range(20):
    data.set_trace(slice(i * 5, (i + 1) * 5))
    print(data)

# -> [0, 1, 2, 3, 4]
# -> [5, 6, 7, 8, 9]
# -> [10, 11, 12, 13, 14]
# -> ...

We haven't decided on the exact implementation yet (I myself find the above one somewhat dangerous), I just observed that difference between wrappers.py and _wrappers.c.

Chaoses-Ib commented 2 years ago

For people with the same problem:
This is the same issue as #115. __warpped__ property is still not implemented in wrapt. PEAK-Legacy/ProxyTypes can be used as an alternative.