45

我有一个依赖元组的 Django 模型。我想知道为我的 Django 程序引用该元组中的常量的最佳做法是什么。例如,在这里,我想将“ default=0”指定为更具可读性且不需要注释的内容。有什么建议么?

Status = (
    (-1, 'Cancelled'),
    (0, 'Requires attention'),
    (1, 'Work in progress'),
    (2, 'Complete'),
)

class Task(models.Model):
    status = models.IntegerField(choices=Status, default=0) # Status is 'Requires attention' (0) by default.

编辑:

如果可能的话,我想完全避免使用数字。不知何故,使用字符串“需要注意”会更具可读性。

4

10 回答 10

72

为整数值定义常量是很常见的,如下所示:

class Task(models.Model):
    CANCELLED = -1
    REQUIRES_ATTENTION = 0
    WORK_IN_PROGRESS = 1
    COMPLETE = 2

    Status = (
        (CANCELLED, 'Cancelled'),
        (REQUIRES_ATTENTION, 'Requires attention'),
        (WORK_IN_PROGRESS, 'Work in progress'),
        (COMPLETE, 'Complete'),
    )

    status = models.IntegerField(choices=Status, default=REQUIRES_ATTENTION)

通过移动常量和Status模型类内部,您可以保持模块的命名空间更干净,并且作为奖励,您可以Task.COMPLETE在导入Task模型的任何位置引用。

于 2012-10-10T15:42:04.973 回答
17
CANCELED, ATTENTION, WIP, COMPLETE = range(-1, 3)
Status = (
    (CANCELED, 'Cancelled'),
    (ATTENTION, 'Requires attention'),
    (WIP, 'Work in progress'),
    (COMPLETE, 'Complete'),
)

class Task(models.Model):
    status = models.IntegerField(choices=Status, default=CANCELED)


请记住,正如其他人指出的那样,正确的方法是将这些变量放在您的 Model 类中。这也是官方 django示例的方式。

只有一个原因,您希望将它放在类命名空间之外,并且只有当这些语义被您的应用程序的其他模型平等共享时。即您无法决定它们属于哪个特定型号。

尽管在您的特定示例中似乎并非如此。

于 2012-10-10T15:40:42.433 回答
13

Python 3.4+:Enum

你写“如果可能的话,我想完全避免使用数字。” 事实上,一个命名的表示显然更pythonic。然而,裸字符串容易出现拼写错误。

Python 3.4 引入了一个名为 enum提供EnumIntEnum伪类的模块来帮助解决这种情况。有了它,您的示例可以按如下方式工作:

# in Python 3.4 or later:
import enum  

class Status(enum.IntEnum):
    Cancelled = -1,
    Requires_attention = 0,
    Work_in_progress = 1,
    Complete = 2

def choiceadapter(enumtype):
    return ((item.value, item.name.replace('_', ' ')) for item in enumtype)

class Task(models.Model):
    status = models.IntegerField(choices=choiceadapter(Status), 
                                 default=Status.Requires_attention.value)

并且一旦 Django 团队接手Enum,它 choiceadapter甚至会被内置到 Django 中。

编辑 2021-06:在与 合作后Enum,我必须说我不感兴趣。在我的工作风格中(在 Django 中;你的里程可能会有所不同),抽象往往会妨碍我,我发现自己更喜欢一个松散的常量列表(通常嵌入到一个无论如何都存在的不同类中)。

于 2015-03-22T16:37:36.590 回答
7

您可以使用namedtuple, 使用 Immutable 作为常量似乎很合适。;-)

>>> from collections import namedtuple
>>> Status = namedtuple('Status', ['CANCELLED', 'REQUIRES_ATTENTION', 'WORK_IN_PROGRESS', 'COMPLETE'])(*range(-1, 3))
>>> Status
Status(CANCELLED=-1, REQUIRES_ATTENTION=0, WORK_IN_PROGRESS=1, COMPLETE=2)
>>> Status.CANCELLED
-1
>>> Status[0]
-1

在这种情况下,像在Alasdair 的答案中那样使用属性Task作为常量更有意义,但命名元组是不改变的字典和对象的非常便宜的替代品。如果你想在内存中有很多它们,特别方便。它们就像具有描述性和属性访问权的常规元组。__repr__

于 2012-10-10T16:05:20.940 回答
3

我的做法:

class Task(models.Model):
    STATUSES = { 'cancelled': 'Cancelled',
                 'requires attention': 'Requires attention',
                 'work in progress': 'Work in progress',
                 'complete': 'Complete' }

    status = models.CharField(choices=STATUSES.items(), default='cancelled')

这使您可以编写方便的表达式:

tasks = Task.objects.filter(status='complete')

此外,它允许您不创建不必要的全局变量。

如果你真的想使用整数字段:

