在 Python 中存储常量的位置

Where to store constants in Python

我目前正在做一个项目,我有大量的全局常量要硬编码到我的代码中。项目写在Python。众所周知,Python 访问全局变量时的性能显着下降。

我可以将那些我只在一种方法中使用的常量移动到我使用它们的方法的本地范围,但这会降低可读性。然后我仍然会有一些在多个方法中使用的全局变量,我真的不能将它们隐藏在一个函数的范围内。

有什么解决办法?我看到一个人在做游戏 (Ludum Dare 31),f.e 在 3:30 你可以看到他只有一个大文件 constants.py,里面有很多全局变量(没有 global 关键字)。这是一个好的做法吗?

如果您只关心您的代码在全局命名空间查找中的性能,您或许可以

globals()['your_constant_name'] # inside your function/method

这将直接在全局命名空间中查找内容。请注意,如果由于某种原因常量不存在,则会引发 'KeyError' 而不是 'AttributeError'.

此外,根据 Python 文档

This is always the dictionary of the current module (inside a function or method, this is the module where it is defined, not the module from which it is called)

所以请谨慎使用。

更新:

这有点极端(在任何现实场景中都不太可能发生),但如果堆栈帧很大,字典查找确实会稍微提高性能,尽管字典构造等如@brunodesthuilliers 所提到的.测试代码:

import itertools,timeit
globals().update({''.join(n):i for i,n in enumerate(itertools.permutations('ABCDEFGHI'))})


def with_dict():
    def func():
        try:
            func()
        except RecursionError:
            globals()['ABCDEFGHI']


def without_dict():
    def func():
        try:
            func()
        except RecursionError:
            ABCDEFGHI

print(timeit.timeit(with_dict)) # output: 0.33404375400277786
print(timeit.timeit(without_dict)) # output: 0.3390919269877486

尽管根据 python wiki,字典查找的平均时间复杂度为 O(1)

as we all know, Python performance when it comes to accessing global variables drops down significantly.

不是 "significantly" - 本地查找确实更便宜一些,但是定义一个局部变量也有成本,所以除非你在一个非常紧密的循环中查找全局变量,否则你有可能注意到差异真的很小,然后你总是可以在本地为全局别名,即 from:

FOO = 42

def foo():
    for i in range(100000000):
        x = i * FOO

FOO = 42

def foo():
    localfoo = FOO
    for i in range(100000000):
        x = i * localfoo

换句话说,你真的不应该担心这里的性能问题,直到性能成为一个真正的问题并且探查器将这个全局查找识别为一个主要瓶颈(这真的不太可能),即便如此我还是认真的怀疑你最终是否会得到任何显着的提升 - 如果全局查找的成本对你的应用程序来说已经太多了,那么 Python 不是正确的工具,是时候用 C 重写这部分了。

I could move those constants, that I use only in one method, to the local scope of the methods I use them from, but that removes readability.

并且,如上所述,不一定会提高性能:

>>> import dis
>>> import timeit
>>> 
>>> FOO = 42
>>> def foo1():
...     return FOO
... 
>>> def foo3():
...     foo = 42
...     return foo
... 
>>> dis.dis(foo1)
  2           0 LOAD_GLOBAL              0 (FOO)
              3 RETURN_VALUE        
>>>
>>> dis.dis(foo3
... )
  2           0 LOAD_CONST               1 (42)
              3 STORE_FAST               0 (foo)

  3           6 LOAD_FAST                0 (foo)
              9 RETURN_VALUE  
>>> timeit.timeit("func()", "from __main__ import foo1 as func")
0.06334185600280762
>>> timeit.timeit("func()", "from __main__ import foo3 as func")
0.06805109977722168

Then I will still have some globals which are used in multiple methods and I really cannot hide them in the scope of only one function. What's the solution to that?

究竟是什么问题?

I saw a guy making a game (...) you can see that he just has one big file constants.py with a hell lotta global variables there (without global keyword).

在模块顶层定义的所有名称(通过赋值、导入、函数定义或 class 定义)对于模块都是 "global"(这是唯一一种 "global" 你会在 Python 中找到 - 没有 "application-wide globals")。 global 关键字只能在函数内使用,并且只有当您真正想在函数内分配给该全局变量时才使用 - 我们都知道我们不应该这样做,对吗?

Is this a good practic?

取决于那些 "constants" 的使用方式和位置。如果您有多个模块使用的常量,并且这些模块之间没有其他依赖关系,那么它确实有意义,但大多数时间常量要么仅由一个模块使用,要么使用它们的其他模块也需要其他来自同一模块的名称(函数,classes 等)。

长话短说:常量没什么特别的,它们只是引用对象的名称(你可能没有意识到,但你所有的函数和 classes 也是 "constants"),所以你只是想应用与其他任何东西相同的指导方针:你的模块应该有很强的内聚性(模块中的所有东西都是强相关的)和低耦合(你的模块尽可能少地依赖其他模块)。从这个角度来看,在一个文件中定义数十个不相关的常量,让 10 多个其他不相关的模块依赖,这是完全错误的——它破坏了内聚性并引入了强耦合。

请注意,您可能有一些其他原因 "centralize" 常量(至少其中一些)这样:使配置更容易 - 但这仅适用于您想要配置的常量(您是否值 "pi" 可配置?),这是一个完全不同的问题。