Posts tagged ‘Python’

Typy wyliczeniowe w Pythonie

2011-02-24 23:52

W Pythonie jest wiele konstrukcji językowych, które wydawać się mogą dziwne dla programistów przyzwyczajonych do innych języków. O kilku z nich już pisałem, a o paru innych pewnie zdarzy mi się jeszcze napomknąć. Dzisiaj jednak chcę wspomnieć o mechanizmie dobrze znanym z wielu innych języków, którego Python nie posiada w ogóle i jakoś sobie z tym brakiem radzi. Mam tu na myśli tytułowe typy wyliczeniowe, czyli enumy.

Jeśli zazwyczaj programujemy w językach kompilowanych ze ściśle kontrolowanymi typami, taki brak może się wydawać co najmniej irytujący. Wiemy oczywiście, że podobną funkcję może pełnić zestaw odpowiednich stałych, ale odpowiedniość nie jest zwykle dokładna – w Javie czy C# konstrukcja enum tworzy na przykład dodatkowy zasięg. Lecz nie jest to jedyna i prawdopodobnie też najważniejsza różnica.
Zdaje mi się raczej, że kluczową cechą typów wyliczeniowych jest to, że definiują one tylko pewien abstrakcyjny zbiór możliwości – bez konieczności ustalania, czym dokładnie jest każda z nich. Naturalnie wiadomo, że “pod spodem” są to po prostu liczby (aczkolwiek w Javie jest trochę inaczej), ale nie musimy się zastanawiać, skąd się one wzięły. Nie musimy nawet wiedzieć, do jakiego typu liczbowego one należą, choć niekiedy (np. w C#) możemy to doprecyzować.

Ta ostatnia cecha nie jest jednak niczym niezwykłym w języku o dynamicznym typowaniu, takim jak Python. Nieokreśloność typu dotyczy tu bowiem każdej zmiennej i dlatego nie za bardzo pasuje tu koncepcja ograniczania jej wartości do jakiegoś z góry ustalonego zbioru. Technicznie rzecz ujmując, nie bardzo też da się to zrobić.
Podobnie niezbyt pasującą do Pythona koncepcją jest sterowanie logiką za pomocą zbioru wariantów wziętych “znikąd”, czyli stałych wyliczeniowych o automatycznie generowanych wartościach. Brak w tym języku instrukcji switch jest pewnie również konsekwencją odejścia od tego rodzaju abstrakcji. Założenie jest raczej takie, aby w miarę możliwości operować na surowych danych i nie dokonywać na nich żadnych pojęciowych “wygładzeń”. Ma to sens, gdyż dwa podstawowe cele abstrahowania wartości na zbiór przypadków – upraszczanie API i zwiększanie efektywności – niespecjalnie aplikują się do Pythona.

Jak to jednak bywa w prawdziwym świecie, coś w rodzaju typów wyliczeniowych przydaje się czasami mimo wszystko. Odpowiedzią jest wtedy rzeczywiście zestaw stałych, zapewne zgrupowanych pod szyldem wspólnego zasięgu klasy. Ponieważ musimy nadać im wartości, możemy zadbać o to, by bezpośrednio odnosiły się do danych, które przetwarzamy. A jeśli w skrajnym przypadku trzeba faktycznie wziąć je z powietrza, wystarczy zastosować poniższy idiom z rozpakowywaniem range‘a:

  1. class SomeEnum(object):
  2.     FOO, BAR, LULZ, KEK = range(4)

Istnieją oczywiście bardziej wyrafinowane rozwiązania, pozwalające chociażby na iterowanie po wszystkich nazwach i wartościach naszego enuma. Sądzę jednak, że podobna funkcjonalność jest przydatna raczej rzadko.

Tags: , ,
Author: Xion, posted under Programming » 1 comment

Więcej niż operatory logiczne

2011-02-11 20:44

Bardzo przydatną cechą operatorów logicznych w wielu językach programowania jest leniwa ewaluacja (lazy evaluation). Polega ona na pominięciu obliczania tych argumentów operatorów && (and) i || (or), które i tak nie mają szans wpłynąć na ostateczny wynik. To pozwala na tworzenie warunków podobnych do poniższego:

  1. if (obj != null && obj.Valid) { obj.DoSomething(); }

Drugi człon nie wykona się tutaj w ogóle, jeśli pierwszy okaże się fałszywy, więc zmienna obj ustawiona na null nie spowoduje błędu wykonania.
Oczywiście technika ta jest doskonale znana każdemu przynajmniej średnio zaawansowanemu programiście. Okazuje się jednak, że przynajmniej jeden język idzie dalej i uogólnia ją w sposób pozwalający na stosowanie operatorów and i or do argumentów niebędących wartościami logicznymi. Jaki to język?… Python, rzecz jasna :)

