【问题标题】:How to determine if a method was called from within the class where it's defined?如何确定是否从定义方法的类中调用了方法?
【发布时间】:2023-03-29 05:23:01
【问题描述】:

我正在尝试实现一种(诚然 unPythonic)封装大量实例变量的方式。

我将这些变量的名称映射到字典中的相应值,因此我没有编写大量样板文件(即self.var = val,例如 50 次),而是在调用 __setattr__() 时遍历字典,这样:

class MyClass:
    __slots__ = ("var1", "var2", "var3")
    def __init__(self, data):
        for k, v in data.items():
            self.__setattr__(k, v)

然后我将覆盖__setattr__(),以控制对这些属性的访问。 在__setattr__() 中,我将首先检查对象是否具有该属性,以便允许在__init__() 中调用 setattr:

def __setattr__(self, k, v):
    if k in self.__class__.__slots__:
        if hasattr(self, k):
            return print("Read-only property")
    super().__setattr__(k, v)

问题是,我还需要在myClass 的其他地方写入其中的一些属性,即使它们已经在__init__() 中初始化。所以我正在寻找某种方法来确定 setattr 是在类范围内还是在类范围外被调用,例如:

class MyClass:
    __slots__ = ("var",)
    def __init__(self):
        self.__setattr__("var", 0)
    def increase_val(self):
        self.var += 1  # THIS SHOULD BE ALLOWED

my_obj = MyClass()
my_obj.var += 1  # THIS SHOULD BE FORBIDDEN

我的伪定义是这样的:

# pseudocode
def setattr:
    if attribute in slots and scope(setattr) != MyClass:
        return print("Read-only property")
    super().setattr

另外,我不想将整个字典存储在一个实例变量中,因为我需要属性是不可变的。

【问题讨论】:

  • 看起来应该使用name mangling 的情况很少见,但请记住,它仍然允许从类外部修改变量,这将更加困难
  • 另一种选择是带有框架检查的属性
  • @DeepSpace 我觉得检查方法其实很有趣,谢谢你的建议!

标签: python scope encapsulation


【解决方案1】:

回答我自己的问题,与有相同问题的人分享。

感谢 cmets 中的@DeepSpace,我深入研究了之前完全忽略的 帧检查 主题。

由于众所周知的inspect 库在某些部分(即我主要感兴趣的部分)依赖于sys._getframe(),因此我决定改用sys

该函数返回执行栈中当前的帧对象,它配备了一些有用的属性。

例如,f_back 允许您定位直接的外框,如果 __setattr__() 在类中被调用,则它就是类本身。

在外框架上,f_locals 返回一个字典,其中包含框架本地范围内的变量及其各自的值。

可以在f_locals 中查找self 以确定上下文是否是一个类,尽管它有点“脏”,因为任何非类上下文都可能有一个self 变量也。 但是,如果 self 映射到 MyClass 类型的对象,则不应有歧义。

这是我对__setattr__()的最终定义

def __setattr__(self, k, v):
    if k in self.__class__.__slots__:
        self_object = sys._getframe(1).f_back.f_locals.get("self")
        if self_object is None or self_object.__class__ != MyClass:
            return print(k, "is a read-only property")
    super().__setattr__(k, v)

作为结论,我觉得在 Python 中追求可变隐私有点逆潮流;根据公认的标准将变量标记为“受保护”,这绝对是一种更清洁的解决方案,而无需过多考虑实际可访问性。

另一个注意事项是,对于用于生产的应用程序而言,帧检查看起来不是一种非常可靠的方法,而更像是一种调试工具。事实上,一些 inspect 函数不适用于某些 Python 实现,例如那些缺乏堆栈框架支持的人。

【讨论】:

  • 您一直使用的return print(...) 是怎么回事?您似乎也可以检查self_object == self
  • 我使用 return print() 只是为了在打印后立即返回函数(因为 print 返回 None)并避免陷入 super().__setattr__()。我通常更喜欢在满足某些“停止”条件时返回,而不是使用 if-else 块(只是一种编码风格,没有任何理由)。根据自我,如果我使用self_object == self,我可能会允许继承相同__setattr__ 的子类更改该属性,而我不想这样做。我实际上并没有在我的问题中明确说明这一点,但我希望定义函数的类是唯一能够做到这一点的类。
  • 防止继承是有道理的,我想。至于return print() 的基本原理,您似乎真的应该提出某种例外。
  • 我在另一个场合也被告知过。我还是不明白为什么;我对 Python 比较陌生,但是 return 不应该接受一个值或评估某个结果的东西吗?如果是这种情况, print() 在技术上评估为无。虽然我承认将代码压缩到不必要的程度,但我仍然觉得有趣的是了解为什么这样的做法是不好的做法。
  • IMO 这是一个糟糕的做法,因为您正在执行 print 的副作用,这可能是大多数人不欢迎的,但更重要的是,设置属性失败会被忽略并允许程序执行继续。似乎没有按预期做应该引发一个致命的异常,除非有一个 except 处理程序,以便调用者可以以其他方式处理它。
猜你喜欢
  • 1970-01-01
  • 2010-09-25
  • 2016-04-22
  • 1970-01-01
  • 1970-01-01
  • 2011-04-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多