这主要是一个“良好的Python风格”问题。
我有一个模块,它使用了许多感觉应该分组的常量。
假设我们有狗和猫,它们每只都有许多条腿和最喜欢的食物。
请注意
我想到了以下解决方案:
模块级别的常量
DOG_NUMBER_OF_LEGS = 4
DOG_FAVOURITE_FOOD = ["Socks", "Meat"]
CAT_NUMBER_OF_LEGS = 4
CAT_FAVOURITE_FOOD = ["Lasagna", "Fish"]
他们看起来并没有真正分组,但我认为这是我更喜欢的解决方案。
类作为命名空间
class Dog(object):
NUMBER_OF_LEGS = 4
DOG_FAVOURITE_FOOD = ["Socks", "Meat"]
class Cat(object):
NUMBER_OF_LEGS = 4
FAVOURITE_FOOD = ["Lasagna", "Fish"]
我不喜欢这个解决方案,因为我们将拥有不会使用的类,并且它们可以实际实例化。
常量字典
ANIMALS_CONFIG = {
"DOG" : {
"NUMBER_OF_LEGS" : 4,
"FAVOURITE_FOOD" : ["Socks", "Meat"]
},
"CAT" : {
"NUMBER_OF_LEGS" : 4,
"FAVOURITE_FOOD" : ["Lasagna", "Fish"]
}
}
我也考虑过添加子模块,但我真的不想公开那些内部常量
最Pythonic的方法是什么/你会怎么做?
collections.namedtuple
:
Animal = namedtuple('Animal', 'number_of_legs favourite_food')
然后您创建如下实例:
DOG = Animal(4, ['Socks', 'Meat'])
CAT = Animal(4, ['Lasagna', 'Fish'])
并从外部访问值:
from animals import CAT
print CAT.number_of_legs
如果您不需要创建任何方法,那么拥有类确实没有意义,而且我认为上面的访问形式比例如更简洁:
from animals import animals
print animals['CAT']['number_of_legs']
namedtuple
,就像普通的tuple
一样,也是不可变,所以你不能意外地重新分配,例如CAT.number_of_legs = 2
某处。
最后,
namedtuple
是一种轻量级数据结构,如果您要创建大量动物,这可能很重要:
>>> import sys
>>> sys.getsizeof({'number_of_legs': 4, 'favourite_food': ['Lasagna', 'Fish']})
140
>>> from collections import namedtuple
>>> Animal = namedtuple('Animal', 'number_of_legs favourite_food')
>>> sys.getsizeof(Animal(4, ['Lasagna', 'Fish']))
36
没有一刀切的答案,这实际上取决于您必须处理多少个常量、如何使用它们、多态调度是否有意义以及月相。
现在在您的示例中,由于您有两组或更多组具有相似结构和含义的常量,所以我会选择“类作为名称空间”解决方案。
FWIW 防止类被实例化并不困难:
>>> class Foo(object):
... def __new__(cls):
... return cls
...
>>> Foo()
<class '__main__.Foo'>
但是文档应该足够了 - 请记住,无论如何,你的“常量”只是约定俗成的常量。
dataclass
并使其成为 frozen
。这不仅可以防止任何成员被更改,而且还允许将常量用作字典键(因为它们变得可哈希)。
并且在 python >= 3.10 中,您甚至可能想要设置
slots=True
来阻止添加新成员。
这样你也能正确打字:
from dataclasses import dataclass
@dataclass(frozen=True, slots=True)
class Animal:
number_of_legs: int
favourite_foods: tuple[str, ...]
DOG = Animal(number_of_legs=4, favourite_foods=('Socks', 'Meat'))
CAT = Animal(number_of_legs=4, favourite_foods=('Lasagna', 'Fish'))
print(CAT) # Animal(number_of_legs=4, favourite_foods=('Lasagna', 'Fish'))
print(DOG.number_of_legs) # 4
我会选择字典方法,因为它感觉更具可读性和更好的组织性,但我相信这是个人喜好的问题。
如果您使用仅包含静态变量和像 this 这样的方法的类并且从不实例化它们,那么使用类似乎也不是一个坏主意。
使用NamedTuples的另一种方式:
如果有一些常量集,将它们分组总是好的。如果我们可以在键入它们时在 IDE 上访问它们,那就更好了。字典不是一个解决方案,因为 IDE 无法在您键入时显示建议。
from collections import namedtuple
_coverage_comments = namedtuple('COVERAGE_COMMENTS',['no_progress', 'coverage_improved'])
COVERAGE_COMMENTS = _coverage_comments(no_progress="No Progress", coverage_improved="Improved")
print(COVERAGE_COMMENTS.coverage_improved)
enum
对常量集合进行分组。与常规 class
、dataclass
或 namedtuple
相比,它具有 优势:
此外,
enum
项是可迭代的(list
)和可散列的(in
)。您可以通过 .value
属性访问实际的常量值,但这有点冗长。
from enum import Enum
class DogConstant(Enum):
NUMBER_OF_LEGS = 4
FAVOURITE_FOOD = ["Socks", "Meat"]
class CatConstant(Enum):
NUMBER_OF_LEGS = 4
FAVOURITE_FOOD = ["Lasagna", "Fish"]
# List all constants
print(f"{list(DogConstant)}")
# Check if an enum item exists in the enum class
print(f"{'NUMBER_OF_LEGS' in DogConstant.__members__ = }")
# Get value of an enum item
print(f"{DogConstant.NUMBER_OF_LEGS.value = }")
print(f"{DogConstant.FAVOURITE_FOOD.value = }")
print(f"{CatConstant.FAVOURITE_FOOD.value = }")
打印
[<DogConstant.NUMBER_OF_LEGS: 4>, <DogConstant.FAVOURITE_FOOD: ['Socks', 'Meat']>]
'NUMBER_OF_LEGS' in DogConstant.__members__ = True
DogConstant.NUMBER_OF_LEGS.value = 4
DogConstant.FAVOURITE_FOOD.value = ['Socks', 'Meat']
CatConstant.FAVOURITE_FOOD.value = ['Lasagna', 'Fish']