W Pythonie dwa standardowe operatory ‘logiczne’ działają w oparciu o możliwość określenia specyficznie pojmowanej prawdziwości wyrażenia, którego typem niekoniecznie jest bool. Mówiąc w skrócie, każde wyrażenie niepuste i różne od zera – łącznie z odwołaniami do obiektów, liczbami, tablicami, słownikami i innymi kolekcjami – jest uważane za prawdziwe, gdy wystąpi w kontekście wymagającym rozróżnienia prawdy i fałszu.
Takim kontekstem jest chociażby warunek instrukcji if lub while – ale nie tylko. Możliwość “rzutowania na bool” (fachowo nazywanego koercją) jest też wykorzystywana w definicji operatorów and i or, które są z grubsza następujące:

  • A and B jest równe:
    • A, jeśli A jest wyrażeniem fałszywym
    • B – w przeciwnym wypadku
  • A or B jest równe:
    • A, jeśli A jest wyrażeniem prawdziwym
    • B – w przeciwnym wypadku

W pierwszej chwili mogą one wydawać się dość skomplikowane, ale nietrudno jest zauważyć, że “działają” one zgodnie z oczekiwaniami wobec argumentów typu bool i mogą być obliczane leniwie. Ponieważ jednak dzięki nim rezultatem operatora nie jest po prostu True lub False, lecz jeden z argumentów, możliwe jest stosowanie and i or także wtedy, gdy wynikiem nie ma być wcale wartość logiczna.

Wbrew pozorom ma to czasem wielki sens. Oto bardzo typowy przykład kodu, który korzysta z tej sztuczki:

  1. s = get_a_string_from_somewhere() or "N/A"
  2. print s

Co tu się dzieje?… Jeśli wywołanie funkcji zwróci prawdziwą (czyli niepustą i niezerową, więc zapewne sensowną) wartość, jest ona wyświetlona. W przeciwnym razie korzysta się z napisu zastępczego. Dzięki elastycznemu operatorowi or łatwo więc można określić pewnego rodzaju wartość domyślną (czyli fallback) dla wyrażenia.
Z kolei operator and jest często wykorzystywany do warunkowego odwoływania się do “głęboko ukrytej” wartości, wymagającej przejścia przez ciąg kilku potencjalnie pustych odwołań:

  1. return obj and obj.wrapper and obj.wrapper.result

Jeśli któreś z nich jest równe None, to taki będzie rezultat całej konstrukcji. W przeciwnym razie wynikiem będzie ostatni argument.

Istnieje szansa, że przynajmniej jeden z powyższych mechanizmów wygląda znajomo, jeśli ma się doświadczenie w językach Java lub C#. Sztuczka z operatorem or odpowiada bowiem podwójnemu znakowi zapytania (??) z C#, zaś przykład z and wprowadzonemu w Javie 7 operatorowi ?. (znak zapytania i kropka).
Zapewne też w tym momencie wszyscy przypomną sobie o starym dobrym operatorze trójargumentowym, występującym we wspomnianych dwóch językach i jeszcze wielu innych. Okazuje się, że w Pythonie jego działanie też można symulować przy użyciu operatorów and i or:

  1. res = (condition and if_true) or if_false

Nie trzeba jednak tego robić, bowiem od wersji 2.5 istnieje nieco inny składniowo odpowiednik takiej konstrukcji:

  1. res = if_true if condition else if_false

Warto zwrócić uwagę na inną niż typowa kolejność wyrażeń w tej konstrukcji.

Tags: ,
Author: Xion, posted under Programming » 1 comment

Formatowanie tekstu

2010-12-12 19:03

Tematem budowania łańcuchów znaków zajmowałem się już kiedyś, badając efektywność tej operacji w C++ dla trzech różnych sposobów. Tym razem przyjrzę się sprawie z perspektywy bardziej niezależnej od języka. Ostatnio poczyniłem bowiem pewną obserwację. Zauważyłem mianowicie, że “tradycyjne” podejście do składania stringów – przez konkatenację kolejnych podciągów – staje się… hmm… niemodne, jeśli można tu użyć takiego określenia.

Ci, którzy kodują nieco dłużej niż ja pewnie zwrócą mi zaraz uwagę, że takie naprawdę tradycyjne podejście do problemu to to, które implementują funkcje z języka C podobne do printf:

  1. fprintf (stderr, "[%s] Error (errno=%d)\n", tag, errno);

Polega ono na uprzednim zdefiniowaniu wzorca tekstu (tzw. formatu, tutaj jest to drugi argument fprintf), zawierającego symbole zastępcze takie jak %s czy %d. Są one następnie zastępowane podanymi wartościami (tutaj: tag oraz errno) w celu wyprodukowania finalnego ciągu. Nie ma tu jawnej operacji łączenia dwóch napisów. Zamiast tego mały parser przegląda podany wzorzec i w miejscu symboli zastępczych wstawia do wyniku podane wartości, przepisując bez zmian resztę znaków.
Wkrótce jednak nadszedł C++ z klasami string i ostream, oferującymi podobne możliwości bez użycia wzorców. Zamiast tego posługiwały się operatorami + i << w celu wyprodukowania tekstowego wyjścia:
cerr << "[" << tag << "] Error (errno=" << errno << ")" << endl;[/cpp] To działa, ale - jak można zobaczyć w porównaniu - wygląda często znacznie mniej przejrzyście. Nie dziwi więc, że w tym względzie historia zdaje się zatoczyć koło. Obecnie bowiem operacja formatowania napisów (w sensie powyższej funkcji fprintf, a nie określania czcionki czy stylu tekstu) jest wbudowana w wiele nowoczesnych, popularnych języków programowania, jak choćby C#, Pythona czy Javę. Najczęściej też, co ciekawe, trzyma się ona z grubsza oryginalnej składni wzorców pochodzącej jeszcze z C, dodając oczywiście jakieś własne, bardziej zaawansowane rozszerzenia:

  1. exc_type, exc, _ = sys.exc_info()
  2. logging.error("[%s] %s" , exc_type.__name__, exc)
  1. Console.WriteLine("[{0}] {1}", e.GetType().Name, e.Message);

Czasami jednak – jak widać wyżej – tak nie jest :) Na obecność mechanizmu formatowania łańcuchów znaków możemy aczkolwiek liczyć w każdym szanującym się języku. I to łącznie z C++, gdzie wszystko prawie wszystko, czego w nim pierwotnie nie ma, zawiera się w jakiejś bibliotece boost – w tym przypadku jest to boost::format.

Tags: , , , ,
Author: Xion, posted under Programming » 5 comments

Małe kruczki

2010-11-22 8:31

Przeskakując między wieloma językami programowania, bibliotekami czy frameworkami po pewnym czasie można wyrobić sobie zdolności “uogólniające”, dzięki którym takie zmiany nie są specjalnie kłopotliwe. Jednak nawet jeśli potrafimy płynnie przechodzić od jednego języka do drugiego, to zawsze istnieje szansa, że zapomnimy przy okazji o jakimś szczególe, drobnej rzeczy specyficznej dla konkretnego narzędzia – tytułowym małym kruczku.
A przynajmniej mi się tak nierzadko zdarza. Dlatego tę notkę dedykuję pojedynczym, drobnym a “dziwnym” cechom poszczególnych języków programowania, o których często zdarza mi się zapominać. A są one następujące:

  • W C++ nie dodaje się stałych tekstowych. W dziesiątkach języków programowania (i nie tylko programowania) zapis w stylu "ala" + " ma " + "kota" jest najzupełniej oczywistą konkatenacją trzech napisów. Ale w C++ nie. Ponieważ stałe dosłowne, takie jak teksty w cudzysłowach, są – w przybliżeniu – typu const char*, ich łączenie operatorem + jest interpretowane jako dodawanie dwóch wskaźników. Serio.
    Wniosek: W C++ w takich sytuacjach plus jest zbędny i należy go pominąć "ala" " ma " "kota", o ile oczywiście nie łączymy obiektów klasy string.
  • W Javie łańcuchy znaków porównujemy… inaczej. Otóż Java jest bardzo obiektowa i w związku z tym nawet dla typu java.lang.String nie robi wyjątku i obsługuje go tak, jak każdą inną klasę. A to oznacza, że zwykły operator == działa dla napisów tak samo, jak dla innych obiektów (inaczej niż choćby w C#), tj. porównuje ich referencje w celu sprawdzenia, czy mamy do czynienia z tym samym obiektem. Tym samym, nie zaś takim samym, czyli równym co do wartości – a o to nam zwykle chodzi, gdy pracujemy z napisami.
    Wniosek: W Javie do porównywania napisów korzystamy z metody equals. To pewnie dlatego wciąż nie ma w niej switcha dla napisów ;-)
  • Słowniki w Pythonie domyślnie “iterują się” po kluczach. Żeby oszczędzić nadmiernie rozbudowanych wyjaśnień, rzucę poniższym kawałkiem kodu:
    1. for k, v in dictionary: print k, " => ", v

    który na pierwszy rzut oka wygląda na zupełnie poprawną iterację po zawartości słownika dictionary, w której zmienna k dostaje klucz, a v wartość. Jest on dowodem, że oczami należy rzucać zawsze przynajmniej dwa razy. W istocie bowiem k i v będą kolejno otrzymywały, uwaga, po dwa elementy klucza (potraktowanego jako para). Po prostu konwersja słownik → coś_po_czym_można_iterować daje w Pythonie kolekcję kluczy, a nie par klucz-wartość. Nie pytajcie, dlaczego.
    Wniosek: Albo iterujemy po kluczach i wartość uzyskujemy zwykłym indeksowaniem, albo korzystamy z metody items w celu uzyskania kolekcji par klucz-wartość.

  • W Javascripcie zmienne – nawet te użyte po raz pierwszy w funkcjach – są domyślnie globalne. Nawet nie śmiem dociekać, co jest uzasadnieniem dla tego faktu…
    Wniosek: Pierwsze przypisanie do nowej zmiennej wewnątrz funkcji należy opatrywać słowem kluczowym var.
Tags: , , , ,
Author: Xion, posted under Programming » 7 comments

Pierwsze kroki w Pythonie

2010-11-06 13:33

Python to już od dłuższego czasu bardzo popularny język do wszelakich zastosowań. Oprócz pisania w nim samodzielnych programów lub aplikacji webowych, jest on też często używany jako język skryptowy rozszerzający możliwości programów, czego chyba najbardziej znanym przykładem jest Blender. Istnieje więc duża szansa, że niezależnie od dziedziny IT, którą się zajmujemy, prędzej czy później przyjdzie nam zetknąć się z tym językiem.
W takim przypadku często dobrze jest wybiec przed szereg i zapoznać się z Pythonem już teraz, co by nie obudzić się potem w wielkim zaskoczeniu ;-) Na szczęście doświadczenie pracy z tym językiem należy raczej do przyjemnych, co oczywiście nie zmienia faktu, że dobrze jest co nieco o nim wiedzieć.

