Python 和 Django 常量的最佳实践

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/12822847/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-18 11:56:36  来源:igfitidea点击:

Best practice for Python & Django constants

pythondjango

提问by Dylan Klomparens

I have a Django model that relies on a tuple. I'm wondering what the best practice is for refering to constants within that tuple for my Django program. Here, for example, I'd like to specify "default=0" as something that is more readable and does not require commenting. Any suggestions?

我有一个依赖元组的 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.

EDIT:

编辑:

If possible I'd like to avoid using a number altogether. Somehow using the string 'Requires attention' instead would be more readable.

如果可能,我想完全避免使用数字。以某种方式使用字符串 'Requires attention' 会更具可读性。

采纳答案by Alasdair

It is quite common to define constants for the integer values as follows:

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

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)

By moving the constants and Statusinside the class, you keep the module's namespace cleaner, and as a bonus you can refer to Tasks.COMPLETEwherever you import the Tasksmodel.

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

回答by Platinum Azure

You could use a dictionary for a small improvement in clarity:

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

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])

回答by defuz

My approach:

我的做法:

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')

This allows you to write convenient expressions:

这允许您编写方便的表达式:

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

Also, it allows you to not create unnecessary global variables.

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

If you really want to use integer field:

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

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)

And:

和:

tasks = Task.objects.filter(status=Task.STATUSES.COMPLETE)

回答by rantanplan

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也是这样example例子做到了。

There is only one reason where you'd want to put it outside the class namespace and that is only if these semantics are equally shared by other models of your app. i.e. you can't decide in which specificmodel they belong.

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

Though it doesn't seem like this is the case in your particular example.

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

回答by Chris Wesseling

You could use a namedtuple, using an Immutable for a constant seems fitting. ;-)

您可以使用 a 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

Using attributes on Taskas constants like in Alasdair's answermakes more sense in this case, but namedtuples are very cheap substitutes for dicts and objects that don't change. Especially very handy if you want to have lotsof them in memory. They are like regular tuples with a bonus of a descriptive __repr__and attribute access.

在这种情况下,Task像在Alasdair 的答案中一样使用属性作为常量更有意义,但是 namedtuples 是不改变的 dicts 和对象的非常便宜的替代品。如果您想在内存中拥有大量它们,则特别方便。它们就像具有描述性__repr__和属性访问权限的常规元组。

回答by Jonathan Vanasco

I don't use Django, but I do something like the following quite a bit under Pyramid and Twisted ...

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

So the essence is this:

所以本质是这样的:

  • There is a base "constants" class , and another class for each type. the class defines the keywords to a value in ALLCAPS
  • I toss in the plaintext _pairsinto the class too. why? because i might need to build out some DB tables with them, or I might want them for error/status messages. I use the numbers and not the ALLCAPS variable name as a personal preference.
  • i initialize a mappingclass variable which basically monkeypatches the class by precompiling a bunch of variables within a dict because...
  • the class is derived from that base class, which offers classmethod functionality to search for a value or do other standard things you often need to do with constants.
  • 有一个基本的“常量”类,每种类型都有另一个类。该类将关键字定义为 ALLCAPS 中的一个值
  • 我也把明文_pairs扔进了班级。为什么?因为我可能需要用它们构建一些数据库表,或者我可能希望它们用于错误/状态消息。我使用数字而不是 ALLCAPS 变量名称作为个人偏好。
  • 我初始化了一个mapping类变量,它基本上通过在 dict 中预编译一堆变量来对类进行猴子补丁,因为......
  • 该类派生自该基类,该基类提供类方法功能来搜索值或执行其他经常需要对常量执行的标准操作。

It's not a one-size-fits-all approach, but I've generally come to really like this. You can easily use a dict to define the pairs , have the 'mapping' function setup some other attributes, such as giving you tuples of the pair values as k,v or v,k or any weird format you might need.

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

my code can then looks like this:

我的代码看起来像这样:

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]

whenever you need to use the constants in another way, you just add or alter the classmethods of the base.

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

回答by Paulo Cheque

Sometimes I have to create some huge choice list. I do not like to type like a monkey, so I rather to create a funcion like this:

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

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

And use like this:

并像这样使用:

my_choices = """
choice1
choice2
choice3
"""
MY_CHOICES = choices(my_choices)
print(MY_CHOICES) # ((1, choice1), (2, choice2), (3, choice3))

回答by Lutz Prechelt

Python 3.4+: Enum

Python 3.4+: Enum

You write "If possible I'd like to avoid using a number altogether."and indeed a named representation is clearly more pythonic. A bare string, however, is susceptible to typos.

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

Python 3.4 introduces a module called enumproviding Enumand IntEnumpseudoclasses that help with this situation. With it, your example could work as follows:

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)

and once the Django team picks up Enum, the choiceadapterwill even be built into Django.

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

回答by Rashid Mahmood

One possible approach could be to use python rangefunction with the combination of tuple.

一种可能的方法是将 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)

回答by Neil

Finally constants have been added to python via https://github.com/python/mypy/pull/5522

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

to instal:

安装:

pip install mypy typing_extensions

usage example:

用法示例:

from typing_extensions import Final

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

You'd need to run mypy type checker:

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

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

for more info see: https://dev.to/wemake-services/1-minute-guide-to-real-constants-in-python-2bpk

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