在Python中可以防止修改对象吗?

14 投票
3 回答
20416 浏览
提问于 2025-04-16 04:08

我想要控制全局变量(或者说是全局范围的变量),让它们在程序初始化的时候只被设置一次,之后就不能再改了。

我用大写字母来命名全局变量,比如UPPER_CASE_VARIABLES,但我想要一个确保不会再改变这些变量的方法。

  • Python有没有提供这样的功能(或者类似的功能)?
  • 你们是怎么控制全局范围的变量的?

3 个回答

2

你可以把全局变量放在一个对象里,然后重写 object.__setattr__ 这个方法。这样一来,你就可以阻止已经设置过的属性被再次设置。不过,这样做对那些通过引用传递的复杂对象就没办法处理了。为了确保这些对象不能被修改,你需要对它们进行浅拷贝或深拷贝。如果你使用的是新式类,可以重写 object.__getattribute__(self, name) 来进行拷贝。

class State(object):
    def __init__(self):
        pass

    def __setattr__(self, name, value):
        if name not in self.__dict__:
            self.__dict__[name] = value

** 我通常不太担心有人会拼命想要破坏我的代码。我发现重写 __setattr__ 就足够了(特别是如果你抛出一个异常),这样可以警告那些玩代码的人,State 的目标是只读的。如果有人还是觉得需要修改状态,那他们遇到的任何未定义行为就不是我的责任了。

8

Python是一种非常开放的编程语言,它没有像其他语言那样的final关键字。Python给你更多的自由去做事情,同时也假设你知道这些事情应该怎么运作。因此,使用你代码的人会被认为知道SOME_CONSTANT这个常量不应该在代码的某个随机地方被赋值。

如果你真的想要限制它,你可以把常量放在一个获取函数里面。

def getConstant()
  return "SOME_VALUE"
19

ActiveState 有一个名为 Cᴏɴsᴛᴀɴᴛs ɪɴ Pʏᴛʜᴏɴ 的食谱,是由著名的 Alex Martelli 编写的,内容是如何创建一个 const 模块,这个模块里的属性在创建后不能被重新绑定。听起来这正是你想要的,除了名字是大写的——不过你可以通过检查属性名是否全是大写来实现这一点。

当然,如果有人下定决心,还是可以绕过这个限制,但这就是 Python 的特点——大多数人认为这是“好事”。不过,为了让事情变得稍微复杂一点,我建议你不要添加那个看起来很明显的 __delattr__ 方法,因为这样人们就可以删除属性名,然后再把它们绑定到不同的值上。

我说的就是这个:

把以下内容放入 const.py

# from http://code.activestate.com/recipes/65207-constants-in-python
class _const:
    class ConstError(TypeError): pass  # Base exception class.
    class ConstCaseError(ConstError): pass

    def __setattr__(self, name, value):
        if name in self.__dict__:
            raise self.ConstError("Can't change const.%s" % name)
        if not name.isupper():
            raise self.ConstCaseError('const name %r is not all uppercase' % name)
        self.__dict__[name] = value

# Replace module entry in sys.modules[__name__] with instance of _const
# (and create additional reference to it to prevent its deletion -- see
#  https://stackoverflow.com/questions/5365562/why-is-the-value-of-name-changing-after-assignment-to-sys-modules-name)
import sys
_ref, sys.modules[__name__] = sys.modules[__name__], _const()

if __name__ == '__main__':
    import __main__  as const  # Test this module...

    try:
        const.Answer = 42  # Not OK to create mixed-case attribute name.
    except const.ConstCaseError as exc:
        print(exc)
    else:  # Test failed - no ConstCaseError exception generated.
        raise RuntimeError("Mixed-case const names should't be allowed!")

    try:
        const.ANSWER = 42  # Should be OK, all uppercase.
    except Exception as exc:
        raise RuntimeError("Defining a valid const attribute should be allowed!")
    else:  # Test succeeded - no exception generated.
        print('const.ANSWER set to %d raised no exception' % const.ANSWER)

    try:
        const.ANSWER = 17  # Not OK, attempt to change defined constant.
    except const.ConstError as exc:
        print(exc)
    else:  # Test failed - no ConstError exception generated.
        raise RuntimeError("Shouldn't be able to change const attribute!")

输出:

const name 'Answer' is not all uppercase
const.ANSWER set to 42 raised no exception
Can't change const.ANSWER

撰写回答