Publi

Píldora: Operador ternario en Python y en C (con ejemplos)

Operador ternario en Python

En programación, hay un momento en el que descubres ciertas técnicas. Y, a partir de ese momento, ya no puedes vivir sin ellas. Eso me sucedió con el operador ternario. Esta estructura nos permite ahorrar código, lo que significa escribir menos líneas y hacerlas más sencillas. Tomaremos tres argumentos, uno de ellos será una condición y los otros dos serán valores (llamémosles a y b). Básicamente, puede solucionar una situación muy común:

Si condición se cumple, la variable c = a. Si no se cumple, la variable c = b

En lenguaje C y derivados

Esto, en lenguajes como C, y cualquier otro que herede parte de su sintaxis, como C++, PHP, Java, Javascript, C#, Swift y muchos más, podemos hacerlo con ?: de la siguiente forma:

1
variable = (condicion)?valor_si:valor_no;

Por supuesto donde digo condición, podemos poner todas las condiciones que queramos, resultado de funciones o cualquier cosa que me pueda devolver un valor verdadero o falso.

Por ejemplo, podemos tener este código (por poner algo funcional):

1
2
3
4
5
6
7
8
9
10
#include <stdio.h>

int main()
{
  int mayor, a=10, b=12;

  mayor = (a>b)?a:b;

  printf ("El mayor es: %d\n", mayor);
}

Aunque no estamos limitados a asignación de variables, podríamos por ejemplo hacer lo siguiente:

1
2
3
4
5
6
7
8
#include <stdio.h>

int main()
{
  int a=10, b=12;

  printf ("El mayor es: %d\n", (a>b)?a:b);
}

Podemos incluso, hacer llamadas a funciones dependiendo de si se cumple o no la condición:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <stdio.h>

int funcion1()
{
  printf ("Has llamado a la funcion 1\n");
}

int funcion2()
{
  printf ("Has llamado a la funcion 2\n");
}

int main()
{
  int a=10, b=12;

  ((a>b)?funcion1:funcion2)();
}

En este último ejemplo es importante el paréntesis que rodea a la expresión, ya que el resultado de (a>b)?funcion1:funcion2 es lo que tiene que ejecutarse. En definitiva, esto tiene muchas posibilidades y podemos usarlo de muchas maneras. Aunque la evaluación de la expresión siempre será de la misma manera.

El caso de Python

Empecé con Python hace algo menos de un año gracias a unas charlas organizadas por Linux Málaga. Desde el primer momento a mí me picó la curiosidad de cómo utilizar el operador ternario o, al menos, simular su comportamiento. Es un lenguaje con mucha potencia, sencillo, y capaz de hacer muchas cosas con poco código. Así que, ¿por qué andarnos repitiendo código para estas cosas?

Es cierto que muchos desarrolladores no lo consideran demasiado pitónico, aunque yo no lo creo así. Y, como siempre, en Python, tenemos varias formas de hacer las cosas, aprovechándonos de su flexibilidad.

Expresiones condicionales

Esta forma, válida desde Python 2.5 (Septiembre de 2006) utiliza if y else. Lo raro, es que el orden de los elementos difiere un poco del operador ?:. En este caso es:

1
resultado = valor_si if condicion else valor_no

Podemos hacer algo como esto. Que nos imprimirá el número introducido sólo si es mayor a 50, si no, imprimirá 0 (aunque utilizar input sea algo inseguro para hacer un ejemplo vale):

1
2
3
c = input("Introduce un número: ")
r = c if (c>50) else 0
print(r)

Como en los ejemplos anteriores de C, podremos utilizarlo incluso para hacer llamadas a funciones:

1
2
3
4
5
6
7
8
def func_si():
    print ("SIIIIIUUUU")

def func_no():
    printf ("NOOOOOOU")

respuesta = raw_input('Introduzca "si" o cualquier otra cosa.')
func_si() if (respuesta=='si') else func_no()

