如何防止属性重新访问,直到所有属性都被访问一次(Python元类)?

问题描述 投票:0回答:1

我有一个属性列表,比如

["foo", "bar", "baz"]
,我想编写一个元类来确保以下属性:

  • 它们都可以访问一次
  • 在全部访问过一次之前,无法再次访问它们

例如,以下是它的工作原理:

class MetaAttributeTracker:
  @classmethod
  def with_attributes(cls, *args):
    cls._expected_attrs = set(args)
    return cls

class MyClass(metaclass=MetaAttributeTracker.with_attributes("foo", "bar", "baz")):
  foo: "quux"
  bar: {"blah": -1}
  baz: 1

ob = MyClass()
for _ in range(10):
  print(ob.foo) # Allowed
  print(ob.bar) # Allowed
  print(ob.foo) # Fails with an error because "baz" has not been accessed yet
  print(ob.baz) # Would be allowed if not for above
  print(ob.foo) # Would be allowed because we've accessed every attribute in the list once

自定义

__getattribute__
的实现可能如下所示:

def _tracking_getattr(self, name):
  value = super().__getattribute__(name)
  remaining = list(expected_attrs - self._accessed_attrs)
  if name in self._accessed_attrs:
    raise ValueError(f"Already accessed '{name}', must access all of {remaining} before this is allowed again")
           
  self._accessed_attrs.add(name)

  # Once we've seen them all, clear out the record so we can see them again
  if self._accessed_attrs == expected_attrs:
    self._accessed_attrs = set()
python metaclass
1个回答
0
投票

您可以像这样实现这个元类,但它只允许每个类有一组“跟踪”属性。您无需使用类方法构造元类,而是将

_tracked_
属性以及元类添加到类构造函数中。

class TrackedAttrsMeta(type):
    def __new__(cls, cls_name, bases, dct):
        # Which attributes have been accessed so far
        dct["_trk_accessed_"] = set()

        # Which attributes we apply the tracking to
        tracked_attrs = set(dct.get("_tracked_", []))
        dct["_trk_attributes_"] = tracked_attrs

        cls_instance = super().__new__(cls, cls_name, bases, dct)

        # Preserve the object's original getattr
        gtattr = cls_instance.__getattribute__

        def _tracking_getattr(cls, name):
            value = gtattr(cls, name)
            # Prevent a stackoverflow by handling these attributes specially
            if name in ["_trk_attributes_", "_trk_accessed_"]:
                return value
            if name in cls._trk_attributes_:
                remaining = list(cls._trk_attributes_ - cls._trk_accessed_)
                if name in cls._trk_accessed_:
                    raise AttributeError(f"Cannot access {name} before accessing {remaining}")
                cls._trk_accessed_.add(name)
                if cls._trk_accessed_ == cls._trk_attributes_:
                    cls._trk_accessed_.clear()
            return value

        # Use the custom getattr
        cls_instance.__getattribute__ = _tracking_getattr

        return cls_instance

class YourClass(metaclass=TrackedAttrsMeta):
    _tracked_ = ["foo", "bar", "baz"]
    foo = "bah"
    bar = 2
    baz = {"h": 6}

# Example usage:
obj = YourClass()
print(obj.foo)  # Access foo for the first time
# print(obj.foo) # This will raise AttributeError
print(obj.bar)  # Access bar for the first time
print(obj.baz)  # Access baz for the first time
print(obj.foo)  # This is allowed since we've accessed all
print(obj.bar)  # This is allowed since we've accessed all

# The following will raise an AttributeError, as we just accessed bar
# print(obj.bar)
最新问题
© www.soinside.com 2019 - 2024. All rights reserved.