GrahamDumpleton / wrapt

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

Wrapping basic types doesn't work as expected with the standard library #174

Closed rnikiforova closed 3 years ago

rnikiforova commented 3 years ago

I'm experiencing some issues wrapping base types such as str. For example, the str.join() fails with "expected str instance" which doesn't happen when str is inherited directly.

Example

import wrapt

class InheritedStr(str):
    pass

class ProxyString(wrapt.ObjectProxy):
    def __copy__(self):
        pass

    def __deepcopy__(self, memo):
        pass

    def __reduce__(self):
        pass

    def __reduce_ex__(self, protocol):
        pass

inherited1 = InheritedStr("text1")
print(isinstance(inherited1, str))   # True
print(",".join([inherited1]))  # Works as expected

proxy1 = ProxyString("proxy1")
print(isinstance(proxy1, str))  # True
print(", ".join([proxy1]))  # Throws TypeError

Produces:

True
text1
True
Traceback (most recent call last):
  File "...", line 28, in <module>
    print(", ".join([proxy1]))
TypeError: sequence item 0: expected str instance, ProxyString found

Meanwhile, the concatenation works as expected:

print("This is " + proxy1) 
# This is proxy1

Versions:

I've experienced the same issue with the re module ('expected string or bytes-like object"). I've also experienced it when wrapping int. For example, datetime.timedelta fails with "TypeError: unsupported type for timedelta days component" when using a proxy object.

Workaround

Currently, the workaround is to convert it to the appropriate type:

print(",".join(map(str, [proxy1])))
# proxy1

However, it would be great if this wasn't needed as other developers on the project should be able to use the proxy without knowing it's a proxy. We use the proxy to store some information about the underlying data.

GrahamDumpleton commented 3 years ago

The problem with anything implemented as a C function is that for where they expect to deal with builtin type they will often have explicit checks for the exact type (not even isinstance() equivalent) and nothing else will work. There isn't a way around it.

Why are you inheriting from builtin types anyway?

For primitive literal types (eg., string, integers, floats) inheriting them from them isn't necessarily a good idea.

rnikiforova commented 3 years ago

There isn't a way around it.

:-(

Why are you inheriting from builtin types anyway?

I didn't plan to, I included it for comparison purposes. However, I'm now considering it since the type (either string or proxy) is not known in advance and they should be interchangeable.

GrahamDumpleton commented 3 years ago

Going to close out this issue as there isn't really a solution for wrapping basic types in Python. For some things it may be possible, but not for many other things.