GrahamDumpleton / wrapt

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

Accessing a class attribute that is a wrapped function raises an AttributeError #264

Open contrast-jproberts opened 3 months ago

contrast-jproberts commented 3 months ago

This issue is similar to #256 , but throws a different exception that might be easier to resolve.

Reproducer

> python --version
Python 3.9.6
> pip show wrapt
Name: wrapt
Version: 1.16.0
Summary: Module for decorators, wrappers and monkey patching.
Home-page: https://github.com/GrahamDumpleton/wrapt
Author: Graham Dumpleton
Author-email: Graham.Dumpleton@gmail.com
License: BSD
Location: ~/scratch/.venv/lib/python3.9/site-packages
Requires:
import wrapt

@wrapt.patches.patch_function_wrapper("operator", "add")
def passthrough(wrapped, instance, args, kwargs):
    return wrapped(*args, **kwargs)

import operator

class A:
    add = operator.add

print(type(A.add))
print(A.add(1, 2))

Running this program gives

Traceback (most recent call last):
  File "~/scratch/wrapt_bug.py", line 16, in <module>
    print(type(A.add))
AttributeError: 'builtin_function_or_method' object has no attribute '__get__'

If the wrapt patches are removed (commenting out everything before line 9 (import operator)), the program runs as expected

<class 'builtin_function_or_method'>
3

Additional Details

The AttributeError is being thrown on https://github.com/GrahamDumpleton/wrapt/blob/5c0997c0b5be36da5f621c3473c95f9efe14891e/src/wrapt/wrappers.py#L534

Experimenting locally, I found that updating the condition from

if not inspect.isclass(self.__wrapped__):
if not inspect.isclass(self.__wrapped__) and not inspect.isbuiltin(self.__wrapped__):

passes my tests, which include more complex uses than the reproducer I showed. I don't have a very complete understanding of descriptors though, so I may be overlooking edge cases. My change to update the condition based on isbuiltin was inspired by this StackOverflow answer: https://stackoverflow.com/a/52140735 . Hopefully it helps guide you all if I misunderstood the response.

Lastly, I'm new to wrapt and tried to read through all the documentation before opening this issue. If I'm misusing the package, please let me know!