Getting the instance of a class decorated function

Viewed 61

I have a class as follows:

class A:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __call__(self, func):
        def wrapper():
            print("decorated", self.x, self.y)
            func()
        return wrapper

Now if I want to use this class to decorate functions:

@A(4, 5)
def fn():
  print("original function")

fn()

Output:

decorated 4 5
original function

Questions:

  1. Since in @A(3, 4) the __init__ class constructor is called, where did the created instance of class A go? Is there a way to reference it?

  2. How can I access the properties self.x, self.y outside of the class, without calling fn()? (I want something like fn.x or fn.y, but they don't work)

2 Answers

You can use __closure__ to get a tuple of cell objects.

In this case the first element is a cell object for the function and the second is the cell object for the class A.

You can access that using f.__closure__[1] and get the object using .cell_contents.

class A:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __call__(self, func):
        def wrapper():
            print("decorated", self.x, self.y)
            func()
        return wrapper

@A(1, 2)
def f():
    print('fun')


A_cell = f.__closure__[1]

A_obj = A_cell.cell_contents

print(A_obj.x, A_obj.y) # 1 2

To answer your questions

  1. Is there a way to reference the created instance of class A? Yes, A_obj = f.__closure__[1].cell_contents
  2. How can I access the properties self.x, self.y outside of the class? A_obj.x, A_obj.y

If you are on Python 3.8+ you can use := and avoid dunders and cells.

You can decorate like so

@(a := A(1, 2)) # this assigns the object to `a`
def f():
    print('fun')

print(a is f.__closure__[1].cell_contents) # True

Which is pretty much this

a=A(1, 2)

@a
def f():
    print('fun')

You can set the attributes directly on the function, if you like:

class A:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __call__(self, func):
        def wrapper():
            print("decorated", self.x, self.y)
            func()
        wrapper.x = self.x
        wrapper.y = self.y
        wrapper.decorator = self
        return wrapper

@A(4, 5)
def fn():
    pass

print(fn.x, fn.y, fn.decorator)

which gives the output:

laurel% python3 ~/tmp/dec.py
4 5 <__main__.A object at 0x7f79ebb2cfd0>
laurel% 
Related