在Python中对常量进行分组

15
我的模块使用了一些我觉得应该分组的常量。狗和猫都有一定数量的腿和喜爱的食物。
  1. 我只想对狗和猫的这些常量进行建模。
  2. 将来可能会有更多的动物。
  3. 这些常量不会在模块外部使用。

我考虑了以下几点:

  1. 模块级别的常量:

    DOG_NUMBER_OF_LEGS = 4
    DOG_FAVOURITE_FOOD = ["袜子", "肉"]
    CAT_NUMBER_OF_LEGS = 4
    CAT_FAVOURITE_FOOD = ["千层面", "鱼"]
    

    它们似乎没有分组,但这是我喜欢的解决方案。

  2. 类作为命名空间:

    class Dog(object):
      NUMBER_OF_LEGS = 4
      DOG_FAVOURITE_FOOD = ["袜子", "肉"]
    class Cat(object):
      NUMBER_OF_LEGS = 4
      FAVOURITE_FOOD = ["千层面", "鱼"]
    

    我不喜欢这个,因为它们是我不会使用但实际上可以实例化的类。

  3. 常量字典:

    ANIMALS_CONFIG = {
       "DOG" : {
         "NUMBER_OF_LEGS" : 4,
         "FAVOURITE_FOOD" : ["袜子", "肉"]
       },
       "CAT" : {
         "NUMBER_OF_LEGS" : 4,
         "FAVOURITE_FOOD" : ["千层面", "鱼"]
       }
    }
    
我也考虑过添加子模块,但我不想暴露那些内部常量。你认为最符合Python风格的方式是什么?或者你会如何处理?

2
你也可以有一个名为Animal的类,其中包含dogcat实例。 - syntonym
4
你也可以使用 命名元组。请注意,原文链接指向的是 Python 2.7 版本的文档,我已将链接更改为适用于 Python 3.x 版本的文档。 - syntonym
1
你将如何在你的代码中使用它们?你会迭代还是选择?选择参数将来自哪里? - Peter Wood
不需要迭代或其他操作,只需使用完整名称调用我的常量。 可以使用DOG_NUMBER_OF_LEGS、ANIMALS_CONFIG["DOG"]["NUMBER_OF_LEGS"]或Dog.NUMBER_OF_LEGS。 - Mario Corchero
@MarioA.CorcheroJiménez 只是为了确认,您希望对象的唯一行为是保存这两个值并通过键和/或属性访问它们吗? - jonrsharpe
6个回答

12

我会选择第四个选项,更喜欢使用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

4

没有适用于所有情况的答案,这取决于您要处理多少个常量,您如何使用它们,是否有多态分派有意义以及月亮的阶段。

现在在您的示例中,由于您有两个或更多具有相似结构和含义的常量集,我会选择“类作为命名空间”的解决方案。

顺便说一句,防止类被实例化并不难:

>>> class Foo(object):
...    def __new__(cls):
...       return cls
... 
>>> Foo()
<class '__main__.Foo'>

但是文档应该足够了 - 记住你的“常量”只是约定俗成的常量。


2

在更近的版本(>= 3.7)中,我建议您使用 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

0

使用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)

0
我会选择字典方法,因为它更易读和组织得更好,但我认为这是个人喜好的问题。

如果使用仅包含静态变量和方法的类(如this),并且您从未实例化它们,则使用类也不是一个坏主意。


0
你可以使用enum.Enum来分组一系列常量。它相较于普通的classdataclassnamedtuple有一些优势
  1. 枚举项的值在运行时无法重新赋值,这使得它真正成为了一个常量
  2. 你无法实例化一个枚举类的对象,这意味着它的行为类似于一个单例。
此外,枚举项是可迭代的(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']

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