我应该如何公开Python类的只读字段?

时间:2012-03-29 07:14:54

标签: python

我有许多不同的小班,每个小班都有几个字段,例如这样:

class Article:
    def __init__(self, name, available):
        self.name = name
        self.available = available

使name字段只读的最简单和/或最惯用的方法是什么,以便

a = Article("Pineapple", True)
a.name = "Banana"  # <-- should not be possible

再也不可能了?

这是我到目前为止所考虑的内容:

  1. 使用getter(呃!)。

    class Article:
        def __init__(self, name, available):
            self._name = name
            self.available = available
    
        def name(self):
            return self._name
    

    丑陋,非pythonic - 以及许多要编写的样板代码(特别是如果我有多个字段可以进行只读)。但是,它完成了这项工作,很容易理解为什么会这样。

  2. 使用__setattr__

    class Article:
        def __init__(self, name, available):
            self.name = name
            self.available = available
    
        def __setattr__(self, name, value):
            if name == "name":
                raise Exception("%s property is read-only" % name)
            self.__dict__[name] = value
    

    在调用者方面看起来很漂亮,似乎是执行这项工作的惯用方法 - 但不幸的是,我有很多只有少数几个字段才能只读取每个字段。所以我需要为所有这些实现添加__setattr__实现。或者使用某种混合物?在任何情况下,我都需要决定如何在客户端尝试将值分配给只读字段时如何操作。我想 - 产生一些例外 - 但是哪个?

  3. 使用实用程序功能自动定义属性(以及可选的getter)。这与(1)基本相同,只是我没有明确地写出getters,而是做了类似的事情

    class Article:
        def __init__(self, name, available):
            # This function would somehow give a '_name' field to self
            # and a 'name()' getter to the 'Article' class object (if
            # necessary); the getter simply returns self._name
            defineField(self, "name")
            self.available = available
    

    这样做的缺点是我甚至不知道这是否可行(或者如何实现),因为我不熟悉Python中的运行时代码生成。 : - )

  4. 到目前为止,(2)对我来说似乎最有希望,除了我对所有课程都需要__setattr__定义。我希望有一种方法可以“注释”字段,以便自动发生这种情况。有人有更好的主意吗?

    对于它的价值,我是在唱Python 2.6。

    更新: 感谢所有有趣的回复!到现在为止,我有这个:

    def ro_property(o, name, value):
        setattr(o.__class__, name, property(lambda o: o.__dict__["_" + name]))
        setattr(o, "_" + name, value)
    
    class Article(object):
        def __init__(self, name, available):
            ro_property(self, "name", name)
            self.available = available
    

    这看起来效果很好。原始类所需的唯一更改是

    • 我需要继承object(我猜这不是一件愚蠢的事),我猜)
    • 我需要将self._name = name更改为ro_property(self, "name", name)

    这对我看起来很整洁 - 任何人都可以看到它的缺点吗?

5 个答案:

答案 0 :(得分:38)

我会使用property作为装饰器来管理name的getter(请参阅文档中的类Parrot的示例)。例如,使用类似的东西:

class Article(object):
    def __init__(self, name, available):
        self._name = name
        self.available = available

    @property
    def name(self):
        return self._name

如果没有为name属性定义setter(使用函数周围的装饰器x.setter),则在尝试重置AttributeError时会抛出name

注意:您必须使用Python的新式类(即在Python 2.6中必须继承object)才能使属性正常工作。根据@SvenMarnach,情况并非如此。

答案 1 :(得分:12)

正如其他答案中所指出的,使用属性是获取只读属性的方法。 Chris' answer中的解决方案是最干净的解决方案:它以直接,简单的方式使用内置的property()。每个熟悉Python的人都会认识到这种模式,并且没有特定领域的伏都教。

如果您不喜欢每个属性需要三行来定义,这是另一种直截了当的方式:

from operator import attrgetter

class Article(object):
    def __init__(self, name, available):
        self._name = name
        self.available = available
    name = property(attrgetter("_name"))

通常,我不喜欢定义特定于域的函数来执行可以使用标准工具轻松完成的操作。如果您不必首先习惯所有项目特定的东西,那么阅读代码就会轻松得多。

答案 2 :(得分:8)

基于克里斯的答案,但可以说是更加pythonic:

def ro_property(field):
    return property(lambda self : self.__dict__[field])

class Article(object):
    name = ro_property('_name')

    def __init__(self):
        self._name = "banana"

如果尝试修改属性,则会引发AttributeError

a = Article()
print a.name # -> 'banana'
a.name = 'apple' # -> AttributeError: can't set attribute

更新:关于您更新的答案,我看到的(小)问题是您在创建实例的每个时间类中修改属性的定义。而且我认为这不是一个好主意。这就是为什么我将ro_property调用放在__init__函数

之外的原因

怎么样?:

def ro_property(name):
    def ro_property_decorator(c):
        setattr(c, name, property(lambda o: o.__dict__["_" + name]))
        return c
    return ro_property_decorator

@ro_property('name')
@ro_property('other')
class Article(object):
    def __init__(self, name):
        self._name = name
        self._other = "foo"

a = Article("banana")
print a.name # -> 'banana'
a.name = 'apple' # -> AttributeError: can't set attribute

类装饰器很花哨!

答案 3 :(得分:4)

应该注意的是,总是可以在Python中修改对象的属性 - 在Python中没有真正的私有变量。只是有些方法让它变得更难。但是,确定的编码器总是可以查找和修改属性的值。例如,如果我愿意,我可以随时修改您的__setattr__

有关更多信息,请参阅The Python Tutorial的Section 9.6。当属性以__作为前缀时,Python使用名称修改,因此运行时的实际名称不同,但您仍然可以在运行时派生该名称(从而修改属性)。

答案 4 :(得分:2)

我会坚持使用您的选项1,但将其改进为使用Python属性:

class Article
    def get_name(self):
        return self.__name

    name = property(get_name)