2012-02-29 2 views
28

Можно создать дубликат:
What's the best way to implement an 'enum' in Python?Булевы имеют два возможных значения. Существуют ли типы, которые имеют три возможных значения?

Я пишу функцию, которая, в идеале, я хотел бы вернуться один из трех состояний: «да», «нет», и «Не знаю».

  1. Существуют ли на всех языках программирования три типа и только три состояния? Как булев, но с тремя состояниями вместо двух?

  2. В языках, которые не имеют такого типа (например, Python), какой лучший тип представляет это?

    В настоящее время я думаю, что я пойду с целым числом (0 для «нет», 1 на «не знаю» и 2 для «да»), но, может быть, есть лучший способ? Целые числа кажутся немного «магическим числом».

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

+2

Это может быть актуально: http://stackoverflow.com/questions/36932/whats-the-best-way-to-implement-an-enum-in-python – Owen

+2

Как и по этому вопросу, не так ли? http://stackoverflow.com/questions/36932/whats-the-best-way-to-implement-an-enum-in-python –

+0

@ JohannesWeiß: вполне возможно - у меня нет фона C, m не знакомы с концепцией перечислений. Выглядит хорошо, ура. –

ответ

24

В Python я хотел бы сделать это с объектом оболочки, которая содержит одну из этих трех значений; Я бы использовал True, False и None. Поскольку значение неявной правдивости объекта, имеющего логические значения, с тремя возможными значениями, является проблематичным, мы разрешим это на , не допуская этого полностью (повышение исключения в __nonzero__() или в Python 3, __bool__()), что требует, чтобы сравнения всегда были сделано явно, используя in, ==, или !=. Мы будем применять равенство как идентификатор, чтобы были сопоставлены только конкретные значения одиночного тона True, False и None.

class Tristate(object): 

    def __init__(self, value=None): 
     if any(value is v for v in (True, False, None)): 
      self.value = value 
     else: 
      raise ValueError("Tristate value must be True, False, or None") 

    def __eq__(self, other): 
     return (self.value is other.value if isinstance(other, Tristate) 
       else self.value is other) 

    def __ne__(self, other): 
     return not self == other 

    def __nonzero__(self): # Python 3: __bool__() 
     raise TypeError("Tristate object may not be used as a Boolean") 

    def __str__(self): 
     return str(self.value) 

    def __repr__(self): 
     return "Tristate(%s)" % self.value 

Использование:

t = Tristate(True) 
t == True   # True 
t != False   # True 
t in (True, False) # True 
bool(t)    # Exception! 
if t: print "woo" # Exception! 

При использовании Tristate объектов, вы должны явно указать, какие значения совпадают, т.е. foo == True or bar != None. Вы также можете сделать foo in (False, None) для соответствия нескольким значениям (хотя, конечно, in два значения противоположны != с одним значением). Если есть другие логические операции, которые вы хотите выполнить с этими объектами, вы можете реализовать их как методы или, возможно, переопределить некоторые операторы (к сожалению, логические not, and и or не являются переопределяемыми, хотя есть a proposal добавьте это).

Также обратите внимание, что вы не можете переопределить id() в Python, так, например, Tristate(None) is None - False; два объекта на самом деле разные. Поскольку хороший стиль Python заключается в использовании is при сравнении с одиночными играми, это несчастливо, но неизбежно.

Редактировать 4/27/16: Добавлена ​​поддержка сравнения одного объекта Tristate с другим.

+0

@ PaulD.Waite Я собирался спросить, почему это недопустимо, но это совершенно понятно, почему. – Marcin

+0

@ Марцин: Конечно, извините, если я причинил какое-либо преступление - этот ответ (если я правильно понимаю), по сути, такой же, как ваш, только с примером кода и обсуждением логического сравнения. Я подумал, что дополнительные детали сделали его приемлемым. –

+0

@ PaulD.Waite Нет, без обид - этот ответ явно намного более полный и затрагивает аспекты проблемы, о которых я даже не думал. – Marcin

2

Это общеизвестно как «enum», после конструкции C этого имени.

Вы можете легко создать свой собственный класс, объекты которого должны быть инициализированы значениями из набора, и вы можете соответствующим образом создать соответствующие функции сравнения, равенства и правды.

+1

Тернарная логика - это не то же самое, что перечисление. –

+1

@EthanFurman: Это правда, но нет, думаю, актуально. – Marcin

0

Нет таких встроенных типов. И перечисление также не поддерживается как тип. Тем не менее, вы можете использовать константу как:

class Option: 
    NO = 0 
    DONT_KNOW = 1 
    YES = 2 

reply = Option.YES 
+0

Обратите внимание, что это не то же самое, что перечисление. – Marcin

+0

Для перечисления, такого как реализация, вы можете обратиться http://stackoverflow.com/a/1695250 –

+0

Это не похоже на C перечисление точно так же, как ваш пример. – Marcin

0

Я не так много программирования Python, но вы могли бы вернуться Ни в Не знаю дела. Вызывающий должен будет проверить «Да», «Нет» или «Нет», но, по крайней мере, они будут знать, что вы не знаете.

http://boodebr.org/main/python/tourist/none-empty-nothing

6

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

class Ternary: 
    FALSE = 0 
    TRUE = 1 
    UNKNOWN = 2 

себя, я бы, вероятно, пойти для решения (True, False, None), но я понимаю ваше беспокойство.

0

Я никогда не видел тип, похожий на булев, который имел более двух значений. Ведь «булево» означает, что функция работает над «булевым доменом», который, в свою очередь, имеет ровно 2 значения.

