I have a list of attributes, say ["foo", "bar", "baz"], and I want to write a metaclass which ensures the following properties:
- They can all be accessed once, in any order
- They cannot be accessed again until all have been accessed once
For example, the following is how it might work:
class TrackedAttrsMeta:
@classmethod
def with_attributes(cls, *args):
cls._expected_attrs = set(args)
return cls
class MyClass(metaclass=TrackedAttrsMeta.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
The implementation of a custom __getattribute__ might look like so:
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()
You can implement this metaclass like so, but it will only allow one set of "tracked" attributes per class. Instead of constructing the metaclass with a classmethod, you will add a
_tracked_attribute as well as the metaclass to your class constructor.You can use it like this: