furas.pl
# prywatne notatki - Python, Linux, Machine Learning, etc.

Operator trójargumentowy w Pythonie

2019.02.19 - wtorek / kategoria: Python / tagi: python /

Operator trójargumentowy (ang: ternary operator) zwany też operatorem warunkowym pozwala zwrócić jedną z dwóch wartości na podstawie wyniku badanego warunku.

Jeśli warunek daje wynik "prawda" to zwracana jest pierwsza wartość. Jeśli warunek daje wynik "fałsz" to zwracana jest druga wartość.

W wielu językach operator trójargumentowy znany jest jako ?: (nawet w Wikipedii jego angielski opis widnieje pod hasłem ?:) i ma składnię

sprawdzany_warunek ? wartość_zwracana_gdy_prawda : wartość_zwracana_gdy_fałsz

Przykłady użycia w PHP (podobnie wygląda to w C i pochodnych)

echo $a != 0 ? "różne od zera" : "równe zero";

# lub

$rok_przestepny = true;

$luty = $rok_przestepny ? 29 : 28;

W Pythonie operator trójargumentowy ma to samo działanie ale inną składnię

wartość_zwracana_gdy_prawda if sprawdzany_warunek else wartość_zwracana_gdy_fałsz

Operator zwraca jedną z wartości, którą można przypisana do zmiennej

luty = 29 if rok_przestepny else 28

co odpowiada zwykłemu if/else

if rok_przestepny:
    luty = 29
else:
    luty = 28

Zwracaną wartość można też od razu użyć w innym działaniu

luty = 28 + (1 if rok_przestepny else 0)

co mogło by odpowiadać

luty = 28

if rok_przestepny:
    luty += 1
else:
    luty += 0

Wartość z operatora można też przekazać od razu do funkcji

print("równe zero" if zmienna == 0 else "różne od zera")

co mogło by odpowiadać

if zmienna == 0:
    _wartosc_ = "równe zero"
else:
    _wartosc_ = "różne od zera"

print(_wartosc_)

Operator może też wywoływać funkcje

print("równe zero") if zmienna == 0 else print("różne od zera")

co odpowiada

if zmienna == 0:
    print("równe zero")
else
    print("różne od zera")

Pierwszy przykład z print() jest popularniejszy w użyciu za to drugi lepiej pokazuje podobieństwo do zwykłego if/else

W operatorze nie da się jednak użyć wyrażeń - przykładowo przypisania wartości czy pass - które nie zwracają wartości.

Poniższe przykłady nie zadziałają

(luty = 29) if rok_przestepny else (luty = 28)

# Syntax error

print("ten rok jest o dzień dłuższy") if rok_przestepny else pass

# Syntax error

Operator trójargumentowy może wywoływać różne funkcje i wyniki przez nie zwrócone przypisać do zmiennej

wynik = a + b if operacja == "dodawanie" else a - b

co odpowiada

if operacja == "dodawanie":
    wynik = a + b
else:
    wynik = a - b

Tak samo może wynik działania jednej funkcji przekazać jako argument w innej funkcji

operacja = "dodawanie"

print(operacja, a + b if operacja == "dodawanie" else a - b)

# dodawanie 17

operacja = "odejmowanie"

print(operacja, a + b if operacja == "dodawanie" else a - b)

# odejmowanie 3

co odpowiada

if operacja == "dodawanie":
    print(operacja, a + b)
else:
    print(operacja, a - b)

Całość operatora trójargumentowego należy wpisać w jednej linii i nie wymaga : na końcu if i else.

Operator trójargumentowy - w przeciwieństwie do normalnego if/else - nie może zawierać więcej niż jednej funkcji.

Nie da się więc normalnie przerobić poniższego na operator trójargumentowy.

if rok_przestepny:
   print('Luty ma o jeden dzień więcej')
   luty = 29
else:
   print('Luty ma tylko 28 dni')
   luty = 28

Można wprawdzie obejść pewne przypadki poprzez użycie listy

[print("równe"), print("zero")] if a == 0 else [print("różne od"), print("zera")]

ale także w liście nie wszystko jest dozwolone - przykładowo nie da się przypisać wartości

[print("równe zero"), luty = 28] if a == 0 else [print("różne od zera"), luty = 29]

# Syntax error

Dodatkowo kod z użyciem listy staje się mniej czytelny niż z normalnym if/else


Operator trójargumentowy może być przydatny w wyrażeniach listowych (ang: list comprehension)

data = ["parzysta" if x % 2 == 0 else "nieparzysta" for x in range(5)]

print(data)
['parzysta', 'nieparzysta', 'parzysta', 'nieparzysta', 'parzysta']

co odpowiada

data = []

for x in range(5):
    if x % 2 == 0:
        data.append("parzysta")
    else:
        data.append("nieparzysta")

print(data)

Bardziej rozbudowany przykład

data = [(x, "parzysta" if x % 2 == 0 else "nieparzysta") for x in range(5)]

print(data)
[(0, 'parzysta'), (1, 'nieparzysta'), (2, 'parzysta'), (3, 'nieparzysta'), (4, 'parzysta')]

Dodatkowo operatory trójargumentowe można składać co daje efekt użycia if/elif/else

data = [(x, "zero" if x == 0 else ("ujemna" if x < 0 else "dodatnia")) for x in (1,0,-5,2,-2,0)]

print(data)
[(1, 'dodatnie'), (0, 'zero'), (-5, 'ujemna'), (3, 'dodatnia'), (-2, 'ujemna'), (0, 'zero')]

co odpowiada

data = []

for x in (1,0,-5,2,-2,0):
    if x == 0:
        data.append( (x, "zero") )
    elif x < 0:
        data.append( (x, "ujemna") )
    else:
        data.append( (x,  "dodatnia") )

print(data)

Można złożyć więcej niż dwa operatory trójargumentowe i otrzymać odpowiednik if/elif/else z większą ilością elif.

Taki kod staje się jednak długi i nieczytelny więc czytelniejsze wydaje się jednak użycie funkcji

def zamien(x):
    if x == 0:
        return (x, "zero")
    elif x < 0:
        return (x, "ujemna")
    else:
        return (x,  "dodatnia")

data = [zamien(x) for x in (1,0,-5,2,-2,0)]

print(data)

Operator trójargumentowy może też być zastosowany w lambda

sorted(range(10), key=lambda x: x if x % 2 == 0 else -x)

# [9, 7, 5, 3, 1, 0, 2, 4, 6, 8]

Operator trójargumentowy może zastąpić if/else lub if/elif/else ale nie może zastąpić samodzielnego if bez else.

Nie da się wstawić pass po else lub przed if aby pominąc jeden z przypadków.


Przez niektórych operator trójargumentowy jest uważany za lukier syntaktyczny (ang: syntatics sugar czyli cechę składni języka, którą daje się zastąpić inną składnią ale istnieje ona dla przyjemności programisty.


Wikipedia: operator warunkowy (ang: ternary operator), operator trójargumentowy w instrukcja warunkowa


Uwaga: w Python 3.8 pojawił się operator := (zwany po angielsku walrus co znaczy mors), który przypisuje wartość do zmiennej i zwraca tę wartość dalej co pozwala na wykonanie poniższego

(luty := 29) if rok_przestepny else (luty := 28)

Operator jednak powstał z myślą o innym przeznaczeni i spodziewam się, że powyższe wykorzystanie nie będzie jednak preferowane.

Już raczej będzie się go wykorzystywać do zwracania wartości funkcji lub wartości domyślnej.

wynik = (kwota if (kwota := podatek()) > 0 else 0)

zamiast

wynik = (podatek() if podatek() > 0 else 0)

Szczególnie to będzie przydatne gdy każde wywołanie funkcji zajmuje bardzo dużo czasu (więc po co wywoływać go dwa razy) lub każde wywołaniem zwraca inny wynik (np. iteratory)

wynik = (linia if len(linia := fp.readline()) > 0 else "")

zamiast błędnie dzałającego (bo każde wywołanie readline zwraca inną linię)

wynik = (fp.readline() if len(fp.readline()) > 0 else "- pusta -")

Będzie to pewnie przypominało operatory, znane z innych języków, które zwracają wartość funkcji lub wartość domyślną

(ang: elvis operator)

Książki: python-dla-kazdego-podstawy-programowania python-wprowadzenie python-leksykon-kieszonkowy python-receptury python-programuj-szybko-i-wydajnie python-projekty-do-wykorzystania black-hat-python-jezyk-python-dla-hackerow-i-pentesterow efektywny-python-59-sposobow-na-lepszy-kod tdd-w-praktyce-niezawodny-kod-w-jezyku-python aplikacje-internetowe-z-django-najlepsze-receptury