【问题标题】:How to restrict setting an attribute outside of constructor?如何限制在构造函数之外设置属性?
【发布时间】:2012-06-11 08:12:08
【问题描述】:

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

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.

【问题讨论】:

  • 只是因为__init__ 中的作业也调用了__setattr__
  • 我认为您可能不得不尝试使用元类
  • 仅供参考,这并不是 Python 的真正工作方式。记录您的 API 以告诉其他开发人员不要更改它。
  • @detly:Python 中通常的理念是采取措施防止意外违反合同,而不是费心防止故意违约。
  • @SvenMarnach - 真;由 OP 将规则应用于他或她的情况。

标签: python variable-assignment setattr


【解决方案1】:

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

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

当然,如果有人有滥用 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'

【讨论】:

    【解决方案2】:

    只是指出我们仍然可以修改_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
    

    希望这会有所帮助!

    【讨论】:

      【解决方案3】:

      你可以通过在构造函数中直接赋值给实例字典来绕过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)
      

      【讨论】:

        【解决方案4】:

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

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

        请注意,这并不真正妨碍任何人更改属性_ssn,但前面的_ 记录了该属性是私有的。

        【讨论】:

        • +1。 Python 的哲学是,不是语言强制“你不能碰这个”,而是使用以_ 开头的属性来表示实现细节——这意味着客户端可以尝试摆弄它,但是它们不再被您对向后/向前兼容性或类不变量所做的任何保证所涵盖。所以,他们可以摆弄,他们可以破坏东西,但这是他们自己的错。
        猜你喜欢
        • 2023-04-02
        • 1970-01-01
        • 2016-04-25
        • 2012-11-29
        • 1970-01-01
        • 2019-11-04
        • 2010-10-22
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多