Python: если более чем одна из трех вещей верна, вернуть false - PullRequest
9 голосов
/ 14 июля 2011

Я пишу модель django, которая позволяет моему сайту иметь купоны.

Купоны могут быть трех типов: ваучер на пожизненный счет, ваучер на определенный период месяцев, ваучер на определенное количество долларов.

Для простоты я разрешаю только купонам иметь одно из трех возможных значений (т. Е. Ваучер не может быть рассчитан на 10 и 5 месяцев). Но я хочу проверить, когда купон сохраняется, чтобы убедиться, что это правило верно.

В настоящее время у меня есть:

true_count = 0
if self.months:
    true_count += 1
if self.dollars:
    true_count += 1
if self.lifetime:
    true_count += 1    

if true_count > 1:
    raise ValueError("Coupon can be valid for only one of: months, lifetime, or dollars")  

Я знаю, что есть лучший способ сделать это, но я его не вижу (назовите это блоком кодера).

Помощь очень ценится.

В случае, если это имеет значение, три типа int, int и bool

months = models.IntegerField(default=0)
cents = models.IntegerField(default=0)
#dollars = models.FloatField(default=0.00)
#dollars replaced with integer cents per advice of group
lifetime = models.BooleanField(default=False)

Ответы [ 11 ]

10 голосов
/ 14 июля 2011

В подобных ситуациях я делал следующее:

coupon_types = (self.months, self.dollars, self.lifetime,)

true_count =  sum(1 for ct in coupon_types if ct)
if true_count > 1:
    raise ValueError("Coupon can be valid for only one of: months, lifetime, or dollars")  

Теперь гораздо проще добавлять новые типы купонов для проверки в будущем!

4 голосов
/ 14 июля 2011

Вы также можете использовать список comp для фильтрации ложных значений:

if len([x for x in [self.months, self.dollars, self.lifetime] if x]) > 1:
    raise ValueError()

Или построение MRAB-ответ :

if sum(map(bool, [self.months, self.dollars, self.lifetime])) > 1:
    raise ValueErrro()
2 голосов
/ 14 июля 2011

Я думаю, что это удобно распределить по нескольким строкам - это облегчает поддержку, если в будущем появилось больше атрибутов для тестирования.Использование len или sum кажется слишком запутанным

# Ensure that only one of these values is set
true_count = 0
true_count += bool(self.months)
true_count += bool(self.dollars)
true_count += bool(self.lifetime)
2 голосов
/ 14 июля 2011

Храните количество в одном поле, и тип должен быть отдельным полем, которое использует choices.

2 голосов
/ 14 июля 2011

Ваш код выглядит нормально. И вот почему:

1.) Вы написали это, и именно вы описываете логику. Вы можете использовать все виды синтаксических трюков, чтобы сократить количество строк кода (true_count + = 1, если self.months еще 0, огромное выражение if и т. Д.), Но я думаю, что то, как вы это делаете, идеально, потому что это то, что вы делаете первым мысли при попытке описать логику.

Оставьте симпатичный код для задач программирования, это реальный мир.

2.) Если вы когда-нибудь решите, что вам нужно добавить другой тип значения купона, вы точно знаете, что вам нужно сделать: добавьте еще один оператор if. В одном сложном операторе if вам придётся выполнить более сложную задачу.

2 голосов
/ 14 июля 2011
if (self.months && (self.dollars || self.lifetime))  || (self.dollars && (self.months || self.lifetime)) || (self.lifetime && (self.dollars || self.months))
    raise ValueError("Coupon can be valid for only one of: months, lifetime, or dollars") 

Edit:

Я сделал быструю имитацию схемы, используя карту Карно (http://en.wikipedia.org/wiki/Karnaugh_map). В итоге это наименьшая возможная функция с логической логикой:

if((self.months && self.dollars) || (self.dollars && self.lifetime) || (self.lifetime && self.months))
    raise ValueError("Coupon can be valid for only one of: months, lifetime, or dollars") 

По логике оба моих утверждения равносильны, но второе - технически быстрее / эффективнее.

Редактировать # 2 : Если кому-то интересно, вот K-Map

A | B | C | f(A, B, C)
----------------------
0 | 0 | 0 |     0
----------------------
0 | 0 | 1 |     0
----------------------
0 | 1 | 0 |     0
----------------------
0 | 1 | 1 |     1
----------------------
1 | 0 | 0 |     0
----------------------
1 | 0 | 1 |     1
----------------------
1 | 1 | 0 |     1
----------------------
1 | 1 | 1 |     1

Что сокращается до:

   C\AB
     -----------------
     | 0 | 0 | 1 | 0 |     
     -----------------      OR      AB + BC + AC
     | 0 | 1 | 1 | 1 |
     -----------------
1 голос
/ 14 июля 2011

Если у вас Python2.7 или новее

from collections import Counter
items_to_test = (self.months, self.dollars, self.lifetime)
true_count = Counter(map(bool, items_to_test))[True]
1 голос
/ 14 июля 2011

Я не знаю, будет ли это лучше для вас, но это будет работать так:

if (self.months && self.dollars) || (self.months && self.lifetime) || (self.dollars && self.lifetime):
   raise ValueError("Coupon can be valid for only one of: months, lifetime, or dollars") 
0 голосов
/ 14 июля 2011

Как насчет

if len(filter([self.months, self.dollars, self.lifetime])) > 1:
...

Я нахожу его таким же читаемым, как понимание списка с предложением if, и более кратким.

0 голосов
/ 14 июля 2011

Даже лучшее решение, чем раньше, с combinations, any и all.Предполагая, что у вас есть все атрибуты, которые вы хотите протестировать, в последовательности, называемой attributes:

from itertools import combinations
any(map(all, combinations(attributes, 2)))

На английском языке это читает:

Любые комбинации длины-2 атрибутоввсе верно?

Это решение работает для произвольного числа атрибутов и может быть изменено, чтобы проверить, является ли произвольное число истинными.

Хотя по общему признанию это очень неэффективно, ясказал бы, что это довольно мило и читабельно.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...