【问题标题】:Raising an exception on updating a 'constant' attribute in python在 python 中更新“常量”属性时引发异常
【发布时间】:2010-11-24 10:17:14
【问题描述】:

由于python没有常量的概念,如果更新'constant'属性是否可以引发异常?如何?

class MyClass():
    CLASS_CONSTANT = 'This is a constant'
    var = 'This is a not a constant, can be updated'

#this should raise an exception    
MyClass.CLASS_CONSTANT = 'No, this cannot be updated, will raise an exception'

#this should not raise an exception    
MyClass.var = 'updating this is fine'

#this also should raise an exception    
MyClass().CLASS_CONSTANT = 'No, this cannot be updated, will raise an exception'

#this should not raise an exception    
MyClass().var = 'updating this is fine'

任何将 CLASS_CONSTANT 更改为类属性或实例属性的尝试都应该引发异常。

将 var 更改为类属性或实例属性不应引发异常。

【问题讨论】:

  • Alex Martelli 和 Ants Aasma 的解决方案都有效。我选择了 Alex Martelli 的解决方案,因为它可以在子类中使用,但一些开发人员可能也更喜欢 Ants Aasma 的风格。

标签: python exception attributes constants


【解决方案1】:

在每个类中自定义__setattr__(例如,如@ainab 的答案所指向的我的旧配方和其他答案所示),只能停止分配给实例属性而不是类属性。因此,现有的答案都不能真正满足您所说的要求。

如果您要求的正是您想要的,您可以使用一些自定义元类和描述符的组合,例如:

class const(object):
  def __init__(self, val): self.val = val
  def __get__(self, *_): return self.val
  def __set__(self, *_): raise TypeError("Can't reset const!")

class mcl(type):
  def __init__(cls, *a, **k):
    mkl = cls.__class__
    class spec(mkl): pass
    for n, v in vars(cls).items():
      if isinstance(v, const):
        setattr(spec, n, v)
    spec.__name__ = mkl.__name__
    cls.__class__ = spec

class with_const:
  __metaclass__ = mcl

class foo(with_const):
  CLASS_CONSTANT = const('this is a constant')

print foo().CLASS_CONSTANT
print foo.CLASS_CONSTANT
foo.CLASS_CONSTANT = 'Oops!'
print foo.CLASS_CONSTANT

这是非常高级的东西,因此您可能更喜欢其他答案中建议的更简单的__setattr__ 方法,尽管它不符合您所述的要求(即,您可能会合理地选择削弱您的要求以获得简单性;- )。但是这里的技术可能仍然很有趣:自定义描述符类型const 是另一种方式(恕我直言,比在每个需要一些常量并使所有属性常量而不是挑选和选择的类中覆盖__setattr__ 要好得多...... ) 阻止分配给实例属性;其余的代码是关于一个自定义元类,它创建自己独特的每类子元类,以便充分利用所述自定义描述符并实现您特别要求的确切功能。

【讨论】:

  • 完美!我非常喜欢你对 py 问题的回答。我希望 SO 有一个邀请功能,所以我可以 ping 你我的 py 问题。谢谢。
【解决方案2】:

