Wpis z mikrobloga

# Python 3.8

Niedawno miała miejsce premiera Pythona 3.7, który przyniósł ze sobą kilka ciekawych rzeczy, jak *async*, *await*, *opóźnioną ewaluację adnotacji typów* czy *breakpoint()*. Pisałem już o tym na blogu. jak i na Steemit.

Prace nad nowymi wersjami jednak nie ustają i nadchodząca wersja - Python 3.8 prawdopodobnie również przyniesie ze sobą coś ciekawego, o czym ostatnio sporo się rozmawia.

# Co to takiego?

Otóż chodzi o możliwość przypisywania zmiennych nie tylko w stwierdzeniach a również i w wyrażeniach. Cóż to takiego znaczy?

Mała notka, nie jestem pewien, czy poprawnie przetłumaczyłem te zdania. Raczej nie korzystam z polskich źródeł i nie wiem, czy takich też używają polscy autorzy. Dla pewności chodzi mi o statement i expression.

# PEP 572

W tym dokumencie, tej propozycji wprowadzenia zmiany, którego autorami są Guido van Rossum, Chris Angelico czy Tim Peters, proponowane jest, by od wersji 3.8 Python wspierał przypisywanie zmiennych nie tylko w stwierdzeniach, ale również i wyrażeniach, za pomoca operatora NAME := expr.

No dobrze, ale co to znaczy w praktyce. Spójrzmy na kod.

data = None
if ourfunctiongettingjson(somearg) is not None:
data = ourfunctiongettingjson(somearg)
data.dostuff()

Raczej proste do zrozumienia, zasadne, racja? Przykład z pewnego kodu wzięty. Przykład brzydki. Powyższy kod ssie w tym kontekście.
Jak można by go poprawić?

data = our
functiongettingjson(somearg)
if data is not None:
data.do
stuff()

Możemy zrobić coś takiego, ale czy to najkrócej jak się da, najlepiej jak się da? Obecnie raczej tak, ale...

Fajnie by było, gdyby można było zadeklarować tą zmienną tam w tym ifie - zapisać po prostu wynik funkcji tam, gdzie jest ona pierwotnie używana. Ważne jest to tam, gdzie chcemy później np. wykonać jakieś operacje na wyniku wyrażenia, które wykonaliśmy, np. w warunku, ale przez to, że w wyrażeniach obecnie nie można zapisywać zmiennych, to musimy zapisać ją sobie sami, wcześniej. Czy to w pętlach, czy w list comprehensions, lambda functions czy w innych.

Obecnie inaczej się nie da. To znaczy, w tym wypadku:

if (data = ourfunctiongettingjson(somearg)) is not None:
data.dostuff()

Jedyne co otrzymamy, to błąd. Taki zapis jest nieprawidłowy.

Od Pythona 3.8 najprawdopodobniej będziemy jednak mogli zrobić coś podobnego:

if (data := our
functiongettinjson(somearg)) is not None:
data.do
stuff()

Krócej, czyściej, lepiej.

Popatrzmy na inne przykłady.

if (match := pattern.search(data)) is not None:
match.dostuff()

Ponownie, zamiast musieć wcześniej definować zmienną, zapisać do niej dane wyrażenie, sprawdzić ją potem, możemy zrobić to w jednym miejscu.

while (value := read
nextitem()) is not None:
...

Alternatywa do 2 argumentowej wersji inwokacji iter().

Teraz coś ciekawego - użycie tego w list comprehensions. Wydaje mi się, że to tutaj ten proponowany element języka będzie błyszczał.

filtered
data = [y for x in data if (y := f(x)) is not None]

Inny fajny przykład, gdzie można użyć tego operatora:

results = [(x, y, x/y) for x in inputdata if (y := f(x)) > 0]
# lub też coś takiego jak niżej
stuff = [[y := f(x), x/y] for x in range(5)]

# Gdzie to nie przejdzie?

Na razie podałem wam przykłady tego, gdzie nowego operatora := można by użyć, ale gdzie będzie to zakazane?

Zacznijmy od pierwszego obostrzenia - := nie będzie można używać po prostu jako zamiennika =. To by było bez sensu - dwa operatory wykonujące tą samą czynność w tych samych miejscach. Skąd wtedy wiedzieć, którego lepiej użyć?

Zatem, jeśli wcześniej mieliśmy kod:

something = 'lalala'
something2 = 'hey'

I zmienimy go na:

something := 'lalala' # BŁĄD
something2 = 'hey' # OK

Bo tak po prostu jakoś nam się uwidzi, to kod taki nie zadziała. Nie ma zatem takiego miejsca, gdzie operator := i = byłyby jednocześnie poprawne. Zwykłe, tradycyjne przypisanie? =. Miejsce, gdzie nie możesz użyć =, czyli domyślnie wyrażenie? :=. Prosta sprawa, nie inaczej.

