what are attributes?
Object attribute are central to most python programs, that is where we store information about our entities. attributes are names for objects, accessed and modified by dot notation.
Managing attributes: by managing attribute access i mean Adding attribute accessor logic. attribute accessor logic is something that we want to run when an attribute is retrieved/modified or is going to get deleted.
Use Cases:
- computing dynamic attributes
- validation, bound checking
- stopping attributes getting changed
- allowing only certain set of attributes to be set on class
- stopping deletion of attributes.
I am going to discuss 4 attribute accessor techniques.
- the property built-in, for routing specific attribute access to get and set handler methods.
- the descriptor protocol, for routing specific attribute access to instance of classes having get/set/delete methods.
- using getattr and setattr, for routing all udefined attribute fetches and assignment to a generic handler method
- using getattribute, for routing all attribute fetches to a generic handler method
the first two of these accessor techniques are generic handlers while last two are specific to particular attributes.
the code i will be showing will work in python 3 and python 2 versions when classes are new-styled classes. new style classes are classes that inherit from object.
Properties:
- Property protocol helps us to introduce attribute accessor logic at get, set and delete operations for specific attribute and we can also provide with attribute docstring.
- Properties are created with property built-in and assigned to class attributes, and like all class attributes can be inherited by their base classes.
- The property built-in accepts four arguments, lets call them fget, fset, fdel and doc. All of these arguments can be passed as None, which will mean None of the get, set or delete operations are permitted on attribute and accessing any of them will result in AttributeError.
- If doc is not provided, the doc of fget will be used and fget does not have a docstring, it will default to None.
- the built-in property call returns a property object assigned as class attribute and can be inherited.
run 01property.py
Points to make:
- print statements when accessing attributes
- properties are inheritable
- class attribute is of <type 'property'>
- has three methods of interest getter, setter and deleter.
run 02_property_as_decorator.py Points to make:
- when we define property with built-in, it takes 3 funcs (optional) as an argument, this makes way for using property as decorators.
- the property type have three methods -> getter, setter and deleter, these methods assign corresponding property accessor methods and return a copy of property itself.
- getter component is filled by act of creating the property.
- we can use these methods as decorators to introduce accessor logic.
run 03computed_attribute.py Points to make:
- properties can be used to compute attributes dynamically when fetched.
Descriptors
- Descriptors allows us to route get, set and delete operation of a specific attribute to instance of some class.
- Descriptors are coded as independent classes, can have three methods, get, set and delete and are assigned to class attributes.
- These methods allows us to insert code to be automatically run when getting, setting or deleteing an attribute.
- Descriptor class instance is assigned as class attribute and is inheritable.
run 04Descriptors.py
Points to make:
- all attribute access is getting routed to descriptors get, set and delete method
- modify the program, raise AttributeError when set is called it a data descriptor
run 05computed_attributes_with_descriptors.py
Points to make:
- getting value have been intercepted and is computed dynamically
- in setting value, we raised AttributeError if value is less than 0.
run 06_mocking_property.py Property class catches attribute access with descriptor protocol and routes access request to methods saved in descriptor state.
Points to make:
- property are convinient way to create a descriptor
- descriptors can maintain state of their own
- go through the code.
"getattr" and "getattribute"
these are generic attribute handlers. we override them in order to intercept attribute fetching operation and run our custom accessor logic. using these two, we can only intercept attribute fetches. (can override setattr for attribute assignment, delattr for attribute deletion.)
"getattr"
- runs for undefined attribute, attributes not stored on an instance or inherited from Parent classes
"getattribute"
- runs for every attribute access
- it gets called first, if call fails, getattr get called
run 07_getattr_and_getattribute.py
Points to make:
- control flow of attribute access interception.
"setattr" and "delattr"
run 08_setattr_delattr.py
Points to make:
- how inside init, how initialization calls setattr which modifies namespace, dict and on calling del, key is removed from namespace dict.