Python - 定义常量列表或字典的最佳/最清晰的方法

42

第一次使用stackoverflow,我很兴奋能来到这里。

简介:最近我开始了进入Python编程世界的神奇冒险 - 我喜欢它。现在在我从C语言笨拙地转换过来的过程中一切都顺利,但是我在创建类似于头文件(.h)的东西时遇到了麻烦。

问题:我有中等大小的字典和列表(约1,000个元素),冗长的枚举和'#define'(实际上不是),但我找不到一个清晰的方法来组织它们。在C语言中,我会将它们全部放在头文件中,并且永远不再考虑它,但在Python中这似乎不可能。

当前混乱的解决方案:我在模块或函数的顶部初始化所有常量变量(如果多个函数需要,则为模块)。

结论:如果有人想出一种清晰地组织常量变量的方法,我将非常感激。

非常感谢!


8
我想说一声,欢迎来到 Stack,并感谢您以一个组织良好的问题开始了这里的旅程! - jdi
5个回答

29

将常量放入它们自己的模块中:

# constants.py

RED = 1
BLUE = 2
GREEN = 3

请导入该模块并使用常量:

import constants

print "RED is", constants.RED

常量可以是任何您喜欢的值,这里显示的是整数,但列表和字典也同样适用。


2
@TSmith:如果你非常关注节省那一点时间,你可以随时执行 from constants import RED,或者在超长循环之前将其赋值给一个变量:red = constants.RED - jdi
谢谢Ned和jdi!我真的很感激,是的,jdi,那更符合我的期望,但是一个本地变量会得到全局导入常量的副本还是引用?只是出于好奇。 - TSmith
@TSmith:不要过度优化。例如,你的代码很可能不是计算密集型的。 - Ned Batchelder
不,绝对不是这样的,但我正在尝试了解Python的个性,如果你知道我的意思。 - TSmith
这对我似乎没有起作用。 我已经按建议做了,将所有常量放在一个名为constants.py的文件中,在脚本开头导入它们,但由于某些原因,常量在主()函数中除外都是可见的。 当我尝试在main()中使用'print constants.NUM_CELLS'时,我收到“UnboundLocalError:引用之前未分配本地变量'constants'”。 - Bill
显示剩余2条评论

14

通常我会这样做:

文件:constants.py

CONSTANT1 = 'asd'
CONSTANT_FOO = 123
CONSTANT_BAR = [1, 2, 5]

文件: your_script.py

from constants import CONSTANT1, CONSTANT_FOO
# or if you want *all* of them
# from constants import *

...

现在,您的常量都在一个文件中,您可以很好地导入和使用它们。


谢谢,如果您能帮忙,我会将效率问题的抄送发送给您。 - TSmith
如果您全部导入它们,常量将变成全局变量,不需要查找。请注意,Python 比 C 慢大约 200 倍,因此如果您尝试进行微小的优化,您不会注意到速度提升。 - Blender
谢谢blender。有一个好奇的问题,如果我将一个局部变量(在函数内部)分配给那个导入的全局变量,那么这个局部变量是会拥有该全局变量的副本还是引用? - TSmith
@TSmith:它将具有引用,但请注意其行为取决于它是可变类型还是不可变类型。如果它是字符串,则对该本地变量所做的任何更改都只会遮蔽全局常量。如果常量是列表对象,则可以向本地变量添加内容,并且它将反映在常量中,因为它仍然是相同的引用。 - jdi
@jdi:是的,我也是通过吃亏才学会可变和不可变类型的区别哈哈。非常感谢你的帮助,我真的很感激。 - TSmith

5
请将全局相关的常量放在一个单独的文件constants.py中,然后您可以使用import constants来引用它们,例如constants.SPAM或者使用from constants import *(不推荐)简单地引用它们,例如SPAMEGGS
顺便提一下,Python不支持真正的常量。约定俗成的方法是仅将它们命名为ALL_CAPS并承诺不对其进行修改。

1
哈哈,我保证:),但是运行时效率呢?当你导入一个模块时,这是否像C中的“__inline”? - TSmith
@TSmith:我不会将其与内联进行比较,因为它实际上只是将其导入到命名空间模块对象中,并且仅在第一次导入时执行,再次导入则共享内存。 - jdi
是的,现在我想起来那是一个愚蠢的问题。谢谢你,祝你有美好的一天! - TSmith

4

接受的答案已经很好了,要更进一步可以在定义常量时使用类型声明; 你可以只指定 Final,或者再进一步提供 Final[type],例如:

from typing import Final, List
CONSTANT1: Final = 'asd'
CONSTANT_FOO: Final[int] = 123
ADLS_ENVIRONMENTS: Final[List[str]] = ["sandbox", "dev", "uat", "prod"]

# etc.

请见https://www.python.org/dev/peps/pep-0591/#the-final-annotation

0

如果你想处理嵌套常量,而不喜欢字典,我想出了这个有趣的解决方案:

# Recursively transform a dict to instances of the desired class

import json
from collections import namedtuple

class DictTransformer():
    @classmethod
    def constantize(self, d):
        return self.transform(d, klass=namedtuple, klassname='namedtuple')

    @classmethod
    def transform(self, d, klass, klassname):
        return self._from_json(self._to_json(d), klass=klass, klassname=klassname)

    @classmethod
    def _to_json(self, d, access_method='__dict__'):
        return json.dumps(d, default=lambda o: getattr(o, access_method, str(o)))

    @classmethod
    def _from_json(self, jsonstr, klass, klassname):
        return json.loads(jsonstr, object_hook=lambda d: klass(klassname, d.keys())(*d.values()))

例:

constants = {
  'A': {
    'B': {
      'C': 'D'
    }
  }
}
CONSTANTS = DictTransformer.transform(d, klass=namedtuple, klassname='namedtuple')
CONSTANTS.A.B.C == 'D'

优点:

  • 处理嵌套字典
  • 可以潜在地生成其他类型的字典/类
  • 命名元组为常量提供了不可变性

缺点:

  • 如果klass上没有提供.keys和.values,可能无法响应这些方法(尽管有时候可以使用._fields和list(A.B.C)来模仿)

想法?

感谢@hlzr和你们提供的原始类想法


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接