【问题标题】:How do I create a constant in Python?如何在 Python 中创建常量?
【发布时间】:2011-02-10 14:00:07
【问题描述】:

有没有办法在 Python 中声明一个常量?在 Java 中,我们可以通过这种方式创建常量值:

public static final String CONST_NAME = "Name";

上面的 Java 常量声明在 Python 中的等价物是什么?

【问题讨论】:

  • 实际上可以通过python的property函数/装饰器来制作只读变量。 invanswer 是自定义用法的一个示例。属性比这更通用,不过,Shalabh Chaturvedi 的 Python Attributes and Methods 对其工作原理进行了很好的分析。
  • 恕我直言,强制恒定性是“不是 pythonic”。在 Python 2.7 中,您甚至可以编写 True=False,然后 (2+2==4)==True 返回 False
  • 正如其他答案所暗示的那样,没有办法或不需要声明常量。但是您可以阅读PEP 关于约定的内容。例如THIS_IS_A_CONSTANT
  • @osa:你不能在 python 3 中做到这一点 - SyntaxError: can't assign to keyword。这似乎是一件好事。
  • 很惊讶直到现在还没有提到这一点,但Enums 似乎是定义枚举常量的好方法。

标签: python constants


【解决方案1】:

不,没有。您不能在 Python 中将变量或值声明为常量。只是不要改变它。

如果您在课堂上,则相当于:

class Foo(object):
    CONST_NAME = "Name"

如果没有,那只是

CONST_NAME = "Name"

但您可能想看看 Alex Martelli 的代码 sn-p Constants in Python


从 Python 3.8 开始,有一个 typing.Final 变量注释将告诉静态类型检查器(如 mypy)不应重新分配您的变量。这与 Java 的 final 最接近。但是,它实际上并不能阻止重新分配

from typing import Final

a: Final = 1

# Executes fine, but mypy will report an error if you run mypy on this:
a = 2

【讨论】:

  • 在 emacs 中 mypy 没有为 :Final 重新分配提供任何符号。我应该为它进行任何配置设置吗?
  • 对初学者的建议:了解为什么不变性是一种代码质量机制。对于那些认为 Python 中缺少常量没有问题的高级程序员,也可以这样做。
  • 考虑像这样添加显式类型:a: Final[int] = 1
【解决方案2】:

在 Python 中,人们使用命名约定而不是强制执行某些东西的语言,例如,__method 用于 private 方法,_method 用于 protected 方法。

因此,您可以以同样的方式简单地将常量声明为全部大写,例如:

MY_CONSTANT = "one"

如果你希望这个常量永远不会改变,你可以挂钩到 attribute 访问并做一些技巧,但更简单的方法是声明一个函数:

def MY_CONSTANT():
    return "one"

唯一的问题是你必须在任何地方做MY_CONSTANT(),但MY_CONSTANT = "one" 是 Python 中的正确方法(通常)。


您也可以使用namedtuple() 创建常量:

>>> from collections import namedtuple
>>> Constants = namedtuple('Constants', ['pi', 'e'])
>>> constants = Constants(3.14, 2.718)
>>> constants.pi
3.14
>>> constants.pi = 3
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: can't set attribute

【讨论】:

  • 使用def MY_CONSTANT(): return "one" 不会阻止方法引用被重新分配,对吧?这不正是鸭子打字的工作原理吗?
【解决方案3】:

声明“常量”的 Pythonic 方式基本上是一个模块级变量:

RED = 1
GREEN = 2
BLUE = 3

然后编写你的类或函数。由于常量几乎总是整数,而且它们在 Python 中也是不可变的,所以你几乎没有机会改变它。

当然,除非您明确设置RED = 2

【讨论】:

  • 是的,但是阻止“显式设置RED = 2”的能力是能够将变量名声明为“常量”的全部好处(在其他语言中) “!
  • 阻止它对你有好处吗? const 最有用的东西通常是编译器优化,这在 Python 中并不是真正的东西。想要一些东西保持不变?只是不要改变它。如果您担心其他人更改它,您可以将其置于他们的范围之外,或者只是意识到,如果有人在更改它,那是他们的问题,他们需要处理它,而不是您。
  • @Kevin:“你会得到什么好处...”,static 的好处是为一个类的所有实例的值提供一个单一的存储空间?除非确实有可能声明一个静态/类变量。
  • 根本问题是,有些人可能将其视为真理来源的值,无法更改,并在整个代码中将其用作真理来源,而不是引入魔法值(我在 Python 中看到了很多)——其他人可能会将其视为允许随意更改的东西。当有人更改了一个全局变量,而你不知道它是从哪里更改的,并且应用程序因为 RED="blue" 而不是 "red" 而崩溃时,您正在引入一个完全不必要的问题,而这个问题已经如此简单地解决了被普遍理解。
  • 阻止它对你有好处吗?” 这是不回答语言怪异的pythonic方式:你应该永远不会犯错误,那么为什么要添加约束呢?为什么要像其他语言一样在块周围添加括号?您只需要永远不要错误地删除选项卡。这就是优秀的程序员所做的。如果你不能,那么你就不是一个好的程序员,你应该使用Java。拥有常量的一个明显优势是您的代码检查器能够告诉您何时尝试为常量分配值(代码检查器被糟糕的程序员使用)。
【解决方案4】:

没有像其他语言那样的const 关键字,但是可以创建一个具有“getter function”的属性来读取数据,但没有“setter function” 重新写入数据。 这基本上可以防止标识符被更改。

这是使用类属性的替代实现:

请注意,对于想了解常量的读者来说,代码远非易事。请参阅下面的说明

def constant(f):
    def fset(self, value):
        raise TypeError
    def fget(self):
        return f()
    return property(fget, fset)

class _Const(object):
    @constant
    def FOO():
        return 0xBAADFACE
    @constant
    def BAR():
        return 0xDEADBEEF

