GrahamDumpleton / wrapt

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

Not clear how to inspect ObjectProxy's fields #175

Open arquolo opened 3 years ago

arquolo commented 3 years ago

Given:

import wrapt

class A:
    def __init__(self, value):
        self.value = value

class B(wrapt.ObjectProxy):
    def __init__(self, wrapped):
        super().__init__(wrapped)
        self._self_wvalue = 42

a = A(1234)
print(vars(a))  # {'value': 1234}

b = B(a)
print(vars(b))  # {'value': 1234},  no '_self_wvalue' at all

c = B(1234)
print(vars(c))  # Fails with TypeError, however c's `__dict__` exists, but not accessible

How to retrieve all wrapper-only fields from b? Calling vars(b) will lead to the result of vars(a), but how to retrieve B's fields only? I checked, there's no anything like b._self___dict__.

Tried with wrapt==1.12.1

GrahamDumpleton commented 3 years ago

What is the use case and problem you are trying to solve? What exactly are you expecting to display in that case?

Note that a property like wvalue is actually on the type, not the instance so it would not be returned anyway and could only be found by using vars() on the type.

As to being able to list the _self_?? variables, it actually gets hard if not impossible to do once you override __dict__ as a property to return the wrapped object. Not even sure if can easily capture a reference to the original wrapper __dict__ and make it available under a different variable due to the ordering of how class initialisation works.

So would really like to understand the use case. The proxy objects are meant to be transparent and you aren't really meant to be interacting with the wrapper itself from outside of the wrapper.

arquolo commented 3 years ago

I am trying to inspect B's instance to get memory size with all its contents (including B's instance-only and wrapped-only). The proxy objects are transparent for underlying ones, but not for themselves.

Currently I use __dictoffset__ approach:

from __future__ import annotations

def magic_get_dict(obj) -> dict | None:
    tp = type(obj)  # Real type

    if offset := tp.__dictoffset__:
        if offset < 0:
            offset += tp.__sizeof__(obj)

        addr = id(obj) + offset
        ptr = ctypes.cast(addr, ctypes.POINTER(ctypes.py_object))
        return ptr.contents.value

    return None

print(magic_get_dict(b))  # {..., '_self_wvalue': 42}

Though it relies on ctypes what is pretty ugly.