How to get the class from which a method was called?

前端 未结 1 826
春和景丽
春和景丽 2021-01-29 06:52

The get_calling_class function must pass the following tests by returning the class of the method that called the A.f method:



        
相关标签:
1条回答
  • 2021-01-29 07:36

    Walking the stack should give the answer.
    The answer should ideally be, in the caller's stack frame.

    The problem is, the stack frames only record the function
    names (like so: 'f', 'g', 'h', etc.) Any information about
    classes is lost. Trying to reverse-engineer the lost info,
    by navigating the class hierarchy (in parallel with the
    stack frame), did not get me very far, and got complicated.

    So, here is a different approach:
    Inject the class info into the stack frame
    (e.g. with local variables),
    and read that, from the called function.

    import inspect
    
    class A:
      def f(self):
        frame = inspect.currentframe()
        callerFrame = frame.f_back
        callerLocals = callerFrame.f_locals
        return callerLocals['cls']
    
    class B(A):
      def g(self):
        cls = B
        return self.f()
        
      def f(self):
        cls = B
        return super().f()
    
    class C(B):
      def h(self):
        cls = C
        return super(B, self).f()
      
      def f(self):
        cls = C
        return super().f()
    
    c = C()
    assert c.h() == C
    assert c.g() == B
    assert c.f() == B
    

    Related:
    get-fully-qualified-method-name-from-inspect-stack


    Without modifying the definition of subclasses:
    Added an "external" decorator, to wrap class methods.
    (At least as a temporary solution.)

    import inspect
    
    class Injector:
      def __init__(self, nameStr, valueStr):
        self.nameStr = nameStr
        self.valueStr = valueStr
      
      # Should inject directly in f's local scope / stack frame.
      # As is, it just adds another stack frame on top of f.
      def injectInLocals(self, f):
        def decorate(*args, **kwargs):
          exec(f'{self.nameStr} = {self.valueStr}')
          return f(*args, **kwargs)
        return decorate
    
    class A:
      def f(self):
        frame = inspect.currentframe()
        callerDecoratorFrame = frame.f_back.f_back  # Note:twice
        callerDecoratorLocals = callerDecoratorFrame.f_locals
        return callerDecoratorLocals['cls']
    
    class B(A):
      def g(self): return self.f()
      def f(self): return super().f()
    
    class C(B):
      def h(self): return super(B, self).f()
      def f(self): return super().f()
    
    bInjector = Injector('cls', B.__name__)
    B.g = bInjector.injectInLocals(B.g)
    B.f = bInjector.injectInLocals(B.f)
    
    cInjector = Injector('cls', C.__name__)
    C.h = cInjector.injectInLocals(C.h)
    C.f = cInjector.injectInLocals(C.f)
    
    c = C()
    assert c.h() == C
    assert c.g() == B
    assert c.f() == B
    

    I found this link very interesting
    (but didn't take advantage of metaclasses here):
    what-are-metaclasses-in-python

    Maybe someone could even replace the function definitions*,
    with functions whose code is a duplicate of the original;
    but with added locals/information, directly in their scope.

    *
    Maybe after the class definitions have completed;
    maybe during class creation (using a metaclass).

    0 讨论(0)
提交回复
热议问题