2012-10-10 108 views
31

我有一個依賴元組的Django模型。我想知道在我的Django程序中引用該元組中的常量的最佳做法是什麼。在這裏,例如,我想指定「default=0」作爲更具可讀性且不需要評論的內容。有什麼建議麼?Python和Django常量的最佳實踐

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. 

編輯:

如果可能的話,我想完全避免使用數量。以某種方式使用字符串'需要注意'而不是可讀性。

+0

你讀過使用'__metaclass__'這個有趣的方法嗎? http://tomforb.es/using-python-metaclasses-to-make-awesome-django-model-field-choices –

回答

51

這是很常見的如下定義的整數值常量:

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類裏面,你把模塊的名稱空間清潔,並作爲獎勵,你可以參考一下Tasks.COMPLETE無論你進口Tasks型號。

+0

還可以訪問字符串表示: 'def status_str(self): return self.Status [self.status] [1]' – Alveoli

+3

@Alveoli django的基本模型爲每個字段提供一個'get_fieldname_display()'函數一個模型。因此,get_status_display()已經可用 – quin

+0

難道你不認爲它需要額外的內存用於該類的對象嗎?一個更好的主意是將它們分開,如果你想從對象訪問它們,那麼你可以定義一個可以返回它的函數。 – Anuj

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

我認爲這與簡單地使用數字常量非常相似。在這種情況下,有沒有一種方法可以將人類可讀的上下文信息添加到「1」的含義?有人如何說出1意味着「正在進行中」? –

2

我的方法:

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) 
+0

我認爲這與簡單地使用數字常量非常相似。在這種情況下,有沒有一種方法可以將人類可讀的上下文信息添加到「1」的含義?有人如何說出1意味着「正在進行中」? –

16
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的做法。

只有一個原因,您希望將它放在類命名空間 之外,並且僅當這些語義由應用程序的其他模型共享時纔有效。即 您無法決定哪個具體的型號屬於哪個。

雖然在您的特定示例中似乎不是這種情況。

+1

Upvoted,但是當常數像Alasdair的答案中一樣移入類時,我更喜歡它。 –

+0

@ChrisWesseling完全同意,只是不想偏離OP的示例代碼。 – rantanplan

+0

儘管它僅用於模型中,但有時將它們放在課程外面也很有用。也許在一個常量文件中。我必須這樣做才能避免循環進口。要調用Task.CANCELED我必須導入它。由於它在很多地方被調用,並且模型變化非常快,如果我們在所有需要的文件中導入Task,就有機會進行循環導入。 –

7

您可以使用namedtuple,使用Immutable作爲常量似乎是合適的。在Task ;-)

>>> 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's answer更有意義在這種情況下,但namedtuples是類型的字典和不改變的對象很便宜的替代品常數。特別是非常方便,如果你想在他們的記憶中有批次。它們就像常規的元組,帶有描述性的__repr__和屬性訪問獎金。

+3

如果您需要進一步[令人信服](http://pyvideo.org/video/367/pycon-2011--fun-with-python--39-s-newer-tools)'[11:35 - 26:00 ]' – kreativitea

0

我不使用Django的,但我做類似金字塔和扭曲在以下頗有幾分......

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類變量由一個字典,因爲內預編譯一堆的變數,基本上monkeypatches類...
  • 類是從基類,它提供了類方法的功能來搜索值或做衍生你常常需要處理常量的其他標準事情。

這不是一個通用的方法,但我通常會非常喜歡這個。你可以很容易地使用一個字典來定義對,讓'映射'功能設置一些其他的屬性,比如給你對值的元組作爲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] 

,當您需要使用常量以另一種方式,你只需要添加或改變基本的classmethods。

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

的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的。