区分从 class(或文件)内部和从外部 python 访问 class 属性

Differentiate accessing class attribute from within the class (or file) and from outside in python

让我们举个例子 class Foo in Python:

class Foo:
    bar = 'bar'
    def access_bar(self):
        return self.bar  

我可以,例如,在直接访问 Foo().bar 时打印警告,但同时在调用 Foo().access_bar() 时不打印此警告,它从 [=27] 中访问该属性=]?

我尝试实施 __getattribute__ 方法,但无法区分这些情况。

我知道这是一个很奇怪的问题,但请不要像 'You should not need this' 那样回答我。

您可以将 bar 设为 属性,这样可以在不向外部显示方法调用的情况下控制访问,并将您的属性设为私有:

class Foo:
    __bar = 'bar'
    @property
    def bar(self):
        print("direct access")
        return Foo.__bar
    def access_bar(self):
        return self.__bar

f = Foo()

print("warn",f.bar)
print("OK",f.access_bar())

打印:

direct access
warn bar
OK bar

我建议将值存储在受保护(一个前导下划线)或私有(两个下划线)属性中,并使 bar 成为可以安全访问的 属性,相当于 access_bar 在你的问题中。在 Python.

中,通常就是这样做的
class Foo:
    _bar = 'bar'

    @property
    def bar(self):
        # do extra things here
        return self._bar

用户仍然可以编写 foo._barfoo._Foo__bar(对于私有属性)以在没有任何警告的情况下从外部获取属性,但是如果他们知道围绕前导下划线的约定,他们可能会这样做有点不舒服,请注意风险。

这是您问题的 'real' 答案,您可能不应该这样做:

import inspect


class Foo:
    bar = 'bar'

    def access_bar(self):
        return self.bar

    def __getattribute__(self, item):
        if item == 'bar':
            code = inspect.currentframe().f_back.f_code
            if not (start_lineno <= code.co_firstlineno <= end_lineno
                    and code.co_filename == __file__):
                print('Warning: accessing bar directly')
        return super().__getattribute__(item)


lines, start_lineno = inspect.getsourcelines(Foo)
end_lineno = start_lineno + len(lines) - 1

print(1, Foo().bar)
print(2, Foo().access_bar())

如果您这样做,重要的是文件中只有一个名为 Foo 的 class,否则 inspect.getsourcelines(Foo) 可能不会给出正确的结果。

这是另一个改进 的尝试,方法是添加元 class 以便它也适用于 class 属性,并取消 inspect 模块,取而代之的是向 __getattribute__ 函数本身添加一个警告标志。

class FooType(type):
    def __getattribute__(self, item):
        if item == "bar":
            print("Warning: accessing bar directly from class")
        return item.__getattribute__(self, item)

class Foo(object, metaclass=FooType):
    bar = 'bar'

    def access_bar(self):
        return self.__getattribute__('bar', warn=False)

    def __getattribute__(self, item, warn=True):
        if item == 'bar' and warn:
            print('Warning: accessing bar directly from instance')
        return super().__getattribute__(item)


print(Foo.bar)
#Warning: accessing bar directly from class
#bar

print(Foo().bar)
#Warning: accessing bar directly from instance
#bar

print(Foo().access_bar())
#bar