【问题标题】:Differentiate accessing class attribute from within the class (or file) and from outside in python在 python 中区分从类(或文件)内部和外部访问类属性
【发布时间】:2019-07-13 21:50:48
【问题描述】:

让我们有一个 Python 中的示例类 Foo:

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

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

我尝试实现__getattribute__ 方法,但没有成功区分这些情况。

我知道这是一个很奇怪的问题,但请不要像“你不应该需要这个”那样回答我。

【问题讨论】:

  • Foo().access_bar()Foo.access_bar() 不同
  • 您似乎有提出问题的动机。如果您解释为什么需要这种区分(即使只是出于好奇!),这可能会有所帮助。
  • 您真的想要 class 属性而不是实例属性 (self.bar = 'bar')?您确定自己知道其中的区别吗?

标签: python python-3.7


【解决方案1】:

这是您问题的“真实”答案,您可能不应该这样做:

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 的类,否则 inspect.getsourcelines(Foo) 可能不会给出正确的结果。

【讨论】:

  • print(Foo.bar) 不打印警告。这仅适用于 Foo 类的实例。如果您希望它也适用于类属性,您将需要 metaclass
  • 我想这正是我所需要的,非常感谢!我会试着玩它。我知道必须有一些东西,因为一切都可以在 Python 中完成! :-)
  • 好电话,我什至不敢告诉 OP 为此使用检查模块。 “你可能不应该这样做”给你我的投票:)
  • 现在它检查属性是否是从 access_bar() 方法外部调用的,如果我想在 Foo 类外部检查怎么办(在类内部的任何方法中访问的 bar 都不会打印警告)?
  • 如何不使用inspect 来查找对__getattribute__modifying it slightly 的调用的来源,以便它学会打印警告?我不知道这个主意有多好。
【解决方案2】:

您可以将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`,而且我不喜欢下划线(以及关于使用它们的 PyCharm 警告)。因此,我尝试实现一种不必在类内部使用下划线的方法,但是当用户尝试从外部访问该属性时会收到警告。
  • @JosefKvita 请记住there are no private class variables in python。无论您如何尝试隐藏保存该值的内部变量,总有人可以找到直接访问它的方法。如果这是为了阻止其他工程师访问敏感字段,也许可以尝试与他们进行对话:)
  • 我知道。我只想打印一个警告,而不是让它无法访问。 :)
  • @JosefKvita 为什么你经常从内部访问__bar?唯一的直接访问不应该来自access_bar吗?如果没有,我的回答对你没有帮助,它不区分课内和课外。
【解决方案3】:

我建议将值存储在受保护的(一个前导下划线)或私有(两个下划线)属性中,并使bar 成为可以安全访问的属性,相当于您问题中的access_bar。这就是在 Python 中通常完成此类事情的方式。

class Foo:
    _bar = 'bar'

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

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

【讨论】:

    【解决方案4】:

    这是改进Alex's answer 的另一种尝试,方法是添加一个元类以便它也适用于类属性,并取消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
    

    【讨论】:

    • 这方面的坏事是我必须使用self.__getattribute__('bar', warn=False),而不仅仅是self.bar,在课堂内的任何地方(比如100个地方)访问属性,还是我错了?这肯定比self._bar 更糟。
    • 没错。我认为这个解决方案可以防止你在任何地方使用instance.bar,包括在课堂内。它鼓励您将self.bar 替换为self.access_bar()
    猜你喜欢
    • 1970-01-01
    • 2021-11-27
    • 1970-01-01
    • 1970-01-01
    • 2015-06-09
    • 2014-08-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多