Attribute Explanations¶
The classcore.standard.explanations.explain_attribute() function explains why an
attribute of a class or an instance is mutable or immutable and visible or
concealed. It returns an explanation record which carries, for each operation
— assignment, deletion, and survey — a verdict with the rule which decided
the outcome. Explanations are observational: they neither bypass nor alter the
behaviors they describe.
>>> import classcore.standard as ccstd
Explaining Immutability¶
Consider a decorated class with selective mutability. Explaining an excluded attribute name reports the decision which permitted the operation, with its payload carried by a typed record.
>>> @ccstd.with_standard_behaviors( mutables = ( 'count', ) )
... class Counter:
... count: int = 0
...
>>> explanation = ccstd.explain_attribute( Counter( ), 'count' )
>>> assign = explanation.operations[ 'assign' ]
>>> assign.permissible
True
>>> assign.decision.name
'count'
Explaining a non-excluded attribute reports that the operation is prohibited.
>>> explanation = ccstd.explain_attribute( Counter( ), 'total' )
>>> assign = explanation.operations[ 'assign' ]
>>> assign.permissible
False
>>> type( assign.decision ).__name__
'Prohibit'
Explaining Concealment¶
Concealment verdicts follow precedence semantics, like every operation: the first matching visibility rule decides the survey verdict.
>>> import re
>>> @ccstd.with_standard_behaviors(
... visibles = ( re.compile( 'pub.*' ), ) )
... class Ledger:
... public_total = 1
... _secret = 2
...
>>> explanation = ccstd.explain_attribute( Ledger( ), 'public_total' )
>>> survey = explanation.operations[ 'survey' ]
>>> survey.permissible
True
>>> survey.decision.pattern
'pub.*'
>>> explanation = ccstd.explain_attribute( Ledger( ), '_secret' )
>>> explanation.operations[ 'survey' ].permissible
False
Internal Attributes¶
Framework attribute names mark as internal wherever the framework’s machinery built the class. Standard library machinery names, like the ABC caches, mark only where that machinery actually operates — a class without ABC machinery keeps such a name unmarked.
>>> import hashlib
>>> digest = hashlib.sha256( b'x' ).hexdigest( )
>>> explanation = ccstd.explain_attribute(
... Ledger( ), f"_classcore_class_in_progress_{digest}" )
>>> explanation.internal
True
>>> explanation = ccstd.explain_attribute( Ledger( ), '_abc_cache' )
>>> explanation.internal
False
>>> class Registry( ccstd.AbstractObject ):
... pass
...
>>> explanation = ccstd.explain_attribute( Registry( ), '_abc_cache' )
>>> explanation.internal
True
>>> explanation = ccstd.explain_attribute( Ledger( ), 'public_total' )
>>> explanation.internal
False
Record Immutability¶
Explanation records are themselves immutable, including every nested collection.
>>> explanation = ccstd.explain_attribute( Ledger( ), 'public_total' )
>>> explanation.name = 'other'
Traceback (most recent call last):
...
classcore.exceptions.AttributeImmutability: Could not assign or delete attribute 'name' on instance of class 'classcore.standard.explanations.AttributeExplanation'.
Summaries¶
The explanation record renders a human-readable summary of the whole decision trace, so digging through attributes and mappings is only necessary for programmatic use.
>>> print( ccstd.explain_attribute( Ledger( ), 'public_total' ) )
'public_total' on instance of class 'builtins.Ledger'
behaviors: concealment, immutability (instance)
assign: prohibited (no permitting rule)
delete: prohibited (no permitting rule)
survey: permitted by regex 'pub.*'