Python 只读属性

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/14594120/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-18 11:55:59  来源:igfitidea点击:

Python read-only property

pythonpropertiespython-2.7privatereadonly

提问by Rafa? ?u?yński

I don't know when attribute should be private and if I should use property.

我不知道什么时候属性应该是私有的,我是否应该使用属性。

I read recently that setters and getters are not pythonic and I should use property decorator. It's ok.

我最近读到 setter 和 getter 不是 Pythonic,我应该使用属性装饰器。没关系。

But what if I have attribute, that mustn't be set from outside of class but can be read (read-only attribute). Should this attribute be private, and by private I mean with underscore, like that self._x? If yes then how can I read it without using getter? Only method I know right now is to write

但是,如果我有属性,则不能从类外部设置该属性,但可以读取(只读属性)怎么办。这个属性应该是私有的self._x吗,私有我的意思是下划线,就像那样?如果是,那么如何在不使用 getter 的情况下阅读它?我现在知道的唯一方法是写

@property
def x(self):
    return self._x

That way I can read attribute by obj.xbut I can't set it obj.x = 1so it's fine.

这样我可以读取属性,obj.x但我无法设置它,obj.x = 1所以很好。

But should I really care about setting object that mustn't be set? Maybe I should just leave it. But then again I can't use underscore because reading obj._xis odd for user, so I should use obj.xand then again user doesn't know that he mustn't set this attribute.

但是我真的应该关心设置不能设置的对象吗?也许我应该离开它。但是我又不能使用下划线,因为obj._x用户阅读很奇怪,所以我应该使用obj.x,然后用户不知道他不能设置这个属性。

What's your opinion and practics?

你有什么看法和做法?

采纳答案by Silas Ray

Generally, Python programs should be written with the assumption that all users are consenting adults, and thus are responsible for using things correctly themselves. However, in the rare instance where it just does not make sense for an attribute to be settable (such as a derived value, or a value read from some static datasource), the getter-only property is generally the preferred pattern.

通常,编写 Python 程序时应该假设所有用户都是成年人的同意,因此他们有责任自己正确使用事物。但是,在极少数情况下,属性可设置(例如派生值或从某些静态数据源读取的值)没有意义,仅 getter 属性通常是首选模式。

回答by siebz0r

Just my two cents, Silas Rayis on the right track, however I felt like adding an example. ;-)

只是我的两分钱,Silas Ray 走在正确的轨道上,但是我想添加一个例子。;-)

Python is a type-unsafe language and thus you'll always have to trust the users of your code to use the code like a reasonable (sensible) person.

Python 是一种类型不安全的语言,因此您必须始终相信代码的用户会像理性(明智)的人一样使用代码。

Per PEP 8:

根据PEP 8

Use one leading underscore only for non-public methods and instance variables.

仅对非公共方法和实例变量使用一个前导下划线。

To have a 'read-only' property in a class you can make use of the @propertydecoration, you'll need to inherit from objectwhen you do so to make use of the new-style classes.

要在类中拥有“只读”属性,您可以使用@property装饰,object当您这样做时,您需要继承以使用新式类。

Example:

例子:

>>> class A(object):
...     def __init__(self, a):
...         self._a = a
...
...     @property
...     def a(self):
...         return self._a
... 
>>> a = A('test')
>>> a.a
'test'
>>> a.a = 'pleh'
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: can't set attribute

回答by memeplex

Notice that instance methods are also attributes (of the class) and that you could set them at the class or instance level if you really wanted to be a badass. Or that you may set a class variable (which is also an attribute of the class), where handy readonly properties won't work neatly out of the box. What I'm trying to say is that the "readonly attribute" problem is in fact more general than it's usually perceived to be. Fortunately there are conventional expectations at work that are so strong as to blind us wrt these other cases (after all, almost everything is an attribute of some sort in python).

请注意,实例方法也是(类的)属性,如果您真的想成为一个坏蛋,您可以在类或实例级别设置它们。或者你可以设置一个类变量(这也是类的一个属性),在那里方便的只读属性不能很好地开箱即用。我想说的是,“只读属性”问题实际上比通常认为的更普遍。幸运的是,在工作中存在着如此强烈的传统期望,以至于我们在这些其他情况下都视而不见(毕竟,几乎所有东西都是 Python 中的某种属性)。

