__getattr__的对应方法

3
我试图找到一种方法来设置封装在类中的字典值,例如使用__getattr__可以返回内部字典值,但是__setattr__即使存在属性也会被调用,使我的实现变得丑陋。下面的示例简化了我的实际类,它继承自Subject类(观察者模式中的subject部分)。
我试图实现以下内容:
obj = Example()
obj.username = 'spidername' # all OK username is a key in the internal dict
# but company is not a key in the internal dict so
obj.company = 'ABC' # will raise AttributeError

我想知道是否有比我下面所做的更好的方法:
class Example(object):
    def __init__(self, table=None):
        self._fields = {}
        self._table = table

    def _set_fields(self):
        """
        this method will be implemented by 
        subclasses and used to set fields names and values
        i.e.
        self._field['username'] = Field(default='unknown', is_primary=False)
        """
        raise NotImplementedError

    def __getattr__(self, name):
        """
        great this method is only called when "name"
        is not an attribute of this class
        """
        if name in self._fields:
            return self._fields[name].value
        return None

    def __setattr__(self, name, value):
        """
        not so great, this method is called even for
        attributes that exists in this class

        is there a better way to do the following?             
        this can be in __init__, but its still ugly
        """
        attribs = ['_fields', '_table'] 
        if name in attribs:
            super(Example, self).__setattr__(name, value)
        else:
            if name in self._fields:
                self._fields[name].value = value
            else:
                raise AttributeError

编辑:修改代码中的注释,添加缺失的引号。


顺带一提:已经有许多优秀的Python库可用于进行对象关系映射(ORM),例如SQLAlchemy;如果这是你想要做的事情。 - Preet Kukreti
谢谢,是的我知道,还有Storm和SQLObject这两个我知道的。但是我更喜欢一些更简单的,而且是我自己的东西。 - andrea
2个回答

3
问题在于,当属性首次被分配时,它们并不存在。在__init__中,当你第一次将一个字典赋给_fields时,_fields并不是一个属性。只有在它被赋值后,它才成为一个已存在的属性。如果你预先知道这些属性是什么,可以使用__slots__,但我猜测你不知道。因此,我的建议是手动将它们插入实例字典中:
class Example(object):
    def __init__(self, table=None):
        self.__dict__['_fields'] = {}
        self.__dict__['_table'] = table

    ...

    def __setattr__(self, name, value):
        if name in self._fields:
            self._fields[name].value = value
        else:
            raise AttributeError

然而,使用这种实现方式后,您只能通过 __dict__ 来添加或更改实例属性。但我认为这不太可能发生。

1
我认为他根本不需要hasattr - 根据他在__setattr__的评论中说“我知道这可以在__init__中”,我没有印象他需要在__init__之外再次分配它们。 - agf
我先尝试了使用__slots__,但是没有起作用,因为__setattr__仍然被调用,这会导致问题,因为在__setattr__内部,我不知道name是指在__init__中设置的合法类属性还是self._fields中的键。你的解决方案完美地解决了这个问题。唯一的问题是,正如agf所评论的那样,在__setattr__内部不再需要使用hasattr。谢谢! - andrea

2

值得注意的是,你可以通过直接使用__slots__来实现你的整体目标:

>>> class Example(object):
        __slots__ = ['username']

>>> obj = Example()
>>> obj.username = 'spiderman'
>>> obj.company = 'ABC'

Traceback (most recent call last):
  File "<pyshell#18>", line 1, in <module>
    obj.company = 'ABC'
AttributeError: 'Example' object has no attribute 'company'

即使使用 __slots__,每个属性仍然会调用 __setattr__,而我不希望如此,我希望只有在客户端代码想要在内部字典中设置值时才调用 __setattr__。 - andrea

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接