Dlatego też postanowiłem napisać krótką instrukcję paru kroków, które powinny pomóc wszystkim zainteresowanym w rozpoczęciu przygody z Pythonem. Chodzi tu przede wszystkim o przygotowanie kompilatora interpretera, narzędzi pomocniczych oraz środowiska programistycznego. Nie jest to, na całe szczęście, bardzo trudne:

  1. Zaczynamy od zaopatrzenia w sam podstawowy pakiet Pythona, czyli w interpreter i pakiety biblioteki standardowej języka. Jeśli korzystamy z jakiejś użytkownikowo-przyjaznej dystrybucji Linuksa (np. Ubuntu), to pewnie będziemy już takie coś posiadali. W przeciwnym razie wybieramy się na stronę Python.org i wybieramy wersję dla swojej platformy. Jeżeli jest to Windows, to zalecam wybranie wersji 32-bitowej niezależnie od bitowości samego systemu – inaczej możemy mieć problemy ze współpracą z pomocniczymi narzędziami.
    Aktualnie w obiegu są dwie edycje Pythona: seria 2.x i 3.x. W dalszych krokach zakładam używanie serii 2.x głównie ze względu na większą przenośność jej i narzędziowych aplikacji.
  2. Niemal niezbędnym programem pomocniczym przy korzystaniu z Pythona jest easy_install, będący częścią pakietu setuptools. Pozwala on na łatwe instalowanie dodatkowych pakietów, trochę tak jak apt-get na debianowych Linuksach. Jest to bardzo wygodne i skutkuje np. tym, że aplikacje napisane w Pythonie potrafią same rozwiązać swoje zależności podczas instalacji i ściągnąć pakiety wymagane do swojego działania.
  3. Jeśli nie zostało to zrobione za nas, powinniśmy dodać katalog instalacyjny Pythona (zawierający plik wykonywalny interpretera) oraz podkatalog Scripts (zawierający program easy_install) do zmiennej środowiskowej PATH. Ułatwi to między innymi późniejszą konfigurację środowiska programistycznego.
  4. Opcjonalnie możemy zaopatrzyć się w DreamPieshell dla Pythona, który jest o wiele wygodniejszy i potężniejszy niż wbudowany IDLE, że o systemowej konsoli nie wspomnę. Korzystanie z shella jest dość częste; dzięki niemu możemy bezboleśnie sprawdzić działanie funkcji i konstrukcji językowych, używanych w naszych programach.
  5. Wreszcie, czas zaopatrzyć się w środowisko programistyczne, czyli IDE. Tu do wyboru mamy kilka możliwości, zależnie od używanej platformy i preferencji, o czym szczegółowo informuje nas oficjalne wiki. Jeśli przypadkiem używamy już któregoś z dwóch popularnych, uniwersalnych IDE – czyli Eclipse lub NetBeans – możemy użyć wtyczek rozszerzających je o możliwość współpracy z Pythonem (odpowiednio PyDev oraz NBPython). Alternatywą jest użycie samodzielnych IDE do Pythona takich jak PyScripter.

Co dalej? No cóż, to już zależy od tego, do czego chcemy Pythona używać. W celu znalezienie inspiracji możemy na przykład przeglądnąć posortowane tematycznie drzewko dostępnych pakietów i wybrać coś, co nas interesuje. Następnie możemy zainstalować nowe pakiety za pomocą programu easy_install, podając po prostu nazwę paczki, lub ściągnąć je w postaci archiwum i uruchomić skrypt setup.py z parametrem install.
A co zrobić w celu nauczenia się samego języka?… Jeżeli nie odpowiada nam studiowanie samej dokumentacji, możemy spróbować poszukać w sieci odpowiedniego tutorialu, takiego jak np. dostępna za darmo ksiażka Dive Into Python.

Tags: ,
Author: Xion, posted under Applications, Programming » 5 comments

Udekorowane funkcje

2010-09-22 16:46

Kilkanaście dni temu opisywałem błąd, który – jak się okazało – był efektem ubocznym pewnej cechy języka Python, uznanej przeze mnie za niemal zupełnie niepotrzebną. Dla równowagi więc dzisiaj przedstawię feature, który wydaje się być bardzo przydatny – żeby nie było, że potrafię tylko krytykować ;-)

