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

Fre*_*abe 28 python

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

class Article:
    def __init__(self, name, available):
        self.name = name
        self.available = available
Run Code Online (Sandbox Code Playgroud)

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

a = Article("Pineapple", True)
a.name = "Banana"  # <-- should not be possible
Run Code Online (Sandbox Code Playgroud)

不可能了吗?

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

  1. 使用吸气剂(呃!).

    class Article:
        def __init__(self, name, available):
            self._name = name
            self.available = available
    
        def name(self):
            return self._name
    
    Run Code Online (Sandbox Code Playgroud)

    丑陋,非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
    
    Run Code Online (Sandbox Code Playgroud)

    在调用者方面看起来很漂亮,似乎是执行这项工作的惯用方法 - 但不幸的是,我有很多只有少数几个字段才能只读取每个字段.所以我需要__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
    
    Run Code Online (Sandbox Code Playgroud)

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

到目前为止,(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
Run Code Online (Sandbox Code Playgroud)

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

  • 我需要继承object(无论如何,这不是一个愚蠢的事情,我猜)
  • 我需要换self._name = namero_property(self, "name", name).

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

Chr*_*ris 40

我将property用作装饰器来管理你的getter name(参见Parrot文档中类的示例).例如,使用类似的东西:

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

    @property
    def name(self):
        return self._name
Run Code Online (Sandbox Code Playgroud)

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

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

  • 这在调用者方面看起来很漂亮,但在实际编写类时它仍然看起来像样板.如果我可以在构造函数中执行类似`@property self_name = name`之类的操作,那将是很漂亮的,这样Python就会自动为我定义getter.实际上,这个代码对于调用者来说更好,但是为类作者编写的代码比普通的getter(`@ property`的额外行)更多. (2认同)

Sve*_*ach 14

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

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

from operator import attrgetter

class Article(object):
    def __init__(self, name, available):
        self._name = name
        self.available = available
    name = property(attrgetter("_name"))
Run Code Online (Sandbox Code Playgroud)

一般来说,我不喜欢定义特定于域的函数来执行可以使用标准工具轻松完成的操作.如果您不必首先习惯所有项目特定的东西,那么阅读代码会更容易.

  • 关于*为什么*克里斯'答案如此优秀的理由真的让我赢了.我认为一些聪明的伎俩,就像罗德里戈发布的那样真的很整洁,但事实上,我确实在一个地方得到了一些眉毛. (2认同)

rod*_*igo 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"
Run Code Online (Sandbox Code Playgroud)

如果尝试修改属性,它将引发一个AttributeError.

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

更新:关于您更新的答案,我看到的(小)问题是您每次创建实例时都在修改类中属性的定义.而且我认为这不是一个好主意.这就是为什么我将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
Run Code Online (Sandbox Code Playgroud)

班装饰者很有意思!


Sim*_*ser 5

应该注意的是,始终可以在 Python 中修改对象的属性——Python 中没有真正的私有变量。只是有些方法使它变得有点困难。但是一个坚定的编码者总是可以查找和修改一个属性的值。例如,__setattr__如果我想,我可以随时修改您的...

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