CONST = _Const()

print CONST.FOO
##3131964110

CONST.FOO = 0
##Traceback (most recent call last):
##    ...
##    CONST.FOO = 0
##TypeError: None

代码说明:

  1. 定义一个函数constant,它接受一个表达式,并使用它来构造一个“getter”——一个只返回表达式值的函数。
  2. setter 函数引发 TypeError,因此它是只读的
  3. 使用我们刚刚创建的constant 函数作为修饰来快速定义只读属性。

还有其他一些更老式的方式:

(代码比较棘手,下面有更多解释)

class _Const(object):
    @apply
    def FOO():
        def fset(self, value):
            raise TypeError
        def fget(self):
            return 0xBAADFACE
        return property(**locals())

CONST = _Const()

print CONST.FOO
##3131964110

CONST.FOO = 0
##Traceback (most recent call last):
##    ...
##    CONST.FOO = 0
##TypeError: None

请注意,@apply 装饰器似乎已被弃用。

  1. 要定义标识符 FOO,首先定义两个函数(fset、fget - 名称由我选择)。
  2. 然后使用内置的property函数构造一个可以“set”或“get”的对象。
  3. 注意property 函数的前两个参数被命名为fsetfget
  4. 使用我们为自己的 getter 和 setter 选择这些名称的事实,并使用应用于该范围的所有本地定义的 **(双星号)创建关键字字典,以将参数传递给 property 函数