Prawdopodobnie najlepiej jest pokazać go na prostym, acz obrazowym przykładzie. Miejmy pewną funkcję na tyle dla nas istotną, że chcemy logować wszystkie jej wywołania. Wydaje się, że nic prostszego:

  1. import logging
  2.  
  3. def vif(): # Very Important Function :)
  4.     logging.debug("Called function vif()")
  5.     # ...

Ciężko jednak nazwać takie rozwiązanie uniwersalnym. Nie chodzi tu jedynie o jawnie wpisaną nazwę funkcji, ale raczej o konieczność dodawania logging.debug(...) na początku każdej funkcji, jaką chcemy monitorować. Sprawa komplikuje się jeszcze bardziej, gdy interesują nas też wyjścia z funkcji; wówczas jedynym wyjściem jest chyba opakowanie całej treści w jeden wielki blok tryfinally. Rezultat na pewno nie będzie piękny :)

I tutaj właśnie z pomocą przychodzą dekoratory – ciekawa opcja języka Python, na pierwszy rzut oka przypominająca adnotacje z Javy. Podobieństwo jest jednak głównie składniowe. Udekorowana wersja naszej ważnej funkcji wygląda bowiem tak:

  1. @trace
  2. def vif():
  3.     # ...

Znak @ poprzedza tutaj nazwę dekoratora, czyli trace (ang. śledź – i bynajmniej nie chodzi o rybę ;]). Czym jednak jest ów dekorator? Otóż on sam również jest funkcją, mogącą wyglądać choćby tak:

  1. import logging
  2.  
  3. def trace(func):
  4.     def _func(*args, **kwargs):
  5.         logging.debug("Calling function %s()", func.__name__)
  6.         return func(*args, **kwargs)
  7.  
  8.     return _func

Jej jedynym argumentem jest w założeniu funkcja, zaś rezultatem wywołania jest… również funkcja :) A zatem nasz dekorator potrafi przekształcić jedną funkcję w drugą, a dokładniej w jej udekorowaną, “opakowaną” wersję. To opakowanie definiowane jest wewnątrz dekoratora i polega, jak widać, na poprzedzeniu wywołania oryginalnej funkcji zapisem do loga.
Oczywiście za to, aby wywołanie funkcji opatrzonej dekoratorem było tak naprawdę odwołaniem do jej udekorowanej wersji odpowiada już sam język. Nie jest to zresztą skomplikowane, bo w istocie cały mechanizm jest tylko cukierkiem składniowym. Jest to jednak bardzo smaczny cukierek, który potrafi wydatnie podnieść czytelność kodu – jeśli stosuje się go właściwie.

Do czego jednak – oprócz logowania wywołań – dekoratory mogą się przydać? Ano przede wszystkim do upewniania się, że pewne konieczne warunki wstępne dla funkcji są spełnione na jej wejściu (i ewentualnie wyjściu). Może to być na przykład:

  • @connected – sprawdzenie połączenia z serwerem zanim spróbujemy wymieniać z nim dane i nawiązanie go w razie potrzeby
  • @authorized – określenie uprawnień wymaganych u aktualnie zalogowanego użytkownika przed wywołaniem funkcji wykonującej potencjalnie niebezpieczną operację
  • @synchronized – zabezpieczenie wywołania funkcji semaforem lub sekcją krytyczną

Wspólną cechą takich dekoratorów jest to, że są one swoistymi pseudodeklaracjami, nieodległymi koncepcyjnie zbyt daleko od komentarzy w rodzaju:

  1. # Ta funkcja wymaga połączenia z serwerem!
  2. def do_something():
  3.     # ...

Ich przewagą jest jednak rzeczywiste sprawdzanie, czy wymagania zostały spełnione – i to w sposób automatyczny i przezroczysty. Według mnie to właśnie stanowi o ich sporej przydatności.

Tags: , ,
Author: Xion, posted under Programming » 9 comments

Python, wcięcia i bardzo wredny błąd

2010-09-04 17:13