你可以这样做: (来自http://www.siafoo.net/snippet/108

class Constants:
  # A constant variable
  foo = 1337

  def __setattr__(self, attr, value):
    if hasattr(self, attr):
      raise ValueError, 'Attribute %s already has a value and so cannot be written to' % attr
    self.__dict__[attr] = value

然后像这样使用它:

>>> const = Constants()
>>> const.test1 = 42
>>> const.test1
42
>>> const.test1 = 43
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 4, in __setattr__
ValueError: Attribute test1 already has a value and so cannot be written to
>>> const.test1
42

【讨论】:

  • &gt;&gt;&gt; Constants.foo = 45; &gt;&gt;&gt; Constants.foo 45
  • 是的,显然类上的 setattr 只保护对实例属性的访问,而不是类属性。因此,正如 EPiddy 建议的那样,您基本上需要确保您使用的对象是类的实例而不是类对象本身。
【解决方案3】:

您可以使用元类来实现:

class ImmutableConstants(type):
    def __init__(cls, name, bases, dct):
        type.__init__(cls, name, bases, dct)

        old_setattr = cls.__setattr__
        def __setattr__(self, key, value):
            cls.assert_attribute_mutable(key)
            old_setattr(self, key, value)
        cls.__setattr__ = __setattr__

    def __setattr__(self, key, value):
        self.assert_attribute_mutable(key)
        type.__setattr__(self, key, value)

    def assert_attribute_mutable(self, name):
        if name.isupper():
            raise AttributeError('Attribute %s is constant' % name)

class Foo(object):
    __metaclass__ = ImmutableConstants
    CONST = 5
    class_var = 'foobar'

Foo.class_var = 'new value'
Foo.CONST = 42 # raises

但是你确定这是一个真正的问题吗?你真的不小心到处设置常量吗?您可以通过grep -r '\.[A-Z][A-Z0-9_]*\s*=' src/ 轻松找到其中的大部分内容。

【讨论】:

  • Foo().CONST = 42 不引发?
  • 是的,您可以覆盖实例上的常量。如果这是一个问题,元类初始化器可以为类安装一个类似的 setattr。我没有尝试让它完整,因为我不明白为什么它是必要的。通过静态分析(例如 grep)很容易发现错误,如果没有解释器中适当的沙盒功能,就不可能确保其安全。即使您使用 C 扩展来创建常量,您也可以使用 ctypes 覆盖内存位置,或者如果您使用外部 const 服务,则覆盖服务位置。
  • 我计划选择“Alex Martelli”答案,因为我可以从 with_const 子类化并使用 const() [ class foo(with_const):],请参阅他的解决方案了解详细信息。但我认为这是风格问题,其他一些开发人员可能更喜欢你的风格。我希望你能在你的回答中解决“Foo().CONST 不会引发”的问题,这样如果有人使用你的解决方案,他们就不会遇到这个问题。我认为您的回答有道理,如果您的解决方案解决了问题,我想投赞成票。
  • 顺便说一句,我是 python 的初学者,我相信你在之前的评论中提出了有效的观点(这超出了我目前对 python 的理解)。我一直在寻找该问题的 python 级别解决方案,主要是为了避免与我一起工作的其他绝对新手意外覆盖其值不应更改的变量。感谢您的贡献。您的解决方案和 Alex Martelli 的解决方案都有助于拓宽我对 python 的了解。
  • 添加了对实例的检查,并通过子类化元类使常量检查可定制。如果您的意图是捕获无意的错误,我仍然认为最好只搜索代码以直接分配给常量。如果您愿意,可以绕过我的或 Alex 的检查,这些检查会给您的代码带来复杂性和开销。而静态检查将复杂性分开并在提交时而不是运行时显示错误。
【解决方案4】:

如果您真的想拥有无法更改的常量,请查看:http://code.activestate.com/recipes/65207/

【讨论】:

    【解决方案5】:

    开始阅读:

    http://docs.python.org/reference/datamodel.html#customizing-attribute-access

    您基本上编写了自己的__setattr__ 版本,它会为某些属性抛出异常,但不会为其他属性抛出异常。

    【讨论】:

    • 仍然可以通过访问:MyClass.CLASS_CONSTANT 来更新属性。我认为这是 OP 的问题。
    • “可能”与 OP 发布的具体示例不同。除非涉及的程序员是恶意的反社会者,否则“可能”不是问题。它被记录为常数;在单元测试之外发现诚实的错误是这样做的唯一目的。
    猜你喜欢
    • 2021-10-04
    • 2016-03-18
    • 1970-01-01
    • 2011-09-10
    • 1970-01-01
    • 1970-01-01
    • 2019-07-15
    • 2014-06-10
    • 2020-08-18
    相关资源
    最近更新 更多