开源 Android 云计算 google linux nginx 编程 centos Python mysql Firefox shell Ubuntu Windows php apache java 微软 程序员 wordpress

Python的神奇方法指南:属性访问控制

有许多从其他语言阵营转到 Python 来的人抱怨 Python 对类缺乏真正的封装(比如,没有办法自定义 private 属性,已经给出 public 的 getter 和 setter)。 这可不是真相哟:Python 通过神奇的方法实现了大量的封装,而不是通过明确的方法或字段修饰符。

请看:

__getattr__(self, name)

你可以为用户在试图访问不存在(不论是存在或尚未建立)的类属性时定义其行为。 这对捕捉和重定向常见的拼写错误,给出使用属性警告是有用的(只要你愿意,你仍旧可选计算,返回那个属性)或抛出一个 AttributeError异常。 这个方法只适用于访问一个不存在的属性,所以,这不算一个真正封装的解决之道。

__setattr__(self, name, value)

不像 __getattr__,__setattr__ 是一个封装的解决方案。 它允许你为一个属性赋值时候的行为,不论这个属性是否存在。 这意味着你可以给属性值的任意变化自定义规则。 然而,你需要在意的是你要小心使用 __setattr__,在稍后的列表中会作为例子给出。

__delattr__

这等价于 __setattr__, 但是作为删除类属性而不是 set 它们。 它需要相同的预防措施,就像 __setattr__,防止无限递归(当在 __delattr__ 中调用 del self.name 会引起无限递归)。

__getattribute__(self, name)

__getattribute__ 良好地适合它的同伴们 __setattr__ 和 __delattr__。 可我却不建议你使用它。__getattribute__ 只能在新式类中使用(在 Python 的最新版本中,所有的类都是新式类,在稍旧的版本中你可以通过继承 object 类来创建一个新式类。 它允许你定规则,在任何时候不管一个类属性的值那时候是否可访问的。) 它会因为他的同伴中的出错连坐受到某些无限递归问题的困扰(这时你可以通过调用基类的__getattribute__ 方法来防止发生)。 当 __getattribute__ 被实现而又只调用了该方法如果__getattribute__ 被显式调用或抛出一个 AttributeError 异常,同时也主要避免了对 __getattr__ 的依赖。 这个方法可以使用(毕竟,这是你自己的选择),不过我不推荐它是因为它有一个小小的用例(虽说比较少见,但我们需要特殊行为以获取一个值而不是赋值)以及它真的很难做到实现 0bug。

你可以很容易地在你自定义任何类属性访问方法时引发一个问题。参考这个例子:

def __setattr__(self, name, value):
    self.name = value
    # 当每次给一个类属性赋值时,会调用__setattr__(),这就形成了递归
    # 因为它真正的含义是 self.__setattr__('name', value)
    # 所以这方法不停地调用它自己,变成了一个无法退出的递归最终引发crash

def __setattr__(self, name, value):
    self.__dict__[name] = value # 给字典中的name赋值
    # 在此自定义行为

再一次,Python 的神奇方法向我们展示了其难以置信的能力,同时巨大的力量也伴随着重大的责任。 重要的是让你明白正确使用神奇方法,这样你就不会破坏其他代码。

那么,我们在关于定制类属性访问中学习了什么? 不要轻易地使用,事实上它过于强大以及反直觉。 这也是它为何存在的理由:Python 寻求干坏事的可能性,但会把它们弄得很难。 自由是至高无上的,所以你可以做任何你想做的事情。 以下是一个关于特殊属性访问方法的实际例子(注意,我们使用 super 因为并非所有类都有 __dict__类属性):

class AccessCounter:
    '''一个类包含一个值和实现了一个访问计数器。
    当值每次发生变化时,计数器+1'''

    def __init__(self, val):
        super(AccessCounter, self).__setattr__('counter',0)
        super(AccessCounter, self).__setattr__('value', val)

    def __setattr__(self, name, value):
        if name == 'value':
            super(AccessCounter, self).__setattr__('counter', self.counter + 1)
        # make this unconditional.
        # 如果你想阻止其他属性被创建,抛出AttributeError(name)异常
        super(AccessCounter, self).__setattr__(name, value)

    def __delattr__(self, name)
        if name == 'value':
            super(AccessCounter, self).__setattr__('counter', self.counter + 1)
        super(AccessCounter, self).__delattr__(name)

延伸阅读

评论