2012-02-29 19 views
28

Duplicar posible:
What's the best way to implement an 'enum' in Python?Los booleanos tienen dos valores posibles. ¿Hay tipos que tienen tres valores posibles?

estoy escribiendo una función que, a ser posible, me gustaría devolver uno de tres estados: “sí”, “no”, y "No sé".

  1. ¿Hay algún lenguaje de programación que tenga tres y solo tres estados? Como un booleano, pero con tres estados en lugar de dos?

  2. En idiomas que no tienen ese tipo (como Python), ¿cuál es el mejor tipo para representar esto?

    Actualmente creo que voy a ir con un entero (0 para “no”, 1 de “no sabe” y 2 para “sí”), pero tal vez hay una manera mejor? Los enteros parecen un poco "número mágico".

    podría volver True, False o None, sino como None evaluaría a False en la mayoría de los contextos de comparación parece un poco madura para errores.

+2

Esto puede ser relevante: http://stackoverflow.com/questions/36932/whats-the-best-way-to-implement-an-enum-in-python – Owen

+2

Similar a esta pregunta, ¿no? http://stackoverflow.com/questions/36932/whats-the-best-way-to-implement-an-enum-in-python –

+0

@ JohannesWeiß: muy posiblemente, no tengo un fondo C, y yo ' No estoy familiarizado con el concepto de enums. Se ve bien, salud. –

Respuesta

24

En Python que haría que con un objeto de contenedor que sostiene uno de esos tres valores; Usaría True, False y None. Dado que el valor de verdad implícita de un objeto Booleano con tres valores posibles es problemático, lo resolveremos por rechazando por completo (generando una excepción en __nonzero__(), o en Python 3, __bool__()), lo que requiere que las comparaciones siempre sean hecho explícitamente, usando in, ==, o !=. Implementaremos la igualdad como identidad de manera que solo coincidan los valores de singleton específicos True, False y 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 

Uso:

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

Al utilizar Tristate objetos, debe especificar explícitamente que valora a la altura, es decir, foo == True or bar != None. También puede hacer foo in (False, None) para que coincida con varios valores (aunque, por supuesto, in dos valores es lo contrario de != con un solo valor). Si hay otras operaciones lógicas que desea realizar con estos objetos, puede implementarlas como métodos, o posiblemente anulando ciertos operadores (lamentablemente, sin embargo, los lógicos not, and y or no son anulables, aunque hay a proposal para agregar eso).

También tenga en cuenta que no puede anular id() en Python, por ejemplo, Tristate(None) is None es False; los dos objetos son de hecho diferentes. Dado que un buen estilo de Python es usar is al comparar contra singletons, esto es desafortunado, pero inevitable.

Editar 4/27/16: Se agregó soporte para comparar un objeto Tristate con otro.

+0

@ PaulD.Waite Iba a preguntar por qué la inacepta, pero está bastante claro por qué. – Marcin

+0

@Marcin: claro, lo siento si causé alguna ofensa; esta respuesta es (si entiendo correctamente) esencialmente la misma que la tuya, solo con código de ejemplo y discusión de comparación booleana. Pensé que el detalle adicional hacía que valiera la pena ser aceptado. –

+0

@ PaulD.Waite No, sin ofender, esta respuesta es claramente más completa y aborda aspectos del problema que ni siquiera había pensado. – Marcin

2

En general se conoce como 'enum', después de la construcción C de ese nombre.

Puede crear fácilmente su propia clase, cuyos objetos deben inicializarse con los valores de un conjunto, y puede crear las funciones de comparación, igualdad y verdad apropiadas en consecuencia.

+1

La lógica ternaria no es lo mismo que una enumeración. –

+1

@EthanFurman: Eso es cierto, pero no, creo que es relevante. – Marcin

0

No hay tales tipos incorporados. Y enum tampoco son compatibles como tipo. Sin embargo, se puede utilizar como constante:

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

