区分 class 层次结构内部和外部的调用者

Discriminate between callers inside and outside class hierarchy

我有两个 classes AB,其中 B 继承自 A 并覆盖 属性。 A 不在我的控制之下,所以我无法更改它。

代码如下所示:

class A():
    def __init__(self, value):
        self.value = value
    @property
    def value(self):
        return self._value
    @value.setter
    def value(self, value):
        self._value = value

class B(A):
    def __init__(self, value):
        super(B, self).__init__(value)
    @property
    def value(self):
        return super(B, self).value
    @value.setter
    def value(self, value):
        raise AttributeError("can't set attribute")

当我尝试调用 B(1) 时,我显然得到了 AttributeError: can't set attribute

当从 class 方法

中设置 value 时,我希望有不同的行为
 @value.setter
 def value(self, value):
     if set from inside class hierarchy:
         pass
     else:
         raise AttributeError("can't set attribute")

模块 inspect 似乎没有给我足够的信息来执行此操作,除了对照已知函数列表进行检查。

您可以使用发出调用的代码来确定调用是否来自 class 内部。仅当调用不是以 self.value =.

开头时才抛出异常
import re
import traceback

class A(object):
    def __init__(self, value):
        self.value = value
    @property
    def value(self):
        return self._value
    @value.setter
    def value(self, value):
        self._value = value

class B(A):
    def __init__(self, value):
        super(B, self).__init__(value)
    @property
    def value(self):
        return super(B, self).value
    @value.setter
    def value(self, value):
        call = traceback.extract_stack(limit=2)[0][3]
        if re.match(r'self.value\s*=', call):
            pass
        else:
            raise AttributeError("can't set attribute")

b = B(1) # OK
b.value = 3 # Exception

当然,一旦您开始调用变量,这就会中断 self:

self = B(1) # OK
self.value = 3 # Meh, doesn't fail

您可以检查堆栈以确定调用者,以及它是否在 class 层次结构中以决定是否允许它:

import inspect


def who_called():
    frame = inspect.stack()[2][0]
    if 'self' not in frame.f_locals:
        return None, None
    cls = frame.f_locals['self'].__class__
    method = frame.f_code.co_name
    return cls, method

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

    @property
    def value(self):
        return self._value

    @value.setter
    def value(self, value):
        self._value = value

    # Assuming this existed it would also work
    def change_value(self, value):
        self.value = value

Class B 现在检查:

class B(A):
    def __init__(self, value):
        super(B, self).__init__(value)

    @property
    def value(self):
        return super(B, self).value

    @value.setter
    def value(self, value):
        cls, method = who_called()
        if cls in B.__mro__ and method in A.__dict__:
            self._value = value
        else:
            raise AttributeError("can't set attribute")

证明:

b = B('does not raise error')
b.change_value('does not raise error')
b.value = 'raises error'