E incluso lo podemos utilizar para asignar varios valores a varias variables al mismo tiempo. En el ejemplo lo hacemos en función de un valor pseudo-aleatorio entre 0 y 100:

1
2
3
import random
(a,b,c,d) = (1,2,3,4) if (random.randint(0,100)>50) else (4,3,2,1)
print (a,b,c,d)

Utilizando tuplas

Aunque, podemos escoger otra forma de hacer las cosas. En Python, es muy cómodo crear tuplas, o listas ordenadas de elementos. Así que podríamos coger una lista de dos elementos (uno para cuando se cumple la condición y otro para cuando no se cumple) y dado que True es 1 y False es 0, escoger la posición en función del cumplimiento de la condición.
Para no liarnos mucho, aquí va el código:

1
2
3
a = 10
b = 12
mayor = (b, a)[a>b]

Lo primero que nos puede chocar de este enfoque es que hay que invertir el orden de los valores. Es decir, el primer valor que ponemos es el que obtendremos cuando no se cumpla la condición, y el segundo será cuando sí se cumpla. Ya que, cuando no se cumple, el valor obtenido al evaluar la condición es False, que equivale a 0. Y cuando sí que se cumple, obtendremos True, que equivale a 1.
Por otro lado, tendremos un comportamiento ligeramente diferente a la hora de realizar la evaluación. Lo primero que hacemos es rellenar la tupla, por lo que estaremos escribiendo en memoria los dos valores resultantes. El problema de esto, es que:

  • Si los valores son muy grandes ocuparemos mucha memoria, porque tenemos que almacenar los dos.
  • Si b y a son llamadas a funciones, se llamará a las funciones antes de evaluar la condición, por lo que gastaremos tiempo de CPU innecesariamente.

Podemos ver este ejemplo más claro:

1
2
3
4
5
6
7
8
9
10
11
12
import random

def diez():
    print ("EJECUTAMOS DIEZ")
    return 10

def doce():
    print ("EJECUTAMOS DOCE")
    return 12

mayor = (diez(), doce())[(random.randint(0,100)>50)]
print(mayor)

Si ejecutamos este código veremos cómo se imprimen en pantalla los mensajes de las funciones diez() y doce() cuando en realidad deberíamos ver sólo uno, generado cuando, tras evaluar la posición de la tupla se escoja un elemento. Imaginemos que las funciones diez() y doce() pueden llevar a ser muy lentas o, que realizan cambios incompatibles en una base de datos y sólo debemos ejecutar una de ellas.

Para solucionar esto, podemos utilizar lambdas, haciendo la condición de la siguiente forma:
Para no liarnos mucho, aquí va el código:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
a = 10
b = 12
mayor = (lambda:b, lambda:a)[a>b]()
[cc]

De esta forma creamos funciones anónimas que evaluaremos (para eso el () final) cuando escojamos el elemento en función de la condición. El código anterior quedaría así:
[cc lang="python"]
import random

def diez():
    print ("EJECUTAMOS DIEZ")
    return 10

def doce():
    print ("EJECUTAMOS DOCE")
    return 12

mayor = (lambda:diez(), lambda:doce())[(random.randint(0,100)>50)]()
print(mayor)

Con diccionarios, lambdas y a lo loco

Para completar aún más esto, podemos poner en orden los valores. Es decir, si estamos acostumbrados a poner primero el valor a devolver cuando la condición se cumple y luego el otro, podemos utilizar un diccionario (o en el ejemplo anterior ponerle un not a la condición). Y para que no se evalúe todo antes de la condición, también utilizar lambdas:

1
2
3
4
5
6
7
8
9
10
11
12
import random

def diez():
    print ("EJECUTAMOS DIEZ")
    return 10

def doce():
    print ("EJECUTAMOS DOCE")
    return 12

mayor = {True:lambda:doce(), False:lambda:diez()}[(random.randint(0,100)>50)]()
print(mayor)