class Task(models.Model):

   class STATUS:
      CANCELED, ATTENTION, WIP, COMPLETE = range(-1, 3)
      choices = {
        CANCELED: 'Cancelled',
        ATTENTION: 'Requires attention',
        WIP: 'Work in progress',
        COMPLETE: 'Complete'
      }


   status = models.CharField(choices=STATUSES.choices.items(), default=STATUSES.CANCELED)

和:

tasks = Task.objects.filter(status=Task.STATUSES.COMPLETE)
于 2012-10-10T15:25:00.663 回答
3

一种可能的方法是使用 python range函数和tuple的组合。

class Task(models.Model):
    CANCELED, ATTENTION, WIP, COMPLETE = range(-1, 3)
    Status = (
        (CANCELLED, 'Cancelled'),
        (REQUIRES_ATTENTION, 'Requires attention'),
        (WORK_IN_PROGRESS, 'Work in progress'),
        (COMPLETE, 'Complete'),
    )

    status = models.IntegerField(choices=Status, default=REQUIRES_ATTENTION)
于 2018-11-21T10:37:21.113 回答
1

您可以使用字典来稍微提高清晰度:

Status = {
    -1: 'Cancelled',
    0: 'Requires attention',
    1: 'Work in progress',
    2: 'Complete',
}

class Task(models.Model):
    status = models.IntegerField(choices=Status.items(), default=Status[0])
于 2012-10-10T15:24:05.077 回答
0

我不使用 Django,但我在 Pyramid 和 Twisted 下做了很多类似以下的事情......

def setup_mapping( pairs ):
    mapping = {'id':{},'name':{}}
    for (k,v) in pairs:
        mapping['id'][k]= v
        mapping['name'][v]= k
    return mapping

class ConstantsObject(object):
    _pairs= None
    mapping= None

    @classmethod
    def lookup_id( cls , id ):
       pass

    @classmethod
    def lookup_name( cls , name ):
       pass

class StatusConstants(ConstantsObject):
    CANCELLED = -1
    REQUIRES_ATTENTION = 0
    WORK_IN_PROGRESS = 1
    COMPLETE = 2

    _pairs= (
        (-1, 'Cancelled'),
        (0, 'Requires attention'),
        (1, 'Work in progress'),
        (2, 'Complete'),
    )
    mapping= setup_mapping(_pairs)

所以本质是这样的:

  • 有一个基本的“常量”类,每种类型都有另一个类。该类将关键字定义为 ALLCAPS 中的值
  • 我也把明文_pairs扔进了课堂。为什么?因为我可能需要用它们构建一些数据库表,或者我可能希望它们用于错误/状态消息。我使用数字而不是 ALLCAPS 变量名称作为个人偏好。
  • 我初始化了一个mapping类变量,它基本上通过在字典中预编译一堆变量来对类进行猴子补丁,因为......
  • 该类派生自该基类,该基类提供类方法功能来搜索值或执行您经常需要对常量执行的其他标准操作。

这不是一种万能的方法,但我通常真的很喜欢这种方法。您可以轻松地使用字典来定义对,让“映射”函数设置一些其他属性,例如为您提供对值的元组,如 k,v 或 v,k 或您可能需要的任何奇怪格式。

我的代码可以如下所示:

status_id = sa.Column(sa.Integer, sa.ForeignKey("_status.id") , nullable=False , default=constants.StatusConstants.CANCELLED )

status_name = constants.StatusConstants.lookup_id(status_id)    
status_name = constants.StatusConstants.mapping['id'][status_id]

每当您需要以其他方式使用常量时,您只需添加或更改基类的类方法。

于 2012-10-10T16:07:26.943 回答
0

有时我必须创建一些巨大的选择列表。我不喜欢像猴子一样打字,所以我宁愿创建一个这样的函数:

def choices(labels):
    labels = labels.strip().split('\n')
    ids = range(1, len(labels)+1)
    return zip(ids, labels)

并像这样使用:

my_choices = """
choice1
choice2
choice3
"""
MY_CHOICES = choices(my_choices)
print(MY_CHOICES) # ((1, choice1), (2, choice2), (3, choice3))
于 2014-08-24T04:08:15.380 回答
0

Finally 常量已通过https://github.com/python/mypy/pull/5522添加到 python

安装:

pip install mypy typing_extensions

用法示例:

from typing_extensions import Final

DAYS_IN_A_WEEK: Final = 7
DAYS_IN_A_WEEK = 8  # I really want more days in a week!

您需要运行 mypy 类型检查器:

mypy --python-version=3.6 --strict week.py
week.py:4: error: Cannot assign to final name "DAYS_IN_A_WEEK"

有关更多信息,请参阅:https ://dev.to/wemake-services/1-minute-guide-to-real-constants-in-python-2bpk

于 2020-04-02T08:38:42.827 回答