Python 3-在不调用__getattr__的情况下检查类属性

时间:2018-12-04 10:37:55

标签: python python-3.x properties attributes hasattr

TL; DR:hasattr是否有不触发属性获取器的替代方法?

我正在为一些现有代码编写Python接口,在其中获取和设置各种形状和截面类的值。由于存在大量可能的组合,因此我目前动态创建新类,将SectionHandler类和形状类(例如Circle)子类化。每个都有我要保留的特定方法。

由于此API将用于脚本中并以交互方式使用,因此我想确保在类实例化之后修改的所有属性都已经存在(这样,就不能从错别字创建新属性,并且当不存在时会警告用户属性)。由于此检查需要“跳过”子类化过程中添加的所有新属性,因此我使用type函数中的属性字典将属性预加载到新类中(下面由nameindex个属性)。

我正在使用hasattr通过覆盖__setattr__来检查创建的类中的属性是否存在,如this SO post中所建议。当属性不存在,但问题出在属性确实存在时,此方法起作用-因为hasattr似乎可以通过调用属性getter来工作,所以我从getter那里获得了不必要的日志记录调用,这污染了许多属性修改的日志(尤其是较长的邮件)。

还有另一种方法来检查动态生成的类中的类属性吗?我尝试在self.__dict__中查找而不是使用hasattr,但是该字典在类创建时为空。有什么替代品?

我尝试给出一个最小的工作示例,以反映我正在使用的结构:

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'

1 个答案:

答案 0 :(得分:0)

TL;DR

您必须尝试调用 <Object>.__getattribute__() 并捕获错误

您的案例

这样的东西可以工作

class CurveBase:
    """Base class for all curves/shapes."""
    def __setattr__(self, attr, value):
        """Restrict to setting of existing attributes only."""
        try:
            super().__getattribute__(attr)
            return super().__setattr__(attr, value)
        except:
            raise AttributeError(f'{attr} does not exist in {self.__class__.__name__}')

替代方案

  • getattr_static() 来自 inspect 模块,如所描述的 here
  • 您还可以将 __getattribute__ try catch 包装到 bool 函数中,就像第 1 点的答案一样。

参考文献