TL;DR: Are there alternatives to hasattr
which don't trigger property getters?
I'm writing a Python interface to some existing code where I am getting and setting values for various shape and section classes. Due to a large number of possible combinations, I currently create new classes dynamically, subclassing a SectionHandler
class and shape class, such as Circle
. Each has specific methods that I want to preserve.
As this API will be used in scripts and interactively, I want to make sure that any attributes modified after class instantiation already exists (so that new attributes can't be created from typos and the user is warned when a non-existent attribute is specified). Since this check needs to 'skip' all the new attributes being added during sub-classing, I preload the attributes into the new class using the attributes dictionary in the type
function (represented below by the name
and index
attributes).
I'm using hasattr
to check if the attribute exists in the created class by overriding __setattr__
, as suggested in this SO post. This works when the attribute does not exist but the issue is when the attribute does exist - since hasattr
seems to work by calling the property getter, I get extraneous logging calls from the getter, which pollutes the log over many attribute modifications (especially for longer messages).
Is there another way to check for a class attribute in dynamically generated classes? I tried looking in self.__dict__
instead of using hasattr
but that dictionary is empty at class creation time. What alternatives are there?
I've tried to give a minimal working example that reflects the structure I'm working with below:
import logging
class CurveBase:
"""Base class for all curves/shapes."""
def __setattr__(self, attr, value):
"""Restrict to setting of existing attributes only."""
if hasattr(self, attr):
return super().__setattr__(attr, value)
else:
raise AttributeError(f'{attr} does not exist in {self.__class__.__name__}')
class Circle(CurveBase):
"""Circle-type shape base class."""
@property
def diameter(self):
logging.info(f'Getting {self.name} section {self.index} diameter')
# diameter = external_getter("Circle_Diameter")
# return diameter
@diameter.setter
def diameter(self, diameter):
logging.info(f'Setting {self.name} section {self.index} diameter to: {diameter}')
# external_setter("Circle_Diameter", diameter)
class SectionHandler:
def __init__(self):
# Minimal example init
self.name = 'section_1'
self.index = 1
if __name__ == '__main__':
# This is set up by the API code
logging.basicConfig(level='INFO', format='%(asctime)s - %(levelname)s - %(message)s')
shape = 'circle'
attribute_dict = {'name': None, 'index': None} # Generated based on classes used.
NewSectionClass = type(f'{shape.capitalize()}Section',
(SectionHandler, Circle),
attribute_dict)
section = NewSectionClass()
# This is an example of API usage
print(section.diameter)
# Returns:
# 2018-12-04 18:53:07,805 - INFO - Getting section_1 section 1 diameter
# None # <-- this would be a value from external_getter
section.diameter = 5
# Returns:
# 2018-12-04 18:53:07,805 - INFO - Getting section_1 section 1 diameter # <-- extra getter call from hasattr()!!!
# 2018-12-04 18:53:07,805 - INFO - Setting section_1 section 1 diameter to: 5
section.non_existent
# Correctly returns:
# Traceback (most recent call last):
# File "scratch_1.py", line 50, in <module>
# section.non_existent
# AttributeError: 'CircleSection' object has no attribute 'non_existent'