Niektórzy programiści, rozmawiając z innymi, znajdują przyjemność we wskazywaniu na specyficzne cechy różnych języków programowania, które im nie odpowiadają. Nie uważam tego za specjalnie produktywne i samemu staram się tego unikać. W końcu co za różnica, że w Javie czy C++ mamy nawiasy klamrowe, w Pascalu begin i end, a w Pythonie bloki wyróżniane wcięciami? Dla sprawnego programisty nie powinno to mieć żadnego znaczenia.
I faktycznie jest – dopóki nie okaże się, że pozornie nieistotna cecha języka prowadzi do błędów. Nieprzyjemnych, wrednych i trudnych do wykrycia błędów. Tak, wtedy dyskutowanie o podobnych składniowych błahostkach może być choć częściowo usprawiedliwione…

A skoro sam ten temat podjąłem, to wymaga mi przedstawić moje usprawiedliwienie. Nie jest ono długie. Wygląda bowiem tak:

  1. def escape_chars(text):
  2.     result = []
  3.     for c in text:
  4.         if ord(c) > 255:
  5.             result.append ("&#%s;" % ord(c))
  6.     else:
  7.         result.append (c)
  8.     return ''.join(result)

Ta prosta pythonowa funkcja ma za zadanie zamienić w podanym tekście znaki spoza zakresu ANSI na ich XML-owe odpowiedniki w postaci encji numerycznych (np. &#456;). Niezbyt skomplikowane, prawda? A jednak całkiem długo zajęło mi dojście do tego, czemu dla tekstu z samymi znakami ANSI wynikiem jest… łańcuch pusty.

Jest oczywiście bardzo prawdopodobne, że ktoś patrząc teraz na powyższy kod znajdzie przyczynę w mniej niż dziesięć sekund – zwłaszcza, że niemal bezpośrednio zasugerowałem ją na samym początku. To naturalnie nie świadczy o niczym, bo napady specyficznego rodzaju ślepoty na rzeczy oczywiste są nieodłączną częścią zajęcia zwanego programowaniem :) Z tym nie ma sensu polemizować.
Ale jak najbardziej można dyskutować o tym, dlaczego “wrodzona” cecha składni języka uważanego za nieskomplikowany, efektywny i nowoczesny (cokolwiek to znaczy) może być bezpośrednią przyczyną powstawania błędów, o którym użytkownikom C, Javy czy Pascala nawet się nie śniło! Nie widzę innego wytłumaczenia oprócz krótkowzroczności projektantów języka, którzy nie potrafili uświadomić sobie, że jego feature‘y mogą wchodzić ze sobą nie tylko w pożyteczne, ale czasem i niepożądane interakcje.

Dla jasności wytłumaczę jeszcze dokładniej, w czym rzecz. Mianowicie w Pythonie koniec bloku kodu rozpoznawany jest nie obecnością terminatora w rodzaju } czy end, lecz zmianą poziomu wcięcia następnej linijki – czyli czymś, co w innych językach pełni rolę wyłącznie estetyczną. Wiersze mające tę samą liczbę początkowych spacji leżą więc na tym samym poziomie zagłębienia.
Stąd zaś wynika fakt, iż w powyższej funkcji fraza else nie jest wcale dołączona do instrukcji if, lecz do… pętli for. Prawidłowe zagnieżdżenie wygląda bowiem tak:

  1. def escape_chars(text):
  2.     result = []
  3.     for c in text:
  4.         if ord(c) > 255:
  5.             result.append ("&#%s;" % ord(c))
  6.         else:
  7.             result.append (c)
  8.     return ''.join(result)

Ale chwilka – jak pętla for może mieć else‘a?… Ano to już jest rzecz specyficznie pythonowska (a biorąc pod uwagę okoliczności, wręcz monty-pythonowska), którą zresztą kiedyś zdarzyło mi się opisać. Wówczas to określiłem ją tylko jako zawracanie głowy. Dzisiaj porównałbym ją raczej z możliwością wpisywania stałych ósemkowych w kodzie C. Oba feature‘y używane są świadomie średnio raz na trzy lata, przez resztę czasu potencjalne powodując jedynie błędy.

Tags: , ,
Author: Xion, posted under Programming » 9 comments
 


© 2023 Karol Kuczmarski "Xion". Layout by Urszulka. Powered by WordPress with QuickLaTeX.com.