Building upon these expectations I think the most general and lightweight approach is to adopt the convention that "public" (no leading underscore) attributes are readonly except when explicitly documented as writeable. This subsumes the usual expectation that methods won't be patched and class variables indicating instance defaults are better let alone. If you feel really paranoid about some special attribute, use a readonly descriptor as a last resource measure.

基于这些期望,我认为最通用和轻量级的方法是采用约定,即“公共”(无前导下划线)属性是只读的,除非明确记录为可写。这包含了通常的期望,即方法不会被修补,并且指示实例默认值的类变量更好更不用说。如果您对某些特殊属性感到非常偏执,请使用只读描述符作为最后的资源度量。

回答by Oz123

Here is a way to avoid the assumption that

这是一种避免假设的方法

all users are consenting adults, and thus are responsible for using things correctly themselves.

所有用户都是同意的成年人,因此有责任自己正确使用事物。

please see my update below

请在下面查看我的更新

Using @property, is very verbose e.g.:

使用@property, 非常冗长,例如:

   class AClassWithManyAttributes:
        '''refactored to properties'''
        def __init__(a, b, c, d, e ...)
             self._a = a
             self._b = b
             self._c = c
             self.d = d
             self.e = e

        @property
        def a(self):
            return self._a
        @property
        def b(self):
            return self._b
        @property
        def c(self):
            return self._c
        # you get this ... it's long

Using

使用

No underscore: it's a public variable.
One underscore: it's a protected variable.
Two underscores: it's a private variable.

没有下划线:它是一个公共变量。
一个下划线:它是一个受保护的变量。
两个下划线:它是一个私有变量。

Except the last one, it's a convention. You can still, if you really try hard, access variables with double underscore.

除了最后一个,这是一个约定。如果您真的很努力,您仍然可以访问带有双下划线的变量。

So what do we do? Do we give up on having read only properties in Python?

那么我们该怎么办?我们会放弃在 Python 中只读属性吗?

Behold! read_only_propertiesdecorator to the rescue!

看!read_only_properties装修师傅来帮忙!

@read_only_properties('readonly', 'forbidden')
class MyClass(object):
    def __init__(self, a, b, c):
        self.readonly = a
        self.forbidden = b
        self.ok = c

m = MyClass(1, 2, 3)
m.ok = 4
# we can re-assign a value to m.ok
# read only access to m.readonly is OK 
print(m.ok, m.readonly) 
print("This worked...")
# this will explode, and raise AttributeError
m.forbidden = 4

You ask:

你问:

Where is read_only_propertiescoming from?

哪里read_only_properties来的?

Glad you asked, here is the source for read_only_properties:

很高兴你问,这是read_only_properties的来源:

def read_only_properties(*attrs):

    def class_rebuilder(cls):
        "The class decorator"

        class NewClass(cls):
            "This is the overwritten class"
            def __setattr__(self, name, value):
                if name not in attrs:
                    pass
                elif name not in self.__dict__:
                    pass
                else:
                    raise AttributeError("Can't modify {}".format(name))

                super().__setattr__(name, value)
        return NewClass
    return class_rebuilder

update

更新

I never expected this answer will get so much attention. Surprisingly it does. This encouraged me to create a package you can use.

我没想到这个答案会受到如此多的关注。令人惊讶的是它确实如此。这鼓励我创建一个您可以使用的包。

$ pip install read-only-properties

in your python shell:

在你的 python shell 中:

In [1]: from rop import read_only_properties

In [2]: @read_only_properties('a')
   ...: class Foo:
   ...:     def __init__(self, a, b):
   ...:         self.a = a
   ...:         self.b = b
   ...:         

In [3]: f=Foo('explodes', 'ok-to-overwrite')

In [4]: f.b = 5

In [5]: f.a = 'boom'
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-5-a5226072b3b4> in <module>()
----> 1 f.a = 'boom'

/home/oznt/.virtualenvs/tracker/lib/python3.5/site-packages/rop.py in __setattr__(self, name, value)
    116                     pass
    117                 else:
--> 118                     raise AttributeError("Can't touch {}".format(name))
    119 
    120                 super().__setattr__(name, value)

AttributeError: Can't touch a

回答by Apollo Marquis

While I like the class decorator from Oz123, you could also do the following, which uses an explicit class wrapper and __new__ with a class Factory method returning the class within a closure:

虽然我喜欢 Oz123 的类装饰器,但您也可以执行以下操作,它使用显式类包装器和 __new__ 以及类工厂方法在闭包中返回类:

class B(object):
    def __new__(cls, val):
        return cls.factory(val)

@classmethod
def factory(cls, val):
    private = {'var': 'test'}

    class InnerB(object):
        def __init__(self):
            self.variable = val
            pass

        @property
        def var(self):
            return private['var']

    return InnerB()

回答by vincedjango

I know i'm bringing back from the dead this thread, but I was looking at how to make a property read only and after finding this topic, I wasn't satisfied with the solutions already shared.

我知道我正在从死里复活这个线程,但我正在研究如何使属性只读,在找到这个主题后,我对已经共享的解决方案不满意。

So, going back to the initial question, if you start with this code:

所以,回到最初的问题,如果你从这段代码开始:

@property
def x(self):
    return self._x

And you want to make X readonly, you can just add:

而你想让 X 只读,你可以添加:

@x.setter
def x(self, value):
    raise Exception("Member readonly")

Then, if you run the following:

然后,如果您运行以下命令:

print (x) # Will print whatever X value is
x = 3 # Will raise exception "Member readonly"

回答by Booboo

Here is a slightly different approach to read-only properties, which perhaps should be called write-once properties since they do have to get initialized, don't they? For the paranoid among us who worry about being able to modify properties by accessing the object's dictionary directly, I've introduced "extreme" name mangling:

这是只读属性的一种稍微不同的方法,它可能应该被称为一次写入属性,因为它们必须被初始化,不是吗?对于我们中间那些担心能够通过直接访问对象的字典来修改属性的偏执狂,我引入了“极端”名称修改:

from uuid import uuid4

class Read_Only_Property:
    def __init__(self, name):
        self.name = name
        self.dict_name = uuid4().hex
        self.initialized = False

    def __get__(self, instance, cls):
        if instance is None:
            return self
        else:
            return instance.__dict__[self.dict_name]

    def __set__(self, instance, value):
        if self.initialized:
            raise AttributeError("Attempt to modify read-only property '%s'." % self.name)
        instance.__dict__[self.dict_name] = value
        self.initialized = True

class Point:
    x = Read_Only_Property('x')
    y = Read_Only_Property('y')
    def __init__(self, x, y):
        self.x = x
        self.y = y

if __name__ == '__main__':
    try:
        p = Point(2, 3)
        print(p.x, p.y)
        p.x = 9
    except Exception as e:
        print(e)

回答by rusiano

That's my workaround.

这就是我的解决方法。

@property
def language(self):
    return self._language
@language.setter
def language(self, value):
    # WORKAROUND to get a "getter-only" behavior
    # set the value only if the attribute does not exist
    try:
        if self.language == value:
            pass
        print("WARNING: Cannot set attribute \'language\'.")
    except AttributeError:
        self._language = value

回答by Michael

I am dissatisfied with the previous two answers to create read only properties because the first solution allows the readonly attribute to be deleted and then set and doesn't block the __dict__. The second solution could be worked around with testing - finding the value that equals what you set it two and changing it eventually.

我对前两个创建只读属性的答案不满意,因为第一个解决方案允许删除只读属性然后设置并且不会阻止 __dict__。第二种解决方案可以通过测试来解决 - 找到与您设置的值相等的值并最终更改它。

Now, for the code.

现在,对于代码。

def final(cls):
    clss = cls
    @classmethod
    def __init_subclass__(cls, **kwargs):
        raise TypeError("type '{}' is not an acceptable base type".format(clss.__name__))
    cls.__init_subclass__ = __init_subclass__
    return cls


def methoddefiner(cls, method_name):
    for clss in cls.mro():
        try:
            getattr(clss, method_name)
            return clss
        except(AttributeError):
            pass
    return None


def readonlyattributes(*attrs):
    """Method to create readonly attributes in a class

    Use as a decorator for a class. This function takes in unlimited 
    string arguments for names of readonly attributes and returns a
    function to make the readonly attributes readonly. 

    The original class's __getattribute__, __setattr__, and __delattr__ methods
    are redefined so avoid defining those methods in the decorated class

    You may create setters and deleters for readonly attributes, however
    if they are overwritten by the subclass, they lose access to the readonly
    attributes. 

    Any method which sets or deletes a readonly attribute within
    the class loses access if overwritten by the subclass besides the __new__
    or __init__ constructors.

    This decorator doesn't support subclassing of these classes
    """
    def classrebuilder(cls):
        def __getattribute__(self, name):
            if name == '__dict__':
                    from types import MappingProxyType
                    return MappingProxyType(super(cls, self).__getattribute__('__dict__'))
            return super(cls, self).__getattribute__(name)
        def __setattr__(self, name, value): 
                if name == '__dict__' or name in attrs:
                    import inspect
                    stack = inspect.stack()
                    try:
                        the_class = stack[1][0].f_locals['self'].__class__
                    except(KeyError):
                        the_class = None
                    the_method = stack[1][0].f_code.co_name
                    if the_class != cls: 
                         if methoddefiner(type(self), the_method) != cls:
                            raise AttributeError("Cannot set readonly attribute '{}'".format(name))                        
                return super(cls, self).__setattr__(name, value)
        def __delattr__(self, name):                
                if name == '__dict__' or name in attrs:
                    import inspect
                    stack = inspect.stack()
                    try:
                        the_class = stack[1][0].f_locals['self'].__class__
                    except(KeyError):
                        the_class = None
                    the_method = stack[1][0].f_code.co_name
                    if the_class != cls:
                        if methoddefiner(type(self), the_method) != cls:
                            raise AttributeError("Cannot delete readonly attribute '{}'".format(name))                        
                return super(cls, self).__delattr__(name)
        clss = cls
        cls.__getattribute__ = __getattribute__
        cls.__setattr__ = __setattr__
        cls.__delattr__ = __delattr__
        #This line will be moved when this algorithm will be compatible with inheritance
        cls = final(cls)
        return cls
    return classrebuilder

def setreadonlyattributes(cls, *readonlyattrs):
    return readonlyattributes(*readonlyattrs)(cls)


if __name__ == '__main__':
    #test readonlyattributes only as an indpendent module
    @readonlyattributes('readonlyfield')
    class ReadonlyFieldClass(object):
        def __init__(self, a, b):
            #Prevent initalization of the internal, unmodified PrivateFieldClass
            #External PrivateFieldClass can be initalized
            self.readonlyfield = a
            self.publicfield = b


    attr = None
    def main():
        global attr
        pfi = ReadonlyFieldClass('forbidden', 'changable')
        ###---test publicfield, ensure its mutable---###
        try:
            #get publicfield
            print(pfi.publicfield)
            print('__getattribute__ works')
            #set publicfield
            pfi.publicfield = 'mutable'
            print('__setattr__ seems to work')
            #get previously set publicfield
            print(pfi.publicfield)
            print('__setattr__ definitely works')
            #delete publicfield
            del pfi.publicfield 
            print('__delattr__ seems to work')
            #get publicfield which was supposed to be deleted therefore should raise AttributeError
            print(pfi.publlicfield)
            #publicfield wasn't deleted, raise RuntimeError
            raise RuntimeError('__delattr__ doesn\'t work')
        except(AttributeError):
            print('__delattr__ works')


        try:
            ###---test readonly, make sure its readonly---###
            #get readonlyfield
            print(pfi.readonlyfield)
            print('__getattribute__ works')
            #set readonlyfield, should raise AttributeError
            pfi.readonlyfield = 'readonly'
            #apparently readonlyfield was set, notify user
            raise RuntimeError('__setattr__ doesn\'t work')
        except(AttributeError):
            print('__setattr__ seems to work')
            try:
                #ensure readonlyfield wasn't set
                print(pfi.readonlyfield)
                print('__setattr__ works')
                #delete readonlyfield
                del pfi.readonlyfield
                #readonlyfield was deleted, raise RuntimeError
                raise RuntimeError('__delattr__ doesn\'t work')
            except(AttributeError):
                print('__delattr__ works')
        try:
            print("Dict testing")
            print(pfi.__dict__, type(pfi.__dict__))
            attr = pfi.readonlyfield
            print(attr)
            print("__getattribute__ works")
            if pfi.readonlyfield != 'forbidden':
                print(pfi.readonlyfield)
                raise RuntimeError("__getattr__ doesn't work")
            try:
                pfi.__dict__ = {}
                raise RuntimeError("__setattr__ doesn't work")
            except(AttributeError):
                print("__setattr__ works")
            del pfi.__dict__
            raise RuntimeError("__delattr__ doesn't work")
        except(AttributeError):
            print(pfi.__dict__)
            print("__delattr__ works")
            print("Basic things work")


main()

There is no point to making read only attributes except when your writing library code, code which is being distributed to others as code to use in order to enhance their programs, not code for any other purpose, like app development. The __dict__ problem is solved, because the __dict__ is now of the immutable types.MappingProxyType, so attributes cannot be changed through the __dict__. Setting or deleting __dict__ is also blocked. The only way to change read only properties is through changing the methods of the class itself.

除非您编写库代码,否则将代码作为代码分发给其他人以用于增强他们的程序,而不是用于任何其他目的(如应用程序开发)的代码,否则设置只读属性是没有意义的。__dict__ 问题解决了,因为 __dict__ 现在是不可变类型的MappingProxyType,因此无法通过 __dict__ 更改属性。设置或删除 __dict__ 也被阻止。更改只读属性的唯一方法是更改​​类本身的方法。

Though I believe my solution is better than of the previous two, it could be improved. These are this code's weaknesses:

虽然我相信我的解决方案比前两个更好,但它可以改进。这些是这段代码的弱点:

a) Doesn't allow adding to a method in a subclass which sets or deletes a readonly attribute. A method defined in a subclass is automatically barred from accessing a readonly attribute, even by calling the superclass' version of the method.

a) 不允许添加到设置或删除只读属性的子类中的方法。子类中定义的方法被自动禁止访问只读属性,即使调用超类的方法版本也是如此。

b) The class' readonly methods can be changed to defeat the read only restrictions.

b) 可以更改类的只读方法以打破只读限制。

However, there is not way without editing the class to set or delete a read only attribute. This isn't dependent on naming conventions, which is good because Python isn't so consistent with naming conventions. This provides a way to make read only attributes that cannot be changed with hidden loopholes without editing the class itself. Simply list the attributes to be read only when calling the decorator as arguments and they will become read only.

但是,如果不编辑类就无法设置或删除只读属性。这不依赖于命名约定,这很好,因为 Python 与命名约定不太一致。这提供了一种方法,使只读属性无法在不编辑类本身的情况下通过隐藏的漏洞进行更改。将装饰器作为参数调用时,只需列出要只读的属性,它们就会变为只读。

Credit to Brice's answer in How to get the caller class name inside a function of another class in python?for getting the caller classes and methods.

归功于 Brice 在How to get the caller class name inside a function in another class in python 中的回答用于获取调用者类和方法。

回答by ThorSummoner

someone mentioned using a proxy object, I didn't see an example of that so I ended up trying it out, [poorly].

有人提到使用代理对象,我没有看到这样的例子,所以我最终尝试了它,[糟糕]。

/!\ Please prefer class definitions and class constructors if possible

/!\ 如果可能,请优先使用类定义和类构造函数

this code is effectively re-writing class.__new__(class constructor) except worse in every way. Save yourself the pain and do not use this pattern if you can.

这段代码实际上是重写class.__new__(类构造函数),除了在各方面都更糟。避免自己的痛苦,如果可以,请不要使用这种模式。

def attr_proxy(obj):
    """ Use dynamic class definition to bind obj and proxy_attrs.
        If you can extend the target class constructor that is 
        cleaner, but its not always trivial to do so.
    """
    proxy_attrs = dict()

    class MyObjAttrProxy():
        def __getattr__(self, name):
            if name in proxy_attrs:
                return proxy_attrs[name]  # overloaded

            return getattr(obj, name)  # proxy

        def __setattr__(self, name, value):
            """ note, self is not bound when overloading methods
            """
            proxy_attrs[name] = value

    return MyObjAttrProxy()


myobj = attr_proxy(Object())
setattr(myobj, 'foo_str', 'foo')

def func_bind_obj_as_self(func, self):
    def _method(*args, **kwargs):
        return func(self, *args, **kwargs)
    return _method

def mymethod(self, foo_ct):
    """ self is not bound because we aren't using object __new__
        you can write the __setattr__ method to bind a self 
        argument, or declare your functions dynamically to bind in 
        a static object reference.
    """
    return self.foo_str + foo_ct

setattr(myobj, 'foo', func_bind_obj_as_self(mymethod, myobj))