reply = Option.YES 
+0

Tenga en cuenta que esto no es exactamente lo mismo que una enumeración. – Marcin

+0

Para una implementación como enum, puede consultar http://stackoverflow.com/a/1695250 –

+0

que es diferente a una C enum exactamente de la misma manera que en su ejemplo. – Marcin

6

Esto se llama Ternary logic o tres valores Logic. Como otras respuestas sugieren, podría o bien implementar una clase:

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

mí mismo, probablemente me vaya para su solución (True, False, None) pero entiendo su preocupación.

0

Nunca vi un tipo similar a boolean que tenía más de dos valores. Después de todo "booleano", significa que la función opera sobre "dominio booleano", que a su vez tiene exactamente 2 valores.

Dicho esto, está perfectamente bien usar enteros, enumeraciones o incluso cadenas. En python puede crear un módulo que contenga solo las variables YES, NO, MAYBE e importar desde allí a cualquier otro lugar.

5

el paralela a la Ninguno problema existe con false = 0, true = 1, desconocido = 2 (desconocido es tampoco es verdad en realidad, pero evaluará a True si no tiene cuidado).

Creo que me costó trabajo conseguir algo que se aproxime lo que quieres. Al menos obtendrá algo que se evaluará de forma trinaria en if/else y otras instancias de evaluación booleanas.

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) 

Uso:

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' 

Se podría hacer Sí, No y Desconocida pseudo-únicos que permitirán refinar eval un poco. Todavía podría hacer simples comprobaciones cuando sepa que su valor será sí o no, pero si intentó hacer un bool recto() (es decir, si x) en Desconocido, obtendría un TypeError. Sin embargo, esto haría su código más explícito, ya que cada vez que verificara un valor del tipo trinario, tendría que definir en su código cómo deseaba que se tratara desconocido en el contexto de ese condicional, por lo que sería un plus .

Edit: Pensé en una alternativa que requeriría un manejo menos especial pero menos flexible. Alterar por encima de esta manera:

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

Uso:

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' 

Esto tiene la ventaja de permitir distinto de cero para trabajar como llamadas de especificaciones en el desconocido, pero tiene la desventaja de tener la función eval para el conjunto desconocido en piedra de ejemplificación y de no permitir que Unknown sea un pseudo-singleton.

+0

'__nonzero__' debe devolver un' bool' o un 'int' - Ninguno no está permitido. –

+0

@Ethan Furman, y de ahí por qué señalé varias veces que esto no cumple con las especificaciones. Y por qué proporcioné una alternativa en la parte inferior que lo hace. ¿Tiene información nueva para agregar? –

+0

Ah, acaba de leer su publicación más a fondo. Si va a hacer que se levante una excepción cuando se desconoce el valor, es mejor definir su propia excepción y plantearla en lugar de que parezca un error. Para una implementación completa de la lógica ternaria, puede verificar el enlace en mi respuesta. (Si quiere ir con una clase de excepción personalizada en su ejemplo, o cualquier otra cosa que no parezca un error, felizmente cambiaré mi voto.) –

1

Tengo una clase lógica de 3 valores en mi dbf module.

Implementa False/True/Unknown como valores únicos Falsth/Truth/Unknown. Implementa todos los operadores de comparación, y también permite la comparación con los singletons de Python False/True/None (None tomado como el significado desconocido). Cualquier comparación con un Unknown resultados de valor en Unknown, y un intento de utilizar de manera implícita un valor Unknown en un if declaración (u otro bool contexto) aumentarán una TypeError, aunque Truth y Falsth puede utilizarse en contextos booleanos, y Unknown se pueden comparar contra directamente.

Debido a que no es posible anular el comportamiento and, or, y not el tipo anula la operadores bit a bit &, |, y ~.

También implementa __index__ con Unknown teniendo el valor de 2.

Ejemplo:

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." 
Cuestiones relacionadas