Тем не менее, вполне нормально использовать целые числа, перечисления или даже строки. В python вы можете создать модуль, содержащий только переменные YES, NO, MAYBE и импортировать оттуда в любое другое место.

2
  1. По крайней мере, один язык имеет следующее: C#: int? num = null;, теперь num на самом деле Nullable<Int32> (знак вопроса является «синтаксический сахар») см: http://msdn.microsoft.com/en-us/library/1t3y8s4s%28v=vs.80%29.aspx и http://en.wikipedia.org/wiki/Nullable_type
  2. Не знаю, Python, но зависит от ваших предпочтений: удобство и простота использования (enum или class) по сравнению с эффективностью (4 битовых полей)
5

параллельный к Отсутствуют задачи существует с ложным = 0, = 1 верно, неизвестно = 2 (неизвестное на самом деле не истинно, но будет иметь значение True, если вы не будете осторожны).

Я придумал хакерский способ получить то, что по крайней мере приближается к тому, что вы хотите, я думаю. По крайней мере, вы получите что-то, что будет оцениваться триниально в if/else и других булевых вариантах eval.

class Yes(object): 
    def __nonzero__(self): 
     return True 

class No(object): 
    def __nonzero__(self): 
     return False 

class Unknown(object): 
    def __nonzero__(self): 
     raise ValueError('Unknown typed values do not evaluate to True/False. Try using Ternary.eval().') 

class Ternary(object): 
    def __init__(self, yes, no, unknown): 
     setattr(self, yes, Yes()) 
     setattr(self, no, No()) 
     setattr(self, unknown, Unknown()) 
    @staticmethod 
    def eval(value, unknown_eval): 
     if isinstance(value, Unknown): 
      return unknown_eval 
     return bool(value) 

Использование:

t = Ternary('yes', 'no', 'unknown') 
# Do stuff to assign ternary value to x 
if Ternary.eval(x, True): 
    print 'x is yes or unknown' 
if Ternary.eval(x, False): 
    print 'x is yes only' 

Вы могли бы сделать Да, Нет, и не указан псевдо-одиночек, которые позволили бы вы уточнить Eval немного. Вы все равно можете сделать простые, если вы проверите, когда знаете, что ваше значение будет да или нет, но если вы попытаетесь сделать прямое bool() (т.е. если x) в Unknown, вы получите TypeError. Это сделало бы ваш код более явным, так как каждый раз, когда вы проверяли значение типа trinary, вам нужно было бы определить в своем коде, как вы хотите, чтобы неизвестный обрабатывался в контексте этого условного, так что это было бы плюсом ,

Редактировать: Я подумал об альтернативе, которая потребует меньше специальной обработки, но менее гибкой. Alter выше таким образом:

class Unknown(object): 
    def __init__(self, eval): 
     self._eval = eval 
    def __nonzero__(self): 
     return self._eval 

class Ternary(object): 
    def __init__(self, yes, no, unknown, unknown_eval): 
     setattr(self, yes, Yes()) 
     setattr(self, no, No()) 
     setattr(self, unknown, Unknown(unknown_eval)) 

Использование:

t1 = Ternary('yes', 'no', 'unknown', True) 
t2 = Ternary('yes', 'no', 'unknown', False) 
# Do stuff to assign ternary values to x1 and x2 
if x1: 
    print 'x is yes or unknown' 
if x2: 
    print 'x is yes only' 

Это имеет преимущество позволяет ненулевым работать спецификации вызовов для в Unknown, но она имеет обратную сторону, имеющий Eval для неизвестного набора в камне от инстанцирования и больше не позволяя Неизвестному быть псевдо-одиночным.

+0

'__nonzero__' должен возвращать' bool' или 'int' - None не допускается. –

+0

@ Ethan Furman, и, следовательно, почему я неоднократно указывал, что это не соответствует спецификации. И почему я предоставил альтернативу внизу, что делает. У вас есть новая информация для добавления? –

+0

Ah - просто прочитайте свой пост более подробно. Если у вас возникнет исключение, возникшее, когда значение неизвестно, лучше определить собственное исключение и повысить его, вместо того, чтобы оно выглядело как ошибка. Для полной реализации тройной логики вы можете проверить ссылку в моем ответе. (Если вы хотите использовать собственный класс исключений в своем примере или что-то еще, что не похоже на ошибку, я с радостью изменю свой голос.) –

1

У меня есть 3-значный логический класс в моем dbf module.

Он реализует False/True/Unknown как одноплодные значения Falsth/Truth/Unknown. Он реализует все операторы сравнения, а также позволяет сравнивать с синглетонами Python False/True/None (None принято считать неизвестным). Любое сравнение с результатами Unknown значение в Unknown, и попытка неявно использовать Unknown значение в if оператора (или другой bool контексте) поднимет TypeError, хотя Truth и Falsth могут быть использованы в логических контекстах, и Unknown можно сравнить против непосредственно.

Поскольку это не представляется возможным переопределить and, or и not поведение типа переопределяет Битовые операторы &, | и ~.

Он также реализует __index__ с Unknown, имеющим значение 2.

Пример:

from dbf import Logical, Truth, Falsth, Unknown 

middle_name = raw_input("What is the middle name? ['?' if unknown] ").strip() 
if middle_name == '?': 
    middle_name = '' 
    middle_exists = Unknown 
elif middle_name: 
    middle_exists = Truth 
else: 
    middle_exists = Falsth 
. 
. 
. 
if middle_exists is Unknown: 
    print "The middle name is unknown." 
elif middle_exists: 
    print "The middle name is %s." % middle_name 
else: 
    print "This person does not have a middle name." 
Смежные вопросы