【讨论】:

    【解决方案5】:

    Python 字典是可变的,因此它们似乎不是声明常量的好方法:

    >>> constants = {"foo":1, "bar":2}
    >>> print constants
    {'foo': 1, 'bar': 2}
    >>> constants["bar"] = 3
    >>> print constants
    {'foo': 1, 'bar': 3}
    

    【讨论】:

      【解决方案6】:

      我会创建一个覆盖基对象类的__setattr__ 方法的类,并用它包装我的常量,注意我使用的是python 2.7:

      class const(object):
          def __init__(self, val):
              super(const, self).__setattr__("value", val)
          def __setattr__(self, name, val):
              raise ValueError("Trying to change a constant value", self)
      

      包装一个字符串:

      >>> constObj = const("Try to change me")
      >>> constObj.value
      'Try to change me'
      >>> constObj.value = "Changed"
      Traceback (most recent call last):
         ...
      ValueError: Trying to change a constant value
      >>> constObj2 = const(" or not")
      >>> mutableObj = constObj.value + constObj2.value
      >>> mutableObj #just a string
      'Try to change me or not'
      

      这很简单,但是如果你想像使用非常量对象一样使用常量(不使用 constObj.value),它会更加密集。这可能会导致问题,因此最好保留 .value 以显示并知道您正在使用常量进行操作(尽管这可能不是最“pythonic”的方式)。

      【讨论】:

      • +1 表示有趣的方法。虽然不如已经提供的答案那么干净。即使是最简单的早期建议解决方案def ONE(): return 1 也比这个答案ONE.value 更容易使用ONE()
      【解决方案7】:

      扩展 Raufio 的答案,添加 __repr__ 以返回值。

      class const(object):
          def __init__(self, val):
              super(const, self).__setattr__("value", val)
          def __setattr__(self, name, val):
              raise ValueError("Trying to change a constant value", self)
          def __repr__(self):
              return ('{0}'.format(self.value))
      
      dt = const(float(0.01))
      print dt
      

      那么该对象的行为会更像您所期望的那样,您可以直接访问它而不是 '.value'

      【讨论】:

      • 没有。结果不是一个常数。 dt = 5 被接受,没有投诉。在 Raufio 的回答中,虽然也可以覆盖它,但结果将导致对下一次使用 dt.value 的投诉。一个不太危险的失败也是如此。你已经取消了他的解决方案的好处。
      【解决方案8】:

      除了两个最重要的答案(只需使用带有大写名称的变量,或使用属性将值设为只读),我想提一下,可以使用元类来实现 named em> 常量。我在GitHub 提供了一个使用元类的非常简单的解决方案,如果您希望这些值能够提供有关其类型/名称的更多信息,这可能会有所帮助:

      >>> from named_constants import Constants
      >>> class Colors(Constants):
      ...     black = 0
      ...     red = 1
      ...     white = 15
      ...
      >>> c = Colors.black
      >>> c == 0
      True
      >>> c
      Colors.black
      >>> c.name()
      'black'
      >>> Colors(0) is c
      True
      

      这是稍微高级一点的 Python,但仍然非常易于使用和方便。 (该模块还有更多功能,包括只读常量,请参阅它的 README。)

      在各种存储库中都有类似的解决方案,但据我所知,它们要么缺乏我期望从常量中获得的基本特征之一(例如常量,或者是任意类型),要么它们具有深奥添加的功能使其不太适用。但是 YMMV,我将不胜感激。 :-)

      【讨论】:

        【解决方案9】:

        这是一个“常量”类的实现,它创建具有只读(常量)属性的实例。例如。可以使用Nums.PI获取已经初始化为3.14159的值,Nums.PI = 22引发异常。

        # ---------- Constants.py ----------
        class Constants(object):
            """
            Create objects with read-only (constant) attributes.
            Example:
                Nums = Constants(ONE=1, PI=3.14159, DefaultWidth=100.0)
                print 10 + Nums.PI
                print '----- Following line is deliberate ValueError -----'
                Nums.PI = 22
            """
        
            def __init__(self, *args, **kwargs):
                self._d = dict(*args, **kwargs)
        
            def __iter__(self):
                return iter(self._d)
        
            def __len__(self):
                return len(self._d)
        
            # NOTE: This is only called if self lacks the attribute.
            # So it does not interfere with get of 'self._d', etc.
            def __getattr__(self, name):
                return self._d[name]
        
            # ASSUMES '_..' attribute is OK to set. Need this to initialize 'self._d', etc.
            #If use as keys, they won't be constant.
            def __setattr__(self, name, value):
                if (name[0] == '_'):
                    super(Constants, self).__setattr__(name, value)
                else:
                    raise ValueError("setattr while locked", self)
        
        if (__name__ == "__main__"):
            # Usage example.
            Nums = Constants(ONE=1, PI=3.14159, DefaultWidth=100.0)
            print 10 + Nums.PI
            print '----- Following line is deliberate ValueError -----'
            Nums.PI = 22
        

        感谢@MikeGraham 's FrozenDict,我以此为起点。已更改,因此使用语法不是Nums['ONE'],而是Nums.ONE

        感谢@Raufio 的回答,提供了覆盖 __ setattr __ 的想法。

        或者对于具有更多功能的实现,请参阅 @Hans_meine 的 named_constants at GitHub

        【讨论】:

        • Python 是成年人同意的语言。没有针对此类事情的保护措施。 Nums._d['PI'] = 22 我相信,该语言本身并没有提供任何将事物标记为非可变的方法。
        【解决方案10】:

        我最近发现了一个非常简洁的更新,它会自动引发有意义的错误消息并阻止通过 __dict__ 进行访问:

        class CONST(object):
            __slots__ = ()
            FOO = 1234
        
        CONST = CONST()
        
        # ----------
        
        print(CONST.FOO)    # 1234
        
        CONST.FOO = 4321              # AttributeError: 'CONST' object attribute 'FOO' is read-only
        CONST.__dict__['FOO'] = 4321  # AttributeError: 'CONST' object has no attribute '__dict__'
        CONST.BAR = 5678              # AttributeError: 'CONST' object has no attribute 'BAR'
        

        我们对自己进行定义,让自己成为一个实例,然后使用槽来确保不能添加额外的属性。这也删除了__dict__ 访问路由。当然,整个对象还是可以重新定义的。

        编辑 - 原始解决方案

        我可能在这里错过了一个技巧,但这似乎对我有用:

        class CONST(object):
            FOO = 1234
        
            def __setattr__(self, *_):
                pass
        
        CONST = CONST()
        
        #----------
        
        print CONST.FOO    # 1234
        
        CONST.FOO = 4321
        CONST.BAR = 5678
        
        print CONST.FOO    # Still 1234!
        print CONST.BAR    # Oops AttributeError
        

        创建实例允许神奇的__setattr__ 方法启动并拦截设置FOO 变量的尝试。如果你愿意,你可以在这里抛出一个异常。通过类名实例化实例可防止直接通过类进行访问。

        对于一个值来说这是一件很痛苦的事情,但是您可以将很多附加到您的 CONST 对象。有上层阶级,类名也显得有些俗套,但我觉得整体还是比较简洁的。

        【讨论】:

          【解决方案11】:

          好吧..即使这已经过时了,让我在这里加上我的 2 美分 :-)

          class ConstDict(dict):
              def __init__(self, *args, **kwargs):
                  super(ConstDict, self).__init__(*args, **kwargs)
          
              def __setitem__(self, key, value):
                  if key in self:
                      raise ValueError("Value %s already exists" % (key))
                  super(ConstDict, self).__setitem__(key, value)
          

          您可以防止发生任何更新,而不是破坏 ValueError。这样做的一个优点是您可以在程序中动态添加常量,但是一旦设置了常量就无法更改。您也可以在设置常量之前添加任何规则或任何内容(在设置键之前,键必须是字符串或小写字符串或大写字符串等)

          但是,我认为在 Python 中设置常量并不重要。不可能像在 C 中那样进行优化,因此我猜这不是必需的。

          【讨论】:

            【解决方案12】:

            在我的例子中,我需要不可变的字节数组来实现一个包含许多我想确保是常量的文字数字的加密库。

            This answer 有效,但尝试重新分配 bytearray 元素不会引发错误。

            def const(func):
                '''implement const decorator'''
                def fset(self, val):
                    '''attempting to set a const raises `ConstError`'''
                    class ConstError(TypeError):
                        '''special exception for const reassignment'''
                        pass
            
                    raise ConstError
            
                def fget(self):
                    '''get a const'''
                    return func()
            
                return property(fget, fset)
            
            
            class Consts(object):
                '''contain all constants'''
            
                @const
                def C1():
                    '''reassignment to C1 fails silently'''
                    return bytearray.fromhex('deadbeef')
            
                @const
                def pi():
                    '''is immutable'''
                    return 3.141592653589793
            

            常量是不可变的,但常量字节数组赋值失败:

            >>> c = Consts()
            >>> c.pi = 6.283185307179586  # (https://en.wikipedia.org/wiki/Tau_(2%CF%80))
            Traceback (most recent call last):
              File "<stdin>", line 1, in <module>
              File "consts.py", line 9, in fset
                raise ConstError
            __main__.ConstError
            >>> c.C1[0] = 0
            >>> c.C1[0]
            222
            >>> c.C1
            bytearray(b'\xde\xad\xbe\xef')
            

            一种更强大、更简单、甚至可能更“pythonic”的方法涉及使用 memoryview 对象(

            import sys
            
            PY_VER = sys.version.split()[0].split('.')
            
            if int(PY_VER[0]) == 2:
                if int(PY_VER[1]) < 6:
                    raise NotImplementedError
                elif int(PY_VER[1]) == 6:
                    memoryview = buffer
            
            class ConstArray(object):
                '''represent a constant bytearray'''
                def __init__(self, init):
                    '''
                    create a hidden bytearray and expose a memoryview of that bytearray for
                    read-only use
                    '''
                    if int(PY_VER[1]) == 6:
                        self.__array = bytearray(init.decode('hex'))
                    else:
                        self.__array = bytearray.fromhex(init)
            
                    self.array = memoryview(self.__array)
            
                def __str__(self):
                    return str(self.__array)
            
                def __getitem__(self, *args, **kwargs):
                   return self.array.__getitem__(*args, **kwargs)
            

            ConstArray 项目分配是TypeError:

            >>> C1 = ConstArray('deadbeef')
            >>> C1[0] = 0
            Traceback (most recent call last):
              File "<stdin>", line 1, in <module>
            TypeError: 'ConstArray' object does not support item assignment
            >>> C1[0]
            222
            

            【讨论】:

              【解决方案13】:

              编辑:添加了 Python 3 的示例代码

              注意:this other answer 看起来提供了一个更完整的实现,类似于以下(具有更多功能)。

              首先,创建一个metaclass

              class MetaConst(type):
                  def __getattr__(cls, key):
                      return cls[key]
              
                  def __setattr__(cls, key, value):
                      raise TypeError
              

              这可以防止静态属性被更改。然后创建另一个使用该元类的类:

              class Const(object):
                  __metaclass__ = MetaConst
              
                  def __getattr__(self, name):
                      return self[name]
              
                  def __setattr__(self, name, value):
                      raise TypeError
              

              或者,如果您使用的是 Python 3:

              class Const(object, metaclass=MetaConst):
                  def __getattr__(self, name):
                      return self[name]
              
                  def __setattr__(self, name, value):
                      raise TypeError
              

              这应该可以防止实例道具被更改。要使用它,请继承:

              class MyConst(Const):
                  A = 1
                  B = 2
              

              现在直接或通过实例访问的道具应该是常量:

              MyConst.A
              # 1
              my_const = MyConst()
              my_const.A
              # 1
              
              MyConst.A = 'changed'
              # TypeError
              my_const.A = 'changed'
              # TypeError
              

              Here's 上面的一个例子。 Here's Python 3 的另一个示例。

              【讨论】:

                【解决方案14】:

                我为 python const 编写了一个 util lib: kkconst - pypi 支持str、int、float、datetime

                const 字段实例将保持其基本类型行为。

                例如:

                from __future__ import print_function
                from kkconst import (
                    BaseConst,
                    ConstFloatField,
                )
                
                class MathConst(BaseConst):
                    PI = ConstFloatField(3.1415926, verbose_name=u"Pi")
                    E = ConstFloatField(2.7182818284, verbose_name=u"mathematical constant")  # Euler's number"
                    GOLDEN_RATIO = ConstFloatField(0.6180339887, verbose_name=u"Golden Ratio")
                
                magic_num = MathConst.GOLDEN_RATIO
                assert isinstance(magic_num, ConstFloatField)
                assert isinstance(magic_num, float)
                
                print(magic_num)  # 0.6180339887
                print(magic_num.verbose_name)  # Golden Ratio
                

                更多详细用法你可以阅读pypi url: pypi github

                【讨论】:

                  【解决方案15】:

                  从技术上讲,元组可以作为常量,因为如果您尝试更改其中一个值,元组会引发错误。如果你想用一个值声明一个元组,那么在它的唯一值之后放置一个逗号,如下所示:

                  my_tuple = (0 """Or any other value""",)
                  

                  要检查此变量的值,请使用类似以下内容:

                  if my_tuple[0] == 0:
                      #Code goes here
                  

                  如果您尝试更改此值,则会引发错误。

                  【讨论】:

                    【解决方案16】:

                    您可以使用命名元组作为一种解决方法来有效地创建一个常量,该常量的工作方式与 Java 中的静态最终变量(Java“常量”)相同。随着变通方法的进行,它有点优雅。 (更优雅的方法是简单地改进 Python 语言 --- 哪种语言可以让您重新定义 math.pi?--- 但我离题了。)

                    (在我写这篇文章的时候,我意识到这个问题的另一个答案提到了 namedtuple,但我会在这里继续,因为我将展示一种更接近于你在 Java 中所期望的语法,因为不需要创建一个命名的 type 作为 namedtuple 强制你做的事情。)

                    按照您的示例,您会记得在 Java 中,我们必须在某个类中定义常量;因为你没有提到类名,所以我们称它为Foo。这是 Java 类:

                    public class Foo {
                      public static final String CONST_NAME = "Name";
                    }
                    

                    这是等效的 Python。

                    from collections import namedtuple
                    Foo = namedtuple('_Foo', 'CONST_NAME')('Name')
                    

                    我想在此处添加的关键点是您不需要单独的 Foo 类型(“匿名命名元组”会很好,即使这听起来像是矛盾修饰语),所以我们将命名元组命名为 @ 987654328@ 这样希望它不会逃到导入模块。

                    这里的第二点是我们立即创建一个nametuple的实例,称之为Foo;无需在单独的步骤中执行此操作(除非您愿意)。现在您可以做您在 Java 中可以做的事情了:

                    >>> Foo.CONST_NAME
                    'Name'
                    

                    但你不能分配给它:

                    >>> Foo.CONST_NAME = 'bar'
                    …
                    AttributeError: can't set attribute
                    

                    致谢:我以为我发明了 namedtuple 方法,但后来我看到其他人给出了类似的(虽然不太紧凑)的答案。然后我还注意到What are "named tuples" in Python?,它指出sys.version_info现在是一个命名元组,所以也许Python标准库很早就提出了这个想法。

                    请注意,不幸的是(这仍然是 Python),您可以完全删除整个 Foo 分配:

                    >>> Foo = 'bar'
                    

                    (捂脸)

                    但至少我们阻止了 Foo.CONST_NAME 值被更改,这总比没有好。祝你好运。

                    【讨论】:

                      【解决方案17】:

                      不幸的是,Python 还没有常量,这很可惜。 ES6 已经为 JavaScript (https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Statements/const) 添加了支持常量,因为它在任何编程语言中都是非常有用的东西。 正如 Python 社区的其他答案中所回答的那样,使用约定 - 用户大写变量作为常量,但它不能防止代码中的任意错误。 如果你喜欢,你可能会发现一个有用的单文件解决方案,下一个 (请参阅文档字符串如何使用它)。

                      文件常量.py

                      import collections
                      
                      
                      __all__ = ('const', )
                      
                      
                      class Constant(object):
                          """
                          Implementation strict constants in Python 3.
                      
                          A constant can be set up, but can not be changed or deleted.
                          Value of constant may any immutable type, as well as list or set.
                          Besides if value of a constant is list or set, it will be converted in an immutable type as next:
                              list -> tuple
                              set -> frozenset
                          Dict as value of a constant has no support.
                      
                          >>> const = Constant()
                          >>> del const.temp
                          Traceback (most recent call last):
                          NameError: name 'temp' is not defined
                          >>> const.temp = 1
                          >>> const.temp = 88
                          Traceback (most recent call last):
                              ...
                          TypeError: Constanst can not be changed
                          >>> del const.temp
                          Traceback (most recent call last):
                              ...
                          TypeError: Constanst can not be deleted
                          >>> const.I = ['a', 1, 1.2]
                          >>> print(const.I)
                          ('a', 1, 1.2)
                          >>> const.F = {1.2}
                          >>> print(const.F)
                          frozenset([1.2])
                          >>> const.D = dict()
                          Traceback (most recent call last):
                              ...
                          TypeError: dict can not be used as constant
                          >>> del const.UNDEFINED
                          Traceback (most recent call last):
                              ...
                          NameError: name 'UNDEFINED' is not defined
                          >>> const()
                          {'I': ('a', 1, 1.2), 'temp': 1, 'F': frozenset([1.2])}
                          """
                      
                          def __setattr__(self, name, value):
                              """Declaration a constant with value. If mutable - it will be converted to immutable, if possible.
                              If the constant already exists, then made prevent againt change it."""
                      
                              if name in self.__dict__:
                                  raise TypeError('Constanst can not be changed')
                      
                              if not isinstance(value, collections.Hashable):
                                  if isinstance(value, list):
                                      value = tuple(value)
                                  elif isinstance(value, set):
                                      value = frozenset(value)
                                  elif isinstance(value, dict):
                                      raise TypeError('dict can not be used as constant')
                                  else:
                                      raise ValueError('Muttable or custom type is not supported')
                              self.__dict__[name] = value
                      
                          def __delattr__(self, name):
                              """Deny against deleting a declared constant."""
                      
                              if name in self.__dict__:
                                  raise TypeError('Constanst can not be deleted')
                              raise NameError("name '%s' is not defined" % name)
                      
                          def __call__(self):
                              """Return all constans."""
                      
                              return self.__dict__
                      
                      
                      const = Constant()
                      
                      
                      if __name__ == '__main__':
                          import doctest
                          doctest.testmod()
                      

                      如果这还不够,请查看完整的测试用例。

                      import decimal
                      import uuid
                      import datetime
                      import unittest
                      
                      from ..constants import Constant
                      
                      
                      class TestConstant(unittest.TestCase):
                          """
                          Test for implementation constants in the Python
                          """
                      
                          def setUp(self):
                      
                              self.const = Constant()
                      
                          def tearDown(self):
                      
                              del self.const
                      
                          def test_create_constant_with_different_variants_of_name(self):
                      
                              self.const.CONSTANT = 1
                              self.assertEqual(self.const.CONSTANT, 1)
                              self.const.Constant = 2
                              self.assertEqual(self.const.Constant, 2)
                              self.const.ConStAnT = 3
                              self.assertEqual(self.const.ConStAnT, 3)
                              self.const.constant = 4
                              self.assertEqual(self.const.constant, 4)
                              self.const.co_ns_ta_nt = 5
                              self.assertEqual(self.const.co_ns_ta_nt, 5)
                              self.const.constant1111 = 6
                              self.assertEqual(self.const.constant1111, 6)
                      
                          def test_create_and_change_integer_constant(self):
                      
                              self.const.INT = 1234
                              self.assertEqual(self.const.INT, 1234)
                              with self.assertRaisesRegexp(TypeError, 'Constanst can not be changed'):
                                  self.const.INT = .211
                      
                          def test_create_and_change_float_constant(self):
                      
                              self.const.FLOAT = .1234
                              self.assertEqual(self.const.FLOAT, .1234)
                              with self.assertRaisesRegexp(TypeError, 'Constanst can not be changed'):
                                  self.const.FLOAT = .211
                      
                          def test_create_and_change_list_constant_but_saved_as_tuple(self):
                      
                              self.const.LIST = [1, .2, None, True, datetime.date.today(), [], {}]
                              self.assertEqual(self.const.LIST, (1, .2, None, True, datetime.date.today(), [], {}))
                      
                              self.assertTrue(isinstance(self.const.LIST, tuple))
                      
                              with self.assertRaisesRegexp(TypeError, 'Constanst can not be changed'):
                                  self.const.LIST = .211
                      
                          def test_create_and_change_none_constant(self):
                      
                              self.const.NONE = None
                              self.assertEqual(self.const.NONE, None)
                              with self.assertRaisesRegexp(TypeError, 'Constanst can not be changed'):
                                  self.const.NONE = .211
                      
                          def test_create_and_change_boolean_constant(self):
                      
                              self.const.BOOLEAN = True
                              self.assertEqual(self.const.BOOLEAN, True)
                              with self.assertRaisesRegexp(TypeError, 'Constanst can not be changed'):
                                  self.const.BOOLEAN = False
                      
                          def test_create_and_change_string_constant(self):
                      
                              self.const.STRING = "Text"
                              self.assertEqual(self.const.STRING, "Text")
                      
                              with self.assertRaisesRegexp(TypeError, 'Constanst can not be changed'):
                                  self.const.STRING += '...'
                      
                              with self.assertRaisesRegexp(TypeError, 'Constanst can not be changed'):
                                  self.const.STRING = 'TEst1'
                      
                          def test_create_dict_constant(self):
                      
                              with self.assertRaisesRegexp(TypeError, 'dict can not be used as constant'):
                                  self.const.DICT = {}
                      
                          def test_create_and_change_tuple_constant(self):
                      
                              self.const.TUPLE = (1, .2, None, True, datetime.date.today(), [], {})
                              self.assertEqual(self.const.TUPLE, (1, .2, None, True, datetime.date.today(), [], {}))
                      
                              with self.assertRaisesRegexp(TypeError, 'Constanst can not be changed'):
                                  self.const.TUPLE = 'TEst1'
                      
                          def test_create_and_change_set_constant(self):
                      
                              self.const.SET = {1, .2, None, True, datetime.date.today()}
                              self.assertEqual(self.const.SET, {1, .2, None, True, datetime.date.today()})
                      
                              self.assertTrue(isinstance(self.const.SET, frozenset))
                      
                              with self.assertRaisesRegexp(TypeError, 'Constanst can not be changed'):
                                  self.const.SET = 3212
                      
                          def test_create_and_change_frozenset_constant(self):
                      
                              self.const.FROZENSET = frozenset({1, .2, None, True, datetime.date.today()})
                              self.assertEqual(self.const.FROZENSET, frozenset({1, .2, None, True, datetime.date.today()}))
                      
                              with self.assertRaisesRegexp(TypeError, 'Constanst can not be changed'):
                                  self.const.FROZENSET = True
                      
                          def test_create_and_change_date_constant(self):
                      
                              self.const.DATE = datetime.date(1111, 11, 11)
                              self.assertEqual(self.const.DATE, datetime.date(1111, 11, 11))
                      
                              with self.assertRaisesRegexp(TypeError, 'Constanst can not be changed'):
                                  self.const.DATE = True
                      
                          def test_create_and_change_datetime_constant(self):
                      
                              self.const.DATETIME = datetime.datetime(2000, 10, 10, 10, 10)
                              self.assertEqual(self.const.DATETIME, datetime.datetime(2000, 10, 10, 10, 10))
                      
                              with self.assertRaisesRegexp(TypeError, 'Constanst can not be changed'):
                                  self.const.DATETIME = None
                      
                          def test_create_and_change_decimal_constant(self):
                      
                              self.const.DECIMAL = decimal.Decimal(13123.12312312321)
                              self.assertEqual(self.const.DECIMAL, decimal.Decimal(13123.12312312321))
                      
                              with self.assertRaisesRegexp(TypeError, 'Constanst can not be changed'):
                                  self.const.DECIMAL = None
                      
                          def test_create_and_change_timedelta_constant(self):
                      
                              self.const.TIMEDELTA = datetime.timedelta(days=45)
                              self.assertEqual(self.const.TIMEDELTA, datetime.timedelta(days=45))
                      
                              with self.assertRaisesRegexp(TypeError, 'Constanst can not be changed'):
                                  self.const.TIMEDELTA = 1
                      
                          def test_create_and_change_uuid_constant(self):
                      
                              value = uuid.uuid4()
                              self.const.UUID = value
                              self.assertEqual(self.const.UUID, value)
                      
                              with self.assertRaisesRegexp(TypeError, 'Constanst can not be changed'):
                                  self.const.UUID = []
                      
                          def test_try_delete_defined_const(self):
                      
                              self.const.VERSION = '0.0.1'
                              with self.assertRaisesRegexp(TypeError, 'Constanst can not be deleted'):
                                  del self.const.VERSION
                      
                          def test_try_delete_undefined_const(self):
                      
                              with self.assertRaisesRegexp(NameError, "name 'UNDEFINED' is not defined"):
                                  del self.const.UNDEFINED
                      
                          def test_get_all_defined_constants(self):
                      
                              self.assertDictEqual(self.const(), {})
                      
                              self.const.A = 1
                              self.assertDictEqual(self.const(), {'A': 1})
                      
                              self.const.B = "Text"
                              self.assertDictEqual(self.const(), {'A': 1, 'B': "Text"})
                      

                      优点: 1.访问整个项目的所有常量 2. 严格控制常量的取值

                      缺乏: 1.不支持自定义类型和类型'dict'

                      注意事项:

                      1. 用 Python3.4 和 Python3.5 测试过(我用的是“毒药”)

                      2. 测试环境:

                      .

                      $ uname -a
                      Linux wlysenko-Aspire 3.13.0-37-generic #64-Ubuntu SMP Mon Sep 22 21:28:38 UTC 2014 x86_64 x86_64 x86_64 GNU/Linux
                      

                      【讨论】:

                      • 您可以通过自动将字典转换为命名元组来稍微改进这一点
                      【解决方案18】:

                      您可以在下一个类的帮助下模拟常量变量。使用示例:

                      # Const
                      const = Const().add(two=2, three=3)
                      
                      print 'const.two: ', const.two
                      print 'const.three: ', const.three
                      
                      const.add(four=4)
                      
                      print 'const.four: ', const.four
                      
                      #const.four = 5 # a error here: four is a constant
                      
                      const.add(six=6)
                      
                      print 'const.six: ', const.six
                      
                      const2 = Const().add(five=5) # creating a new namespace with Const()
                      print 'const2.five: ', const2.five
                      #print 'const2.four: ', const2.four # a error here: four does not exist in const2 namespace
                      
                      const2.add(five=26)
                      

                      当你想启动一个新的常量命名空间时调用构造函数。请注意,当 Martelli 的 const 类不是时,该类受到保护,不会意外修改序列类型常量。

                      来源如下。

                      from copy import copy
                      
                      class Const(object):
                      "A class to create objects with constant fields."
                      
                      def __init__(self):
                          object.__setattr__(self, '_names', [])
                      
                      
                      def add(self, **nameVals):
                          for name, val in nameVals.iteritems():          
                              if hasattr(self, name):
                                  raise ConstError('A field with a name \'%s\' is already exist in Const class.' % name)
                      
                              setattr(self, name, copy(val)) # set up getter
                      
                              self._names.append(name)
                      
                          return self
                      
                      
                      def __setattr__(self, name, val):
                          if name in self._names:
                              raise ConstError('You cannot change a value of a stored constant.')
                      
                          object.__setattr__(self, name, val)
                      

                      【讨论】:

                        【解决方案19】:

                        您可以将一个常量包装在一个 numpy 数组中,将其标记为只写,并始终通过索引零调用它。

                        import numpy as np
                        
                        # declare a constant
                        CONSTANT = 'hello'
                        
                        # put constant in numpy and make read only
                        CONSTANT = np.array([CONSTANT])
                        CONSTANT.flags.writeable = False
                        # alternatively: CONSTANT.setflags(write=0)
                        
                        # call our constant using 0 index    
                        print 'CONSTANT %s' % CONSTANT[0]
                        
                        # attempt to modify our constant with try/except
                        new_value = 'goodbye'
                        try:
                            CONSTANT[0] = new_value
                        except:
                            print "cannot change CONSTANT to '%s' it's value '%s' is immutable" % (
                                new_value, CONSTANT[0])
                        
                        # attempt to modify our constant producing ValueError
                        CONSTANT[0] = new_value
                        
                        
                        
                        >>>
                        CONSTANT hello
                        cannot change CONSTANT to 'goodbye' it's value 'hello' is immutable
                        Traceback (most recent call last):
                          File "shuffle_test.py", line 15, in <module>
                            CONSTANT[0] = new_value
                        ValueError: assignment destination is read-only
                        

                        当然,这只是保护 numpy 的内容,而不是变量“CONSTANT”本身;你仍然可以这样做:

                        CONSTANT = 'foo'
                        

                        并且CONSTANT 会发生变化,但是这会在稍后在脚本中第一次调用CONSTANT[0] 时迅速引发 TypeError。

                        虽然...我想如果您在某个时候将其更改为

                        CONSTANT = [1,2,3]
                        

                        现在你不会再收到 TypeError 了。嗯……

                        https://docs.scipy.org/doc/numpy/reference/generated/numpy.ndarray.setflags.html

                        【讨论】:

                          【解决方案20】:

                          我们可以创建一个描述符对象。

                          class Constant:
                            def __init__(self,value=None):
                              self.value = value
                            def __get__(self,instance,owner):
                              return self.value
                            def __set__(self,instance,value):
                              raise ValueError("You can't change a constant")
                          

                          1) 如果我们想在实例级别使用常量,那么:

                          class A:
                            NULL = Constant()
                            NUM = Constant(0xFF)
                          
                          class B:
                            NAME = Constant('bar')
                            LISTA = Constant([0,1,'INFINITY'])
                          
                          >>> obj=A()
                          >>> print(obj.NUM)  #=> 255
                          >>> obj.NUM =100
                          
                          Traceback (most recent call last):
                          File "<stdin>", line 1, in <module>
                          ValueError: You can't change a constant
                          

                          2) 如果我们只想在类级别创建常量,我们可以使用元类作为常量(我们的描述符对象)的容器;所有下降的类都将继承我们的常量(我们的描述符对象),没有任何可以修改的风险。

                          # metaclass of my class Foo
                          class FooMeta(type): pass
                          
                          # class Foo
                          class Foo(metaclass=FooMeta): pass
                          
                          # I create constants in my metaclass
                          FooMeta.NUM = Constant(0xff)
                          FooMeta.NAME = Constant('FOO')
                          
                          >>> Foo.NUM   #=> 255
                          >>> Foo.NAME  #=> 'FOO'
                          >>> Foo.NUM = 0 #=> ValueError: You can't change a constant
                          

                          如果我创建 Foo 的子类,这个类将继承该常量而不能修改它们

                          class Bar(Foo): pass
                          
                          >>> Bar.NUM  #=> 255
                          >>> Bar.NUM = 0  #=> ValueError: You can't change a constant
                          

                          【讨论】:

                          • 赞成这个,因为这个答案实际上解决了原始问题的“静态”部分,并提供了一种简洁的方法来使用元类声明基于类的常量,而不是像其他的实例级常量答案。对我来说更有意义。
                          【解决方案21】:

                          属性是创建常量的一种方式。你可以通过声明一个 getter 属性来做到这一点,但忽略 setter。例如:

                          class MyFinalProperty(object):
                          
                              @property
                              def name(self):
                                  return "John"
                          

                          您可以查看an article I've written 以找到更多使用 Python 属性的方法。

                          【讨论】:

                          • 价值不足的解决方案。我在找到这个页面(不是这个答案)之后才实现了这个,如果还没有的话,我会转回来添加它。我想强调这个答案的有用性。
                          【解决方案22】:

                          如果你想要常量而不关心它们的值,这里有一个技巧:

                          只需定义空类。

                          例如:

                          class RED: 
                              pass
                          class BLUE: 
                              pass
                          

                          【讨论】:

                            【解决方案23】:

                            Python 没有常量。

                            也许最简单的选择是为它定义一个函数:

                            def MY_CONSTANT():
                                return 42
                            

                            MY_CONSTANT() 现在拥有常量的所有功能(加上一些烦人的大括号)。

                            【讨论】:

                            • 我只是想添加这个建议,但幸运的是我向下滚动到了低评价的答案。我希望它会得到进一步的支持,我完全同意它具有常量的所有功能,并且非常简单明了。查看所有复杂解决方案中的样板代码量,我发现大括号相对不烦人。
                            • 这是最简单的答案,尽管应该注意它有一些开销并且不会阻止白痴修改返回值。它只会阻止代码进一步更改源代码
                            • @MrMesees 修改返回值?你的意思是编辑源?但是,即使在 C++ 中,您也不会受到保护,其中常量(如 constexpr)是真正的硬常量。
                            • @Ruslan 我的意思是,由于 python 没有 constexpr,它不会在返回到外部上下文后停止正在编辑的值。在此示例中,没有对 42 执行任何操作来强制执行冻结状态。
                            • 在这种情况下很容易为 MY_CONSTANT 设置新值,例如 MY_CONSTANT = 43
                            【解决方案24】:

                            你可以使用StringVar或者IntVar等,你的常量是const_val

                            val = 'Stackoverflow'
                            const_val = StringVar(val)
                            const.trace('w', reverse)
                            
                            def reverse(*args):
                                const_val.set(val)
                            

                            【讨论】:

                              【解决方案25】:

                              您可以使用collections.namedtupleitertools

                              import collections
                              import itertools
                              def Constants(Name, *Args, **Kwargs):
                                t = collections.namedtuple(Name, itertools.chain(Args, Kwargs.keys()))
                                return t(*itertools.chain(Args, Kwargs.values()))
                              
                              >>> myConstants = Constants('MyConstants', 'One', 'Two', Three = 'Four')
                              >>> print myConstants.One
                              One
                              >>> print myConstants.Two
                              Two
                              >>> print myConstants.Three
                              Four
                              >>> myConstants.One = 'Two'
                              Traceback (most recent call last):
                                File "<stdin>", line 1, in <module>
                              AttributeError: can't set attribute
                              

                              【讨论】:

                                【解决方案26】:

                                在 Python 中,常量不存在,但您可以通过在变量名的开头添加 CONST_ 并在注释中声明它是常量来指示变量是常量且不得更改:

                                myVariable = 0
                                CONST_daysInWeek = 7    # This is a constant - do not change its value.   
                                CONSTANT_daysInMonth = 30 # This is also a constant - do not change this value.
                                

                                或者,您可以创建一个作用类似于常量的函数:

                                def CONST_daysInWeek():
                                    return 7;
                                

                                【讨论】:

                                  【解决方案27】:

                                  在python中,常量就是一个变量,名字全部大写,单词之间用下划线分隔,

                                  例如

                                  DAYS_IN_WEEK = 7

                                  该值是可变的,因为您可以更改它。但是鉴于规则的名称告诉你是一个常数,你为什么要这样做?我的意思是,它毕竟是你的程序!

                                  这是整个 python 所采用的方法。出于同样的原因,没有 private 关键字。在名称前加上下划线,您就知道它是私有的。代码可以打破规则......就像程序员无论如何都可以删除 private 关键字一样。

                                  Python 可以添加一个const 关键字...但是程序员可以删除关键字,然后根据需要更改常量,但为什么要这样做呢?如果你想打破规则,你无论如何都可以改变规则。但是,如果名称说明了意图,为什么还要费心打破规则呢?

                                  也许有一些单元测试可以对值进行更改?看看一周 8 天会发生什么,即使在现实世界中一周中的天数无法更改。如果语言阻止了您的例外,如果只有这种情况,您需要打破规则......然后您将不得不停止将其声明为常量,即使它在应用程序中仍然是常量,并且有只是这一个测试用例,看看如果它被改变会发生什么。

                                  全部大写的名称告诉您它是一个常量。这才是重要的。不是一种强制限制代码的语言,无论如何您都有权更改。

                                  这就是python的哲学。

                                  【讨论】:

                                    【解决方案28】:

                                    您只需:

                                    STRING_CONSTANT = "hi"
                                    NUMBER_CONSTANT = 89
                                    

                                    希望让一切变得更简单

                                    【讨论】:

                                      【解决方案29】:

                                      没有完美的方法来做到这一点。据我了解,大多数程序员只会将标识符大写,因此 PI = 3.142 可以很容易地理解为一个常数。

                                      另一方面,如果你想要一个实际上像常量一样的东西,我不确定你会找到它。对于您所做的任何事情,总会有某种方式来编辑“常数”,因此它不会真的是一个常数。这是一个非常简单、肮脏的例子:

                                      def define(name, value):
                                        if (name + str(id(name))) not in globals():
                                          globals()[name + str(id(name))] = value
                                      
                                      def constant(name):
                                        return globals()[name + str(id(name))]
                                      
                                      define("PI",3.142)
                                      
                                      print(constant("PI"))
                                      

                                      这看起来会生成一个 PHP 样式的常量。

                                      实际上,某人更改值所需要的只是:

                                      globals()["PI"+str(id("PI"))] = 3.1415
                                      

                                      这对于您将在此处找到的所有其他解决方案都是相同的——即使是创建类并重新定义 set 属性方法的聪明的解决方案——总会有解决它们的方法。 Python就是这样。

                                      我的建议是避免所有麻烦,只需将标识符大写即可。它实际上不是一个适当的常数,但又什么都不是。

                                      【讨论】:

                                        【解决方案30】:

                                        (本段是对herethere 的答案的评论,其中提到了namedtuple,但是太长了,无法放入评论中,所以,就这样吧。)

                                        上面提到的 namedtuple 方法绝对是创新的。不过,为了完整起见,在 its official documentation 的 NamedTuple 部分的末尾,它写道:

                                        枚举常量可以用命名元组来实现,但使用简单的类声明更简单、更高效:

                                        class Status:
                                            open, pending, closed = range(3)
                                        

                                        换句话说,官方文档更喜欢使用实用的方式,而不是实际实现只读行为。我猜它会成为Zen of Python 的又一个例子:

                                        简单胜于复杂。

                                        实用胜于纯粹。

                                        【讨论】:

                                          猜你喜欢
                                          • 2021-04-17
                                          • 1970-01-01
                                          • 2011-03-10
                                          • 1970-01-01
                                          • 1970-01-01
                                          • 1970-01-01
                                          • 2011-02-21
                                          • 2021-10-27
                                          相关资源
                                          最近更新 更多