¿Más formas?

Python da mucho juego. Por ejemplo, podemos utilizar booleanos para conseguir el mismo efecto. De forma que hagamos:

1
mayor = ((a>b) and [a] or [b])[0]

En este caso, devolveríamos a cuando se cumpla la condición y en caso contrario b. Podríamos, no hacer la lista [] con un elemento solo. Si os fijáis creamos una lista que sólo contenga a y una lista que sólo contenga b. Y al final, elegimos el primer elemento de esas listas, que sería a o b. Podríamos simplificarlo haciendo:

1
mayor = (a>b) and a or b

Aunque si el valor de a es False, 0 o None, mayor será igual a b. Por lo que el comportamiento puede que no sea el deseado. Metiendo los elementos en listas salvaríamos el problema.

Operador Elvis

Operator Elvis. Elvis Operator
Y como ?: parece el emoticono de Elvis, o eso dicen. Tenemos el operador Elvis, derivado del ternario. Que nos hará la vida un poco más fácil en algunos lenguajes del estilo C (en C o C++ no todos los compiladores lo soportan porque no forma parte del lenguaje C). El operador Elvis también se llama operador condicional nulo, o Null Conditional Operator.

Este operador funciona así:

1
resultado = valor_1 ?: valor_2;

En este caso, resultado valdrá valor_1, siempre y cuando valor_1 no sea algo falso, como 0, nulo, etc, en ese caso, resultado valdrá valor_2. Es muy útil para establecer valores por defecto a variables. O cuando en C un puntero debe apuntar necesariamente a un objeto o función y ésta tiene valor NULL. Por ejemplo:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include <stdio.h>

void spanish()
{
  printf ("Hola!\n");
}

void english()
{
  printf ("Hello\n");
}

void french()
{
  printf ("Salut!\n");
}

void saluda(void (*idioma)())
{
  printf ("Te voy a saludar\n");
  (idioma?:&english)();
}

int main()
{
  saluda(&spanish);
  saluda(&french);
  saluda(NULL);
}

En este caso, saluda() llamará a una función según el idioma deseado. Pero el idioma podría ser NULL, y cuando vale NULL, se tomará el idioma inglés. Para eso evaluamos:

1
  (idioma?:&english)();

Haciendo que se llame a idioma si no vale NULL, y si éste es NULL, llamaremos a la función english().

En otros lenguajes como PHP nos puede servir por ejemplo para determinar el nombre de usuario conectado.

1
2
<?php
$userName = $currentUser['userName'] ?: 'invitado';

En este caso, si el nombre de usuario no existe en mi variable $currentUser, cogeremos de nombre de usuario ‘invitado’

¿Y en Python?

Ya que estábamos hablando de Python. En dicho lenguaje no tenemos este operador, como tampoco teníamos el ternario. Pero podríamos simular su comportamiento de la siguiente manera:

1
userName = currentUser['userName'] or 'invitado'

Lo que hacemos es utilizar el operador or que evaluará a verdadero siempre que, al menos uno de los dos extremos sea verdadero. Siguiendo esta norma, primero evalua currentUser[‘userName’], si esto tiene un valor que no sea falso, nos quedamos ahí y lo devolvemos como userName, pero si no, devolveremos ‘invitado’ que sí que es un valor que no es falso (es una cadena con contenido). Si los dos extremos son falsos, ya sí que userName será falso.

Incorpóralo en tus programas

Seguro que a partir de ahora empezarás a utilizar tanto el operador ternario, como el operador Elvis. O buscarás en tu lenguaje de programación el mismo comportamiento. Me encantaría ver comentarios con vuestros ejemplos.
Foto principal: Tom Parsons

También podría interesarte...

Only 1 comment left Ir a comentario

  1. Pingback: Píldora: Operador ternario en Python y en C (con ejemplos) | PlanetaLibre /

Leave a Reply