Known Issues

The following known issues exist.

@classmethod.__get__()

Prior to Python 3.9 the @classmethod decorator assumes in the implementation of its __get__() method that the wrapped function is always a normal function. It doesn’t entertain the idea that the wrapped function could actually be a descriptor, the result of a nested decorator. This is an issue because it means that the complete descriptor binding protocol is not performed on anything which is wrapped by the @classmethod decorator.

The consequence of this is that when @classmethod is used to wrap a decorator implemented using @wrapt.decorator, that __get__() isn’t called on the latter. The result is that it is not possible in the latter to properly identify the decorator as being bound to a class method and it will instead be identified as being associated with a normal function, with the class type being passed as the first argument.

The behaviour of the Python @classmethod was reported in the issue (http://bugs.python.org/issue19072). Prior to Python 3.9, which is where the Python interpreter was fixed, the only solution is the recommendation that decorators implemented using @wrapt.decorator always be placed outside of @classmethod and never inside.

Unfortunately, in Python 3.13 this change in Python was reverted back to the old behaviour because various third party code relied on the broken behaviour and even though technically not correct, it was deemed safer to revert the fix. The original warning thus applies.

Using decorated class with super()

In the implementation of a decorated class, if needing to use a reference to the class type with super, it is necessary to access the original wrapped class and use it instead of the decorated class.

@mydecorator
class Derived(Base):

    def __init__(self):
        super(Derived.__wrapped__, self).__init__()

If using Python 3, one can simply use super() with no arguments and everything will work fine.

@mydecorator
class Derived(Base):

    def __init__(self):
        super().__init__()

Deriving from decorated class

If deriving from a decorated class, it is necessary to access the original wrapped class and use it as the base class.

@mydecorator
class Base:
    pass

class Derived(Base.__wrapped__):
    pass

In doing this, the functionality of any decorator on the base class is not inherited. If creation of a derived class needs to also be mediated via the decorator, the decorator would need to be applied to the derived class also.

In this case of trying to decorate a base class in a class hierarchy, it may turn out to be more appropriate to use a meta class instead of trying to decorate the base class.

Note that as of Python 3.7 and wrapt 1.12.0, accessing the true type of the base class using __wrapped__ is not required. Such code though will not work for versions of Python older than Python 3.7.

Using issubclass() on abstract classes

If a class hierarchy has a base class which uses the abc.ABCMeta metaclass, and a decorator is applied to a class in the hierarchy, use of issubclass() with classes where the decorator is applied will result in an exception of:

TypeError: issubclass() arg 1 must be a class

This is due to what can be argued as being a bug in The Python standard library and has been reported (https://bugs.python.org/issue44847).

Using issubclass() and isinstance() with proxied types

When wrapping a class (type) object with ObjectProxy, the issubclass() and isinstance() checks work correctly when the proxy appears on the right side of the check:

import wrapt

class Base:
    pass

class Child(Base):
    pass

proxy = wrapt.ObjectProxy(Base)

issubclass(Child, proxy)       # True
isinstance(Child(), proxy)     # True

This works because Python calls __subclasscheck__ or __instancecheck__ on the proxy, and ObjectProxy delegates these to the wrapped type.

There are several cases that cannot be fixed when the proxy appears on the left side of the check:

  1. issubclass(proxy, WrappedClass) returns False when testing against the same class the proxy wraps. This is because CPython’s issubclass() first performs an identity check (proxy is WrappedClass), which fails since the proxy is not the actual class. It then walks proxy.__bases__ looking for the class, but a class is not in its own __bases__. Checking against ancestors of the wrapped class works correctly since they are found via the __bases__ walk.

    proxy = wrapt.ObjectProxy(Child)
    
    issubclass(proxy, Base)    # True — Base is in Child.__bases__
    issubclass(proxy, Child)   # False — identity check fails
    
  2. issubclass(proxy, ABCClass) raises TypeError when the right-hand class uses abc.ABCMeta as its metaclass. The C-level __subclasscheck__ in ABCMeta strictly requires its argument to be a real class. This is the same limitation described in the Using issubclass() on abstract classes section above.

  3. isinstance(proxy, typing.Dict) and other typing generic aliases return False when the proxy wraps a matching value. This happens because typing._BaseGenericAlias.__instancecheck__ is implemented using type(obj) rather than obj.__class__. Because type() returns the concrete C-level type, it sees ObjectProxy instead of the wrapped object’s class, and the check fails. This is a known CPython issue (https://github.com/python/cpython/issues/89949).

    from typing import Dict
    import wrapt
    
    proxy = wrapt.ObjectProxy({1: 2})
    
    isinstance(proxy, dict)    # True — default __instancecheck__ uses __class__
    isinstance(proxy, Dict)    # False — typing uses type(obj)
    

    The workaround is to check against the origin type instead:

    import typing
    
    isinstance(proxy, typing.get_origin(Dict))    # True
    

    Note that the newer parameterised generic syntax (dict[str, int]) does not support isinstance() checks at all — with or without a proxy — and raises TypeError.

More generally, any __instancecheck__ or __subclasscheck__ implementation that calls type(obj) instead of inspecting obj.__class__ will see ObjectProxy rather than the wrapped type. The same applies to C-level type-check macros such as PyTuple_Check or PyDict_Check, which inspect the internal ob_type field directly. Code paths that rely on these C-level checks — for example, the C-accelerated JSON encoder in the standard library — will not recognise a proxied object as the type it wraps:

import json
import wrapt

proxy = wrapt.ObjectProxy((1, 2, 3))

json.dumps(proxy)    # TypeError — C encoder does not see a tuple

This is an inherent limitation of the transparent proxy pattern: the proxy can override __class__ at the Python level, but it cannot change the object’s C-level type.

Deriving from ObjectProxy alongside an ABCMeta-based class

A custom proxy that derives from both ObjectProxy and a second base class whose metaclass is abc.ABCMeta will fail when used with isinstance() or issubclass():

from abc import ABC
from wrapt import ObjectProxy

class Base(ABC):
    pass

class Proxy(ObjectProxy, Base):
    pass

isinstance(1, Proxy)
# TypeError: descriptor '__subclasscheck__' for '_wrappers.ObjectProxy'
# objects doesn't apply to a 'type' object

The same failure occurs when the second base class is one of the abstract base classes exported from collections.abc (for example Hashable, Iterable, Container), since they too use ABCMeta as their metaclass.

The cause is the way ObjectProxy implements __instancecheck__ and __subclasscheck__. These are defined as instance methods on the proxy class so that an ObjectProxy instance can appear on the right hand side of an isinstance() or issubclass() check and have the check delegate to the wrapped type. They rely on self being a real proxy instance, and the C extension enforces this at the descriptor level.

When ObjectProxy is mixed in as a base class alongside an ABCMeta-based class, those methods are inherited as ordinary instance methods on the resulting class. Unlike the default type.__instancecheck__, ABCMeta.__instancecheck__ performs its work by calling cls.__subclasscheck__(...) via normal attribute access on the class. That attribute access finds the inherited __subclasscheck__ from ObjectProxy and invokes it with a class as the first argument. The C descriptor sees that the first argument is not an ObjectProxy instance and raises the TypeError shown above.

Mixing ObjectProxy with one of these abstract base classes at runtime is almost always the wrong approach to begin with. ObjectProxy is designed to be used as a single base class, with derived classes overriding only the specific methods that need to change. Adding a second, unrelated base class brings in extra protocol-level behaviour which interacts poorly with what ObjectProxy already does internally.

The usual motivation for adding an abstract base class such as Hashable to the base list is to satisfy a static type checker which has been told to expect the proxy to be declared as a subtype of that abstract base class. At runtime the inheritance is typically redundant. The abstract base classes in collections.abc use a structural __subclasshook__ (Hashable is satisfied by anything that defines __hash__, Iterable by anything that defines __iter__, and so on), and ObjectProxy already defines those methods where appropriate, forwarding to the wrapped object. So isinstance(proxy, Hashable) is already True for an ObjectProxy instance without any explicit inheritance:

from collections.abc import Hashable
import wrapt

isinstance(wrapt.ObjectProxy("s"), Hashable)    # True

The runtime inheritance from the abstract base class adds nothing useful in this case, and brings in the ABCMeta metaclass which then collides with ObjectProxy as described above.

The recommended approach is to keep the runtime class hierarchy clean and present the type-checker-required relationship using typing constructs rather than runtime inheritance. When the annotation site is under your own control, the cleanest option is to define a typing.Protocol that captures the required structural shape and use that as the annotation, instead of inheriting from an abstract base class. ObjectProxy will structurally satisfy such a Protocol through the dunder methods it already forwards, with no inheritance and no runtime change at all.

When the annotation site is not under your own control and demands a nominal subtype of a specific abstract base class, the class can be declared twice in the same file, guarded by typing.TYPE_CHECKING:

from typing import TYPE_CHECKING

from wrapt import ObjectProxy

if TYPE_CHECKING:
    from collections.abc import Hashable

    class Proxy(ObjectProxy, Hashable):
        ...
else:
    class Proxy(ObjectProxy):
        pass

TYPE_CHECKING is False at runtime and True during static analysis, and both mypy and pyright honour this. The type checker sees the multi-base version, which satisfies whatever annotation required Hashable to appear in the inheritance chain. The Python interpreter only ever executes the else branch, so at runtime Proxy is a plain ObjectProxy subclass with no ABCMeta in the picture and the original TypeError does not occur.

The same trick generalises to any case where the view of a class presented to a static type checker needs to differ from the runtime class hierarchy. If several such declarations need to be maintained together it can be cleaner to lift them into a sibling .pyi stub file, which the type checker will honour in preference to the .py source. For a single case the inline TYPE_CHECKING form is usually enough.

Using the json module with ObjectProxy

Serialising an ObjectProxy with the standard library json module does not work reliably, even when the wrapped object is a type that json natively supports. The reason is the same C-level type check issue described in the preceding section: the C-accelerated encoder that json.dumps() uses by default inspects the internal ob_type field of each value rather than calling isinstance(), and therefore does not recognise a proxied dict, list, tuple, str, int, float or bool as the type it wraps.

For example, the following raises TypeError from the C encoder, even though the proxy wraps a plain dict:

import json
import wrapt

proxy = wrapt.ObjectProxy({"b": "123"})

json.dumps({"a": proxy})    # TypeError: ... is not JSON serializable

The json module falls back to its pure Python encoder in a handful of cases, most notably when indent is supplied. The pure Python encoder uses isinstance() checks, which ObjectProxy satisfies for the wrapped type, so the same call succeeds if indent is passed:

json.dumps({"a": proxy}, indent=4)    # works, pure Python encoder

Relying on indent as a way to force a working encoder is fragile (it is an implementation detail of the standard library and affects output formatting), so it should not be treated as a real fix.

The supported approach is to supply a default fallback to json.dumps() that unwraps any ObjectProxy instance. The default callable is invoked by the encoder for any value it does not otherwise know how to serialise, and its return value is then encoded in place of the original:

import json
import wrapt

def unwrap_proxy(obj):
    if isinstance(obj, wrapt.ObjectProxy):
        to_json = getattr(obj, "__to_json__", None)
        if to_json is not None:
            return to_json()
        return obj.__wrapped__
    raise TypeError(
        f"Object of type {obj.__class__.__name__} is not JSON serializable"
    )

json.dumps({"a": proxy}, default=unwrap_proxy)

This pattern also gives derived proxy classes a place to contribute extra state to the serialised form. A subclass that wants its own attributes to appear alongside the wrapped value can define a __to_json__() method (the name is a local convention, not something recognised by the json module) that returns the representation to encode:

class Tagged(wrapt.ObjectProxy):
    def __init__(self, wrapped, metadata):
        super().__init__(wrapped)
        self._self_metadata = metadata

    def __to_json__(self):
        result = dict(self.__wrapped__)
        result["_metadata"] = self._self_metadata
        return result

The default callback is only consulted for values the encoder cannot otherwise handle. When indent is supplied and the pure Python encoder is in use, an ObjectProxy around a dict or list is recognised directly via isinstance() and walked structurally, so __to_json__() will not be called in that case. Code that needs the __to_json__() hook to run consistently should therefore avoid combining indent with a proxy over a JSON container type, or should apply the unwrapping itself before calling json.dumps().

The underlying limitation cannot be fixed in wrapt. The C encoder in the standard library is deliberately written against concrete C-level types for performance, and a transparent proxy has no way to present itself as a different C-level type. Any code path that similarly bypasses isinstance() in favour of C-level type-check macros will exhibit the same behaviour.

Serialising an ObjectProxy

Attempting to pickle an instance of ObjectProxy (or any subclass of BaseObjectProxy) that does not override __reduce__ will fail with NotImplementedError:

import pickle
import wrapt

proxy = wrapt.ObjectProxy({"a": 1})

pickle.dumps(proxy)
# NotImplementedError: object proxy must define __reduce__()

The object proxy base classes intentionally define __reduce__ such that it raises NotImplementedError. This is because there is no generic implementation that would correctly capture both the wrapped object and any additional state a proxy subclass may add on top of it. The user is therefore required to define __reduce__ on their own proxy subclass, indicating how its data should be saved and restored.

The same restriction applies to third party serialisers such as dill which build on the standard library pickle protocol. They use __reduce__ in the same way as pickle and do not bypass the NotImplementedError raised by the base proxy’s __reduce__. Defining __reduce__ on a proxy subclass therefore makes it serialisable with both pickle and dill. Note, however, that when using dill with a BaseObjectProxy subclass the dump must be made with byref=True so that the proxy class is referenced by its import path rather than serialised by value. The proxy base class is a C extension type and cannot be reconstructed from a serialised class body.

See the “Serialising an Object Proxy” section in Assorted Examples for a worked example of a proxy subclass that implements __reduce__ so that it can be pickled and unpickled, along with notes on using the same subclass with dill.

Serialising a Decorated Function

A function or method decorated with @wrapt.decorator cannot be serialised with pickle or dill as is. Decorators built with @wrapt.decorator are implemented using FunctionWrapper, which is itself a subclass of the object proxy base class, and so inherits the same __reduce__ that raises NotImplementedError:

import dill
import wrapt

@wrapt.decorator
def trace(wrapped, instance, args, kwargs):
    return wrapped(*args, **kwargs)

@trace
def add(a, b):
    return a + b

dill.dumps(add, byref=True)
# NotImplementedError: object proxy must define __reduce__()

The same error is raised by pickle.dumps(add). This is not a special case; it is the same underlying limitation described in the preceding section, surfacing through FunctionWrapper.

Making @wrapt.decorator and FunctionWrapper themselves serialisable in a general way is not something wrapt provides, and is not recommended. FunctionWrapper carries additional state such as optional adapter functions, enabled/disabled flags and descriptor protocol integration. A fully general __reduce__ implementation covering all of that is substantially more involved than a typical application needs, and tying the serialised form to all of it creates a compatibility surface that is awkward to evolve.

The recommended approach, when decorator serialisation is genuinely required, is to build a small decorator factory of your own based on a FunctionWrapper subclass that defines __reduce__ for only the state the factory actually uses. See the “Serialising a Decorator” section in Assorted Examples for a worked example of this pattern, including the byref=True requirement when using dill with a FunctionWrapper subclass.

Before doing that, consider whether decorator serialisation is really needed at all. In most applications decorated functions are rebuilt from source at import time and only plain values travel through the serialisation boundary, so the issue does not arise.

hasattr() on ObjectProxy and pre-defined dunder methods

Although ObjectProxy is described as a transparent object proxy, in practice it always defines a large number of Python “dunder” (double underscore) methods on the proxy class itself, regardless of whether the wrapped object defines the equivalent method. As a result, hasattr() checks for these dunder methods on the proxy always return True, even when the same check against the wrapped object directly would return False:

import wrapt

hasattr(wrapt.ObjectProxy(1), "__contains__")    # True
hasattr(1, "__contains__")                       # False

This is a deliberate design trade-off rather than a bug.

For most Python special methods, the interpreter looks up the method on the type of the object, not on the instance. That is how operators such as x in obj, len(obj), -obj, obj + 1 and so on are dispatched internally. For these operations to delegate correctly to the wrapped object, the corresponding dunder method must be defined on the proxy class. A proxy class that did not define __contains__, __len__, __add__ and the rest could not forward those operations at all, regardless of what the wrapped object supports.

The obvious alternative, generating a custom proxy class per wrapped object whose dunder methods exactly mirror those of the wrapped type, is not free. Constructing a fresh class for every proxy instance adds meaningful memory and construction-time overhead, which is a problem when proxies are used pervasively (for example, when decorating every function and method in a large codebase). ObjectProxy is intended to be cheap enough to use in that setting, so it instead defines a fixed set of dunder methods on a single shared class.

For the dunder methods that ObjectProxy pre-defines, this is usually harmless in practice. Code that wants to use one of these features, such as arithmetic, containment, length, comparison, hashing, attribute access, subscripting or the context-manager protocol, almost always just uses the feature directly. If the wrapped object does not implement the corresponding dunder method, the shim on ObjectProxy will delegate through to self.__wrapped__ and an AttributeError will be raised from there, which is the same exception Python would raise for an object that didn’t define the method in the first place. Code that simply does len(obj), a in b or x + y therefore behaves correctly whether or not the argument is a proxy.

The cases where this becomes a problem are the dunder methods whose existence is itself meaningful, typically methods that Python introspection APIs or user code probe for explicitly rather than just invoking. The most common examples are:

  • __call__, probed by callable(obj).

  • __iter__ and __next__, probed by code that distinguishes iterables from iterators, or that wants to decide whether an object can be iterated.

  • __aiter__, __anext__ and __await__, the async counterparts of the above, probed by async frameworks when deciding how to drive an object.

  • Descriptor protocol methods __get__, __set__, __delete__ and __set_name__, whose mere presence on a class attribute changes how Python treats that attribute.

  • __length_hint__, consulted by built-ins as an optimisation hint; its presence implies the object can cheaply estimate its length.

If ObjectProxy defined these unconditionally, callable(proxy) would always be True even when wrapping a non-callable, every proxy would appear to be iterable, and every proxy attribute on a class body would silently behave as a descriptor. To avoid those incorrect answers, these specific methods are not defined on ObjectProxy by default. The trade-off is that if you want a proxy around a callable (or an iterable, or an awaitable, and so on) to itself be recognised as callable/iterable/awaitable, you have to opt in.

There are two ways to opt in:

  1. Define a derived proxy class that implements the required dunder methods explicitly. This is the preferred approach when you know at the point of wrapping what kind of object you are wrapping, and is the cheapest in terms of runtime cost. For example, to wrap a callable so that callable(proxy) returns True:

    class CallableProxy(wrapt.ObjectProxy):
        def __call__(self, *args, **kwargs):
            return self.__wrapped__(*args, **kwargs)
    

    Equivalent subclasses can be defined for iterators, async iterators, awaitables and descriptors, adding only the dunder methods actually required.

  2. Use AutoObjectProxy when the type of the wrapped object is not known statically, or varies. AutoObjectProxy inspects the wrapped object at construction time and dynamically creates a subclass that defines exactly those problematic dunder methods (__call__, __iter__, __next__, __aiter__, __anext__, __await__, __length_hint__, __get__, __set__, __delete__, __set_name__) that the wrapped object actually supports:

    import wrapt
    
    proxy = wrapt.AutoObjectProxy(lambda: 42)
    callable(proxy)    # True
    
    proxy = wrapt.AutoObjectProxy(42)
    callable(proxy)    # False
    

    The cost is that AutoObjectProxy generates a new class per wrapped object. That is significantly more expensive, in both time and memory, than using a pre-defined proxy class, and the generated classes are not deduplicated. AutoObjectProxy is therefore intended for situations where the flexibility is genuinely needed, typically a small number of long-lived proxies over objects of varying types, rather than as a drop-in replacement for ObjectProxy.

The short version is that ObjectProxy chooses a fixed set of pre-defined dunder methods as a compromise between transparency and efficiency. The dunder methods whose presence is benign in practice are defined unconditionally; the dunder methods whose presence would give incorrect answers to introspection are left off, and opt-in is provided via subclassing or AutoObjectProxy. Code that relies on hasattr(proxy, "__some_dunder__") producing the same answer as hasattr(wrapped, "__some_dunder__") will therefore see mismatches for the pre-defined set, and should either test the wrapped object directly (via proxy.__wrapped__) or use the feature and handle any resulting AttributeError rather than probing for it.

__qualname__ snapshot vs live-read divergence

The Python and C implementations of ObjectProxy handle the __qualname__ attribute differently. CPython does not allow __qualname__ to be overridden via a Python property — it must be an actual string object stored on the instance. To work around this, the pure-Python ObjectProxy.__init__ copies the wrapped object’s __qualname__ into the proxy’s instance dictionary at construction time using object.__setattr__(). This creates a snapshot of the value.

The C extension, by contrast, uses a PyGetSetDef descriptor to live-read __qualname__ from the wrapped object on every access. C-level getset descriptors operate below the layer where CPython enforces the “must be a real string” restriction, so this works without issue.

The practical consequence is that if the wrapped object’s __qualname__ is mutated directly (not through the proxy) after wrapping, the two implementations diverge:

import wrapt

def foo(): pass

proxy = wrapt.ObjectProxy(foo)
foo.__qualname__ = "Changed"

# Pure-Python: proxy.__qualname__ returns the original value (snapshot)
# C extension: proxy.__qualname__ returns "Changed" (live-read)

This only matters when code mutates __qualname__ on the wrapped object after the proxy has been constructed. Setting __qualname__ through the proxy (proxy.__qualname__ = "new") updates both the wrapped object and the local snapshot in the Python implementation, so both implementations agree in that case.

Free-threaded Python (PEP 703)

The C extension declares Py_mod_gil = Py_MOD_GIL_NOT_USED on Python 3.13 and later, which allows it to be loaded into a free-threaded build without the runtime re-enabling the GIL on import. This declaration is sound for the way wrapt is used in the overwhelming majority of applications: a decorator is applied to a function or class at import time, the resulting wrapper or proxy is published once, and from then on it is only read (called, introspected, used as a descriptor) from multiple threads. That pattern is safe on free-threaded builds.

The current implementation does not, however, guarantee safety when a single proxy or wrapper instance is mutated from one thread while another thread concurrently reads from or calls it. In particular, the following operations are not race-free on free-threaded builds when the same instance is shared across threads:

  • Assigning to __wrapped__ (or any other proxy attribute) on an ObjectProxy while another thread is calling, iterating, or performing attribute access on the same proxy.

  • Reassigning fields on a FunctionWrapper (such as the enabled callable, the wrapper function, or the bound instance) after construction, while another thread is invoking the wrapper.

  • Replacing the captured args or kwargs on a PartialCallableObjectProxy while another thread is calling it.

In each case the writer’s update is not atomic with respect to a concurrent reader. A reader may observe a torn view of multiple proxy fields, or, in the worst case, a use-after-free of an object that the writer has just released. The same hazards exist in the pure-Python implementation — Python attribute assignment is not atomic with respect to readers in any meaningful sense — so the limitation is a property of the proxy model, not specifically of the C extension.

The recommended pattern on free-threaded builds is therefore the same as the pattern on GIL builds: construct the proxy or wrapper once, publish it, and treat it as immutable thereafter. Concurrent readers and concurrent calls are supported; concurrent mutation of a shared instance is not.

More robust support for free-threaded Python — covering the shared-mutation case via atomic field access and per-instance critical sections — is being investigated for a future release. Until then, applications that genuinely need to mutate a shared proxy from multiple threads should serialise those mutations externally (for example, with a threading.Lock held across both the write and any concurrent read).

Introspecting the ObjectProxy instance __dict__

ObjectProxy replaces __dict__ with a property that delegates to the wrapped object. This means that vars(proxy) returns the wrapped object’s __dict__ rather than the proxy’s own instance dictionary:

import wrapt

class Target:
    def __init__(self, name):
        self.name = name

class MyProxy(wrapt.ObjectProxy):
    def __init__(self, wrapped):
        super().__init__(wrapped)
        self._self_tag = "example"

target = Target("test")
proxy = MyProxy(target)

vars(proxy)       # {'name': 'test'} — no '_self_tag'

This is by design — the proxy is meant to be transparent — but it makes it difficult to introspect what attributes are stored on the proxy instance itself.

To allow introspection of the proxy’s own instance dictionary, ObjectProxy exposes it as __self_dict__:

proxy.__self_dict__    # {'_self_tag': 'example', ...}

This returns the live instance dictionary of the proxy, so any _self_ attributes set on the proxy will appear there. Mutations to the returned dictionary are reflected on the proxy.

If the combined view of the wrapped object’s __dict__ together with the proxy’s own _self_ attributes is desired as the result of vars(), a derived ObjectProxy can override __dict__ with its own property:

class IntrospectableProxy(wrapt.ObjectProxy):
    def __init__(self, wrapped):
        super().__init__(wrapped)
        self._self_tag = "example"
        self._self_count = 0

    @property
    def __dict__(self):
        result = self.__wrapped__.__dict__.copy()
        result.update(self.__self_dict__)
        return result

target = Target("test")
proxy = IntrospectableProxy(target)

vars(proxy)       # includes 'name' from target and '_self_tag', '_self_count'

Note that because the result is a copy, modifying the dictionary returned by vars() in this case will not affect either the proxy or the wrapped object.

Ternary pow() with ObjectProxy

The three-argument form of the builtin pow() function does not accept an ObjectProxy in every argument position, and the set of positions that is accepted depends on whether the C extension is in use.

With the pure Python implementation, only the first argument (the base) may be an ObjectProxy. This is because the __pow__ method on ObjectProxy unwraps self before delegating to pow(), but it does not unwrap the second argument or the modulo argument. When pow() is called with a proxy in either of those positions, the underlying numeric type has no way to coerce the proxy and a TypeError is raised.

With the C extension, the first and second arguments may both be an ObjectProxy because the nb_power slot explicitly unwraps them before dispatching to PyNumber_Power. The modulo argument is still not unwrapped, for two reasons. Firstly, unwrapping modulo would make the C extension behaviour diverge further from the pure Python and PyPy implementations, which cannot be made to support a proxy in that slot. Secondly, if PyNumber_Power were invoked with a proxy modulo, the CPython ternary operator fallback would end up calling back into the proxy’s nb_power slot indefinitely, overflowing the C stack. A proxy passed as the modulo argument therefore always results in a TypeError regardless of implementation.

The practical consequence is that for portability across the pure Python implementation, the C extension and PyPy, callers should pass only the base as an ObjectProxy and should unwrap the exponent and modulo arguments themselves where necessary.

import wrapt

base = wrapt.ObjectProxy(2)
exponent = wrapt.ObjectProxy(3)
modulo = wrapt.ObjectProxy(5)

# Portable: only the base is a proxy.
pow(base, 3, 5)

# C extension only: exponent may also be a proxy.
pow(base, exponent, 5)

# Not supported anywhere: unwrap the modulo yourself.
pow(base, exponent, modulo.__wrapped__)

pytest setup_class/teardown_class hooks

Decorators implemented using @wrapt.decorator are silently bypassed when applied to the setup_class or teardown_class xunit-style hooks that pytest recognises on test classes. The decorator is never invoked when pytest runs the hook, even though the same decorator applied to a regular test method, or to setup_method / teardown_method, works correctly.

The following test illustrates the problem:

import wrapt

@wrapt.decorator
def pass_through(wrapped, instance, args, kwargs):
    return wrapped(*args, foo=1, **kwargs)

class TestMyClass:
    @pass_through
    @classmethod
    def setup_class(cls, **kwargs):
        cls.kwargs = kwargs

    def test_something(self):
        assert self.kwargs == {'foo': 1}

When this is run under pytest, test_something fails with kwargs equal to {} rather than {'foo': 1}. The pass_through decorator is never called. Replacing its body with a raise confirms that the wrapper is not entered at all.

The cause is specific to how pytest invokes the class-level xunit hooks. For these hooks, pytest does not use normal attribute access on the class. Instead, it retrieves the attribute statically, then extracts the underlying function object by reading __func__ directly (via an internal helper _pytest.compat.getimfunc), and finally calls that function with the class as an explicit first argument. The equivalent of:

cls.setup_class()        # normal, goes through descriptor protocol

is replaced by:

func = setup_class.__func__    # reach past the descriptor
func(cls)                     # call the raw function directly

Reading __func__ off a @classmethod (or @staticmethod) returns the underlying plain function, that is, the original function supplied to the method decorator. When a @wrapt.decorator has been applied on top of the @classmethod, wrapt’s wrapper object sits between the classmethod descriptor and the caller, and its behaviour is delivered via the descriptor binding protocol. By reading __func__ and calling the result directly, pytest bypasses the descriptor binding protocol entirely, and with it any decorator that relies on that protocol to inject itself into the call.

The same shortcut is taken for teardown_class, and analogous behaviour applies to the setup_class / teardown_class forms whether the method is declared with @classmethod, as a plain function taking cls, or as a zero-argument function. By contrast, setup_method and teardown_method are invoked by pytest via normal attribute access on the instance, so the descriptor protocol is honoured and @wrapt.decorator wrappers on those hooks work correctly.

This is believed to be an issue in pytest rather than in wrapt. The Python descriptor binding protocol is the language-defined mechanism for invoking methods, including @classmethod and @staticmethod, and decorators, proxies and other wrappers legitimately rely on it to interpose on calls. Code that reaches past that protocol by extracting __func__ and calling it directly will skip any wrapping applied via the descriptor protocol, regardless of whether the wrapper is from wrapt or implemented by some other means. If pytest were to follow normal Python practice, invoking the hook via attribute access on the class and allowing the descriptor protocol to deliver the correctly bound callable, the problem would not arise.

It is likely that the __func__ extraction in pytest is a historical shortcut rather than a considered design decision. Pytest supports three different ways of declaring the class-level xunit hooks, namely as a @classmethod, as a plain function taking cls, or as a zero-argument function, and the __func__ trick normalises all three into a single uniform dispatch: always call the underlying plain function with cls as an explicit argument. This was probably the shortest path that worked across older versions of Python, and it has remained in place ever since because it handles the common cases. The same uniformity could be achieved today without reaching past the descriptor protocol, for example by dispatching through normal attribute access and using inspect.signature to decide how many arguments to pass, but the original code has never been revisited to match more current practice.

Until this is addressed upstream in pytest, the practical workaround is to avoid applying @wrapt.decorator directly on top of @classmethod or @staticmethod for the setup_class and teardown_class hooks. The decorated behaviour can instead be moved into an ordinary helper method that the hook calls, or into a setup_method / teardown_method hook, both of which are dispatched through normal attribute access and therefore honour decorators applied via @wrapt.decorator.