如何公开Python类中的只读字段?

2024-04-19 22:40:31 发布

您现在位置: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. 用吸气剂。

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

    丑陋的、非pythonic的——还有很多样板代码要写(特别是如果我有多个字段要使其成为只读的话)。然而,它确实起到了作用,而且很容易看出原因。

  2. 使用^{}

    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)基本上是一样的,只是我没有把getter写得很清楚,而是做了如下的事情

    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中的运行时代码生成。:-)

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

不管怎样,我要唱Python2.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)

在我看来,这很整洁-有人能看出它的缺点吗?


Tags: 方法nameselfobjectroinitvaluedef
3条回答

我将使用^{}作为装饰器来管理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(在函数周围使用decoratorx.setter),则在尝试重置name时,将抛出AttributeError

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

根据克里斯的回答,但可以说更像是Python:

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

UPDATE:关于您更新的答案,我看到的(小)问题是,每次创建实例时,您都在修改类中属性的定义。我不认为这是个好主意。所以我把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

班主任真是太花哨了!

正如在其他答案中指出的,使用属性是只读属性的方法。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"))

一般来说,我不喜欢定义特定于域的函数来做一些可以用标准工具轻松完成的事情。如果你不需要先习惯所有特定于项目的东西,那么阅读代码就容易多了。

相关问题 更多 >