3

I want to create a proxy class that wraps an int for thread-safe access. In contrast to the built-in type, the proxy class is mutable, so that it can be incremented in-place. Now, I want to use that class just as a normal integer from the outside. Usually, Python's __getattr__ makes it very easy to forward attribute access to the inner object:

class Proxy:

    def __init__(self, initial=0):
        self._lock = threading.Lock()
        self._value = initial

    def increment(self):
        with self._lock:
            self._value += 1

    def __getattr__(self, name):
        return getattr(self._value, name)

However, __getattr__ does not get triggered for magic methods like __add__, __rtruediv__, etc that I need for the proxy to behave like an integer. Is there a way to generate those methods automatically, or otherwise forward them to the wrapped integer object?

Community
  • 1
  • 1
danijar
  • 32,406
  • 45
  • 166
  • 297
  • Implicit lookup is also mentioned in the official docs: https://docs.python.org/3/reference/datamodel.html#special-lookup – Ilja Everilä Aug 28 '16 at 00:02
  • 1
    @IljaEverilä I know why `__getattr__` doesn't work for this, and that's not what my question is asking. I'm asking for a way to generate or otherwise forward the methods. So a workaround if you want to see it that way. – danijar Aug 28 '16 at 00:33
  • 1
    Related to your question: http://yauhen.yakimovich.info/blog/2011/08/12/wrapping-built-in-python-types/ – VPfB Aug 28 '16 at 08:42
  • @VPfB. The link seems dead. Do you have an updated version, or at least the title of the post available? – Mad Physicist Feb 11 '18 at 19:39
  • @MadPhysicist Unfortunately no, but I was able to find a saved copy. The link is `http://web.archive.org/web/20160516220457/http://yauhen.yakimovich.info/blog/2011/08/12/wrapping-built-in-python-types/` – VPfB Feb 11 '18 at 20:45
  • @VPfB. Thanks, that was incredibly useful. – Mad Physicist Feb 12 '18 at 03:00

1 Answers1

2

The blog post linked by @VPfB in the comments has a more generic and thorough solution to proxying dunder methods for builtin types, but here's a simplified and rather brutish example for the same. I hope it helps in understanding how to create such forwarding methods.

import threading
import numbers


def _proxy_slotted(name):
    def _proxy_method(self, *args, **kwgs):
        return getattr(self._value, name)(*args, **kwgs)
    # Not a proper qualname, but oh well
    _proxy_method.__name__ = _proxy_method.__qualname__ = name
    return _proxy_method

# The list of abstract methods of numbers.Integral
_integral_methods = """
    __abs__ __add__ __and__ __ceil__ __eq__ __floor__
    __floordiv__ __int__ __invert__ __le__ __lshift__
    __lt__ __mod__ __mul__ __neg__ __or__ __pos__ __pow__
    __radd__ __rand__ __rfloordiv__ __rlshift__ __rmod__
    __rmul__ __ror__ __round__ __rpow__ __rrshift__
    __rshift__ __rtruediv__ __rxor__ __truediv__ __trunc__
    __xor__""".split()

# The dunder, aka magic methods
_Magic = type('_Magic', (),
              {name: _proxy_slotted(name)
               for name in _integral_methods})


class IntProxy(_Magic, numbers.Integral):
    """
    >>> res = IntProxy(1) + 1
    >>> print(type(res), res)
    <class 'int'> 2
    >>> print(IntProxy(2) / 3)
    0.6666666666666666
    """

    def __init__(self, initial=0, Lock=threading.Lock):
        self._lock = Lock()
        self._value = initial

    def increment(self):
        with self._lock:
            self._value += 1

    def __getattr__(self, name):
        return getattr(self._value, name)
Community
  • 1
  • 1
Ilja Everilä
  • 50,538
  • 7
  • 126
  • 127