Nie będzie ich też można 'łączyć ze sobą.' Co to znaczy?

y = y1 := f(x) # BŁĄD

Taki zapis nie przejdzie. Ponownie byłoby to niepotrzebne, mylące i zbędne. Nie chcemy redundancji w Pythonie. Wszystko ma być czytelne, dlatego też taką operację można zastąpić następującym zapisem:

y = (y1 := f(x)) # OK

Innym tego przykładem mogło by być:

bar(x = y := f(x)) # BŁĄD
bar(x = (y := f(x))) # OK

To również nie zadziała - bez nawiasów nie wolno używać przypisania w wyrażeniu jako wartości dla keyword-argument. Ponownie - żeby czytelność była.

Dyskusja toczy się, czy zapis poniżej powinien być dozwolony

foo(x=0, y := f(0))
bar(x := 0, y = f(x))

Czyli używanie keyword-arguments i przypisania w wyrażeniu jednocześnie w wywołaniu funkcji. Co z tego wyjdzie, to się okaże.

Przypisania w wyrażeniu nie będzie można też użyć podczas definiowania domyślnych wartości argumentów funkcji czy też w pośrednich jego wyrażeniach. Co to znaczy?

def foo(answer = p := 42): # BŁĄD
def bar(answer = (p := 42)): # BŁĄD
def baz(callback = (lambda arg: p := arg)): # BŁĄD

To chyba tyle, jeśli chodzi o główne przypadki gdzie można, a gdzie nie można będzie użyć :=. Oczywiście, to, że dana konstrukcja jest dozwolona, nie znaczy, że należy jej używać w każdym miejscu, gdyż większość z tych możliwości wymienionych wyżej, jest niepotrzebnie komplikującym elementem kodu i raczej nie powinno się ich tak wykorzystywać, := powinien byc używany tam, gdzie faktycznie uprości on kod, a nie wszędzie, bo to jakaś fajna nowinka technologiczna.

# Różnice między dwoma operatorami

Różnice są widoczne chociażby wtedy, kiedy chcemy przypisać wartości do wielu zmiennych jednocześnie. Jak to wygląda?

x = y = z = 0
(x := (y := (z := 0)))

Bardzo brzydki konstrukt, racja?

Przypsanie w wyrażeniu może być tylko w postaci NAME := exp, to znaczy, że takie operacje jak:

x[0] = 0
some
obj.prop1 = 'something'

Nie będą wspierane przez NAME := exp.

Podobnie +=, -=, *= itd. również nie zadziała podczas używania :=.

x += y

Trzeba będzie to zrobić tak:

(x := x + y)

# Podsumowanie

Nowego operatora przypisania w wyrażeniu, będzie można użyć do *uproszczania list comprehensions, zapisywania wartości warunków czy innych wyrażeń.*

Oczywiście nic nie jest jeszcze pewne, wciąż prowadzi się dyskusje na temat tego, jak to wszystko ma wyglądać i działać, zmienić się może nawet sam operator, bo rozważane, zamiast :=, są jeszcze as albo -> itd., ale taki mniej więcej będzie tego obraz.

Artykuł można przeczytać też na moim blogu, o tutaj. O tyle tam ładniej, że kolorowanie składni jest.
Albo na steemit, tu, jeśli ktoś korzysta.
#programowanie #mlodyprogramuje #python
  • 8
@KrzaQ2: jak wspomniałem, nie każdy z dozwolonych zapisów jest czytelny i warto go używać, ale z tego co podałeś, jak dlam nie coś takiego:

filtered_data = [ (y := f(x)) for x in data if y is not None]

jest dość czytelne i przyjazne a jest to kolejna z dozwolonych form
po prostu kwestia tego, jak my to napiszemy, jak akurat podałem przykłady głównie z pepa
@11001100110O11:

filtereddata = [y for x in data if (y := f(x)) is not None]

ale to jest masakrycznie nieczytelne.


@KrzaQ2: Też mi się tak wydawało i zajrzałem do tego PEP, i tam sa lepsze przykłady, np.:

if match := re.search(pat, text):
_____print("Found:", match.group(0))

jest oczywiście przykład is not None, ale mnie on od razu raził w oczy i pewnie zapis:

filtered_data = [y for x in data if
@KrzaQ2: Szczerze powiedziawszy mi się też to niespecjalnie widzi, jakby się uprzeć to pewnie każdą wersję by się dało zrobić. Dla mnie istotniejsze jest, jak bardzo funkcjonalnie istotny to przykład. Przykład z match mi się podoba (tzn. widzę korzyści).