如何限制在构造函数之外设置属性?

问题描述 投票:0回答:6

我想禁止在初始化后对类的某些属性进行进一步赋值。例如;在 Person 实例“p”初始化之后,没有人可以显式地为“ssn”(社会安全号码)属性分配任何值。在 _init_ 方法中赋值时也会调用 _setattr_ ,因此这不是我想要的。我只想限制进一步的作业。我怎样才能实现这一目标?

class Person(object):
    def __init__(self, name, ssn):
        self.name = name
        self._ssn = ssn

    def __setattr__(self, name, value):
        if name == '_ssn':
            raise AttributeError('Denied.')
        else:
            object.__setattr__(self, name, value)

>> p = Person('Ozgur', '1234')
>> AttributeError: Denied.
python variable-assignment setattr
6个回答
18
投票

通常的方法是使用以下划线开头的“私有”属性,以及用于公共访问的只读属性:

import operator

class Person(object):
    def __init__(self, name, ssn):
        self.name = name
        self._ssn = ssn
    ssn = property(operator.attrgetter("_ssn"))

请注意,这并不真正妨碍任何人更改属性

_ssn
,但前导
_
记录了该属性是私有的。


7
投票

Python 不支持私有或受保护的属性。您需要改为实现描述符协议。标准库提供了装饰器来简洁地做到这一点。

只需在 init 方法中声明前面有两个下划线的属性即可。它称为名称修改,并阻止通过 __ssn 访问该属性,尽管在这种情况下仍然可以通过 _Person__ssn 访问和修改该属性。但是,如果您没有显式地为其定义 setter,则会引发 AttributeError。

当然,如果有人有意滥用 API,只要他非常有意图,他就可以滥用 API。但这不会偶然发生。

import re

class Person:
    """Encapsulates the private data of a person."""

    _PATTERN = re.COMPILE(r'abcd-efgh-ssn')

    def __init__(self, name, ssn):
       """Initializes Person class with input name of person and
       his social security number (ssn).
       """
       # you can add some type and value checking here for defensive programming
       # or validation for the ssn using regex for example that raises an error
       if not self._PATTERN.match(ssn):
           raise ValueError('ssn is not valid')
       self.__name = name
       self.__ssn = snn

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

    @name.setter
    def name(self, value):
        self.__name = value

    @property
    def ssn(self):
        return self.__ssn

>>> p = Person('aname', 'abcd-efgh-ssn')
>>> p.ssn
'abcd-efgh-ssn'
>>> p.ssn = 'mistake'
AttributeError: 'can't set attribute'

6
投票

只是指出我们仍然可以修改

_ssn

对象具有特殊属性,

__dict__
,它是一个字典,将对象的所有实例属性与其相应的值映射。我们可以通过修改对象的
__dict__
属性直接添加/更新/删除实例属性。

我们仍然可以像这样修改

_snn

p = Person('Ozgur', '1234')

p.__dict__.get('_ssn') # returns '1234'

p.__dict__['_ssn'] = '4321'

p.__dict__.get('_ssn') # returns '4321'

正如我们所看到的,我们仍然能够更改

_ssn
的值。根据设计,没有一种直接的方法(如果有的话)可以在所有情况下规避 Python 中的属性访问。

我将展示一种使用 property() 作为装饰器来限制属性访问的更常见方法:

class Person(object):
    def __init__(self, name, ssn):
        self.name = name
        self._ssn = ssn

    @property
    def ssn(self):
        return self._ssn

    @ssn.setter
    def ssn(self, value):
        raise AttributeError('Denied')


>> p = Person('Ozgur', '1234')
>> p.ssn
>> '1234'
>> p.ssn = '4321'
>> AttributeError: Denied

希望这有帮助!


2
投票

您可以通过直接在构造函数中分配给实例字典来绕过setattr。当然,这个技巧总是可以用来欺骗您用来使 _ssn 只读的任何其他环(在初始写入之后)

 class Person(object): 
    def __init__(self, name, ssn):    
        self.name = name
        self.__dict__['_ssn'] = ssn 

    def __setattr__(self, name, value):
        if name == '_ssn':
            raise AttributeError('Denied.')
        else:
            object.__setattr__(self, name, value)

0
投票

问题📖

在 Python 中,您不能创建真正的只读属性,但您可以使其修改变得更困难

即使使用 Python,修改属性也会成为一件真正令人头痛的事情。这是食谱。即使您愿意进入 C 扩展级别,仍然可以对属性进行修改,但它们需要使用 C 指针。

解决方案🍝

from types import MappingProxyType as Proxy
class Person:
    """A person with two read-only attributes.
    
    Setting __slots__ will prevent adding new attributes, but __slots__ itself
    can still be modified. Custom __setattr__ will prevent direct modification
    of __slots__ while custom __getattr__ will direct attribute lookup towards
    the real keys hiding behind the proxy attribute. Then, MappingProxyType is
    used to prevent easily modifying the proxy attribute even when accessed by
    using object.__getattribute__ and object.__setattr__ to bypass the methods
    defined in 'read-only' class. Normal 'self.proxy' can not be used here due
    to the __getattr__ and __setattr__ preventing it.
    """
    __slots__ = ("proxy")
    def __init__(self, name, ssn):
        object.__setattr__(self, "proxy", Proxy({"name": name, "ssn": ssn}))
    def __getattr__(self, name):
        proxy = object.__getattribute__(self, "proxy")
        if (name == "proxy") or (name not in proxy):
            return object.__getattribute__(self, name)
        return proxy[name]
    def __setattr__(self, _name, _val):
        raise AttributeError("Read-only object")

示例🖥️

>>> person = Person("Joe", 123)
>>>
>>> person.name, person.ssn
('Joe', 123)
>>>
>>> person.name, person.ssn = "Niinistö", 1337
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 22, in __setattr__
AttributeError: Read-only object
>>>
>>> 
>>> person.fake_attribute = 1337
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 22, in __setattr__
AttributeError: Read-only object
>>>

0
投票

我在构造函数中使用了一个打开然后关闭的布尔标志。这样做的效果是只允许 setter 在实例化期间工作。显然,恶意行为者可以轻松绕过它,但它会阻止 ssn 的意外重新分配:

class Person():
    def __init__(self, name, ssn):
        self.constructing = True
        self.name = name
        self.ssn = ssn
        self.constructing = False

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

    @name.setter
    def name(self, name):
        self._name = name

    @property
    def ssn(self):
        return self._ssn

    @ssn.setter
    def ssn(self, ssn):
        if not self.constructing:
            raise ValueError("Cannot change social security number")
        self._ssn = ssn
© www.soinside.com 2019 - 2024. All rights reserved.