The __dict__
of the class gives us class attributes, which includes the methods (which are plain functions when we look them up directly in the class) and properties:
>>> Policy.__dict__
mappingproxy({'__module__': '__main__', '__init__': <function Policy.__init__ at 0x7f26fb4e9310>, 'some_method': <function Policy.some_method at 0x7f26fb4e93a0>, 'policyDefinitionId': <property object at 0x7f26fb4e2e50>, 'displayName': <property object at 0x7f26fb4e7040>, 'policyDefinitionReferenceId': <property object at 0x7f26fb4e7090>, 'get_all_properties': <function Policy.get_all_properties at 0x7f26fb4e95e0>, '__dict__': <attribute '__dict__' of 'Policy' objects>, '__weakref__': <attribute '__weakref__' of 'Policy' objects>, '__doc__': None})
Notice how the properties are represented as <property object at ...>
. This is because the @property
decorator actually creates instances of a built-in property class, and those instances (through the usual decorator process) replace the original functions. The name property
is exposed as a builtin, so we can trivially use it for type checking:
>>> {k: v for k, v in Policy.__dict__.items() if isinstance(v, property)}
{'policyDefinitionId': <property object at 0x7f26fb4e2e50>, 'displayName': <property object at 0x7f26fb4e7040>, 'policyDefinitionReferenceId': <property object at 0x7f26fb4e7090>}
In the get_all_properties
method, the goal is to apply the logic of each property to the self
instance. There are two approaches we can take:
Using the property
object directly
We need to understand a little about how it works, first:
>>> prop = Policy.__dict__['displayName']
>>> dir(prop)
['__class__', '__delattr__', '__delete__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__get__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__isabstractmethod__', '__le__', '__lt__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__set__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'deleter', 'fdel', 'fget', 'fset', 'getter', 'setter']
See the fget
attribute? That stores the original function. Through this chain of lookup, it's still a function, not a method; so we need to pass an instance explicitly:
>>> policy = Policy()
>>> prop.fget(policy)
'value_display_name'
So now we know everything we need in order to implement get_all_properties
- we just pass self
explicitly, to functions that we access as the .fget
of properties that are found in the __dict__
:
def get_all_properties(self) -> dict:
return {
k: v.fget(self)
for k, v in Policy.__dict__.items()
if isinstance(v, property)
}
Using dynamic lookup
Since we already know the names of the properties from the filtered __dict__
result, we can dynamically look up the attribute with that name on self
. That looks like:
def get_all_properties(self) -> dict:
return {
k: getattr(self, k)
for k, v in Policy.__dict__.items()
if isinstance(v, property)
}
Either way gets us the desired result:
>>> Policy().get_all_properties()
{'policyDefinitionId': 'value_policy_id', 'displayName': 'value_display_name', 'policyDefinitionReferenceId': 'value_def_reference_id'}