我有一个属性列表,比如
["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()
您可以像这样实现这个元类,但它只允许每个类有一组“跟踪”属性。您无需使用类方法构造元类,而是将
_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)