Obiekty mają metody. Tak, w tym stwierdzeniu nie należy doszukiwać głębokiego sensu – jest ono po prostu prawdziwe :) Gdy mówimy o metodach obiektów czy też klas, zwykle mamy jednak na myśli tylko jeden ich rodzaj: metody instancyjne. W wielu językach programowania nie jest to aczkolwiek ich jedyny rodzaj – z takim przypadkiem mamy do czynienia chociażby w Pythonie.
Jak zawsze metody instancyjne są domyślnym typem, który tutaj jest dodatkowo zaznaczony obecnością specjalnego parametru – self
– występującego zawsze jako pierwszy argument. To odpowiednik this
z języków C++, C# czy Java i reprezentuje instancję obiektu, na rzecz której wywoływana jest metoda:
Fakt, że musi być on jawnie przekazany, wynika z zasad tworzenia zmiennych w Pythonie. Nie muszą być one jawnie deklarowane. Dlatego też odwołanie do pola obiektu jest zawsze kwalifikowane, gdyż przypisanie do _value
zamiast self._value
stworzyłoby po prostu zmienną lokalną.
Istnieją jednak takie metody, które nie operują na konkretnej instancji klasy. Typowo nazywa się je statycznymi. W Pythonie nie posiadają one parametru self
, lecz są opatrzone dekoratorem @staticmethod
:
Statyczną metodę można wywołać zarówno przy pomocy nazwy klasy (Counter.format_string()
), jak i jej obiektu (Counter().format_string()
), ale w obu przypadkach rezultat będzie ten sam. Technicznie jest to bowiem zwyczajna funkcja umieszczona po prostu w zasięgu klasy zamiast zasięgu globalnego.
Mamy wreszcie trzeci typ, mieszczący się w pewnym sensie pomiędzy dwoma opisanymi powyżej. Nie wydaje mi się jednak, żeby występował on w żadnym innym, popularnym języku. Chodzi o metody klasowe (class methods). Nazywają się tak, bo są wywoływane na rzecz całej klasy (a nie jakiejś jej instancji) i przyjmują ową klasę jako swój pierwszy parametr. (Argument ten jest często nazywany cls
, ale jest to o wiele słabsza konwencja niż ta dotycząca self
).
W celu odróżnienia od innych rodzajów, metody klasowe oznaczone są dekoratorem @classmethod
:
Podobnie jak metody statyczne, można je wywoływać na dwa sposoby – przy pomocy klasy lub obiektu – ale w obu przypadkach do cls
trafi wyłącznie klasa. Tutaj akurat będzie to Counter
, lecz w ogólności może to być także klasa pochodna:
Powyższy kod – będący przykładem dodatkowego sposobu inicjalizacji obiektu – to dość typowy przypadek użycia metod klasowych. Korzystanie z nich wymaga aczkolwiek nieco wprawy w operowaniu pojęciami instancji klasy i samej klasy oraz ich poprawnego rozróżniania.
W gruncie rzeczy nie jest to jednak nic trudnego.
Publikuję slajdy z prezentacji The Beauty and the JavaScript, którą wygłosiłem w zeszły poniedziałek w ramach Polidea Talks – cyklu wykładów wygłaszanych w różnych odstępach czasu przez osoby pracujące w Polidei. Jak sugeruje nazwa, rzecz dotyczyła języka JavaScript i bynajmniej nie przedstawiała go w specjalnie pozytywnym świetle ;) Chciałem aczkolwiek pokazać, że przy odrobinie dobrej woli i zręcznym unikaniu pułapek da się z nim żyć, co obecnie jest często koniecznością.
The Beauty and the JavaScript (27.6 KiB, 1,545 downloads)
Sama prezentacja jest też pewnego rodzaju ciekawostką, bo została przygotowana jako dokument HTML do oglądania w przeglądarce WWW. Do renderowania slajdów używa biblioteki S5, która napisana została w – a jakże – JavaScripcie. Rozwiązanie to sprawdziło się zresztą całkiem nieźle w przypadku prostych slajdów, takich jak niniejsze.
Całość jest też dostępna jako gałąź w repozytorium na GitHubie.
Czas pochwalić się swoim nowym dziełem. Nie jest ono bardzo imponujące ani specjalnie duże. Mam jednak nadzieję, że będzie ono przydatne dla tego (wąskiego) grona odbiorców, do którego jest skierowane.
Mam tu na myśli niewielką biblioteką do Pythona, która ma na celu poprawienie użyteczności jednej z głównych, ideowych cech języka – tak zwanego typowania kaczkowego (duck typing). Geneza tego terminu jest oczywiście wielce intrygująca, ale założenie jest proste. Zamiast czynić obietnice i jawnie deklarować implementowane interfejsy, obiekty w Pythonie “po prostu są” i zwykle próbują być od razu używane do założonych celów. Jeśli okażą się niekompatybilne (np. nie posiadają żądanej metody), wtedy oczywiście rzucany jest wyjątek. Pythonowska praktyka polega więc na przechodzeniu do rzeczy bez zbędnych ceregieli i obsłudze ewentualnych błędów.
Ma to rzecz jasna swoje zalety, ma też wady, a czasami może również rodzić problemy, jeśli błąd spowodowany niekompatybilnością obiektu ujawni się za późno. Z drugiej strony brak konieczności jawnego specyfikowania implementowanych interfejsów to spora zaleta. Najlepiej więc byłoby jakoś połączyć te dwa światy i umożliwić wcześniejsze sprawdzenie możliwości obiektu…
Jak można się pewnie domyślić, to właśnie próbuje umożliwić mój framework, noszący wdzięczną nazwę pyduck. Dodaje on do Pythona mechanizm interfejsów bardzo podobny do tego, który obecny jest w języku Go. Najważniejszą jego cechą jest właśnie fakt, że w konkretnych typach interfejsy są implementowane niejako automatycznie – wystarczy, że mają one odpowiednie metody. Samo sprawdzenie, czy obiekt implementuje dany interfejs polega zaś na faktycznym zaglądnięciu w listę jego metod, a nie weryfikacji jakichś jawnych deklaracji.
Inaczej mówiąc, nie czynimy tutaj żadnych obietnic odnośnie obiektu, ale wciąż mamy możliwość kontroli, czy nasze wymagania są spełnione. Najlepiej ilustruje to oczywiście konkretny przykład:
Zaznaczamy w nim, że metoda Canvas.draw_object
spodziewa się obiektu zgodnego z interfejsem Drawable
. Jest on zdefiniowany wyżej jako posiadający metody get_bounds
i draw
. Sprawdzenie, czy rzeczywisty argument funkcji spełnia nasze wymogi, zostanie wykonane przez dekorator @expects
. Zweryfikuje on obecność i sygnatury metod wspomnianych metod.
Dzięki temu będziemy mogli być pewni, że mamy do czynienia z obiektem, który rzeczywiście potrafi się narysować. Jego konkretna klasa nie będzie musiała natomiast nic wiedzieć na temat interfejsu Drawable
ani jawnie deklarować jego wykorzystania.
Po więcej informacji zapraszam oczywiście na stronę projektu na GitHubie. Ewentualnie można też od razu zainstalować paczkę, np. poprzez easy_install
:
A ponieważ wszystko open source jest zawsze wersją rozwojową, nie muszę chyba wspominać, że z chęcią witam pull requesty z usprawnieniami i poprawkami :>
Pisałem już wcześniej o tym, że ostatnimi czasy programowanie obiektowe nie ma zbyt dobrej prasy i swego rodzaju modą stało się jego krytykowanie. Nie jest to oczywiście trend zupełnie pozbawiony podstaw. Z jednej bowiem strony dla pewnych problemów lepsze wydają się inne paradygmaty: np. dla modelu żądanie-odpowiedź (typowego dla aplikacji webowych) najwyraźniej całkiem dobrze sprawdza się programowanie funkcyjne. Z kolei zastosowania wymagające dużej wydajności (np. programowanie grafiki 3D czasu rzeczywistego) mogą istotnie nie tolerować narzutu związanego z polimorfizmem funkcji wirtualnych – albo przynajmniej tak się wydaje tym, którzy się nimi zajmują.
Sądzę jednak, że spory udział ma tu też pewien powszechny (niestety) zwyczaj krytykowania rzeczy, których nie do końca rozumiemy. Szerokie kręgi zatacza bowiem pewien specyficzny sposób opacznego interpretowania idei OOP-u. Jego źródła upatruję w wielu (może nawet większości) kursach, książkach, tutorialach, artykułach i wszelkiego rodzaju materiałach instruktażowych dla początkujących adeptów programowania. Sam dodałem swój niechlubny wkład do tego stanu rzeczy, do czego się tu otwarcie przyznaję. No cóż, nikt mnie ostrzegł – ani wtedy (czyli jakieś 6 lat temu), ani nawet teraz.
A ostrzeżenie jest absolutnie niezbędne. Także dlatego, że co najmniej raz słyszałem, jak owo mylne pojęcie na temat OOP-u stało się poważnym argumentem w rzeczowej poza tym dyskusji na temat jego zalet i (głównie) wad. Nawet ważniejszym powodem jest jednak to, iż niewłaściwa interpretacja założeń programowania obiektowego może prowadzić do źle zaprojektowanych systemów, które trudno się potem konserwuje i rozbudowuje.
O jakiego więc rodzaju konfuzji tu mówimy?
Sporo szumu zrobiła ostatnio informacja o tym, że pewien chatterbot (program udający rozmowę tekstową i/lub głosową) z sukcesem przebrnął przez test Turinga. Przebiła się ona nawet do mediów ogólnych, co zwykle oznacza jedną z dwóch rzeczy: albo wydarzenie jest naprawdę ważne, albo wybitnie wyolbrzymione.
Niestety, w tym przypadku mamy do czynienia raczej z tym drugim wariantem.
Dla porządku warto oczywiście przypomnieć, o co tu właściwie chodzi. Test Turinga był zaproponowany w pierwszej połowie zeszłego stulecia jako jedna z możliwości odpowiedzi na pytanie o to, czy komputery mogą “myśleć”. Ogólna idea jest tu bardzo prosta: jeśli maszyna będzie dla postronnego rozmówcy nieodróżnialna od człowieka, wtedy przechodzi test pozytywnie. Szczegóły dotyczą rzecz jasna dokładnego sposobu przeprowadzenia testu: medium komunikacji (np. konsoli tekstowej), dozwolonego czasu odpowiedzi, a przede wszystkim sposobu oceny – co obejmuje chociażby ilość sędziów i metodę ustalania werdyktu.
W przypadku Cleverbota mówimy o rezultacie trochę lepszym niż rzut monetą (ale statystycznie istotnym): 59.3% rozmówców uznało, że mają do czynienia z człowiekiem. W tym samym scenariuszu faktyczni ludzie uzyskiwali wynik… 63.3%. Najwyraźniej komputery są już całkiem blisko, by być lepsze od ludzi w… udawaniu ludzi :)
Czy jednak wynika z tego faktu cokolwiek znaczącego? Nawet jeśli uznamy, że rzeczywiście mamy tu do czynienia z pierwszym sukcesem w teście Turinga (bo diabeł jak zwykle tkwi w szczegółach), to nie wydaje mi się, aby wydarzenie to było czymś więcej niż tylko historycznym symbolem. Mamy tu oczywiście do czynienia ze sztuczną inteligencją (AI), lecz jest ona podobnej klasy co programy do rozpoznawania obrazu i dźwięku, algorytmy szachowe czy systemy automatycznego wnioskowania w stylu 20Q. W każdym z tych przypadków chodzi o wykonywanie określonego zadania, które zazwyczaj lepiej wychodzi ludziom niż komputerom. Sposób zaatakowania problemu jest też zawsze taki sam i polega na rzuceniu na front odpowiednio dużej mocy obliczeniowej, opcjonalnie wspartej pokaźnym zbiorem danych.
Tak też robi Cleverbot, będący w gruncie rzeczy silnikiem do przeszukiwania bazy wiedzy w odpowiedzi na zapytania pisane w określonym języku (w tym przypadku angielskim). Został stworzony w konkretnym celu i okazał się być w swojej domenie wystarczająco dobry, by zacząć konkurować z ludźmi. Dokładnie tak, jak kiedyś Deep Blue – który przynajmniej nie twierdził, że jest jednorożcem ;]
Każdemu absolwentowi szkoły podstawowej (mam nadzieję, że wciąż podstawowej…) powinna być znana poniższa funkcja:
Tak, jest to zwyczajna wartość bezwzględna, znana również jako moduł liczby. Jej formalna definicja gwarantuje, że dla każdego
. Innymi słowy, wartość bezwzględna nigdy nie może być ujemna. Nic więc dziwnego, że używamy jej chętnie w tych właśnie sytuacjach, gdy interesuje nas tylko dodatnia połowa osi liczb. Oto bardzo prosty przykład:
Mamy tu elementarną sztuczkę, polegającą na początkowym przypisaniu polu identifier
wartości spoza zwykłej dziedziny (-1
), co oznaczać ma brak identyfikatora. Możemy tak zrobić, bo dzięki wartości bezwzględnej (funkcji Math.abs
) generowane losowe identyfikatory będą zawsze dodatnie.
Albo raczej prawie zawsze…
Zdarzyło się dzisiaj, że musiałem zaimplementować rozwiązanie wyjątkowo klasycznego problemu. Siląc się matematyczny formalizm, mógłbym go zdefiniować następująco:
Krótko mówiąc, chodzi o trywialną wariację z powtórzeniami. Ambitne to zadanie jest w sumie nawet mniej skomplikowane niż częste ćwiczenie dla początkujących pt. losowanie Lotto, więc po chwili wyprodukowałem coś podobnego do kodu poniżej:
I na tym pewnie historia by się zakończyła, gdybym nie przypomniał sobie, że Python domyślnie potrafi obsługiwać naprawdę duże liczby. (Może nie aż tak duże jak te tutaj, ale jednak dość spore ;]). Obserwacja ta daje się bowiem połączyć z inną: taką, iż ciąg elementów z pewnego zbioru jest równoważny liczbie w systemie o podstawie równej mocy tego zbioru. Taka liczba jest oczywiście bardzo duża w prawie każdym praktycznym przypadku, lecz to nie umniejsza w niczym prawdziwości stwierdzenia. Jest ono zresztą z powodzeniem wykorzystywane w systemach kryptograficznych w rodzaju RSA.
Postanowiłem więc i ja z niego skorzystać. Przynajmniej teoretycznie fakt ten powinien dawać lepsze rezultaty, zamieniając k losowań na tylko jedno – tak jak poniżej:
Doświadczenie z kolei uczyłoby, że bezpośrednie aplikowanie dziwnych matematycznych koncepcji do programowania rzadko miewa dobre skutki ;) Jak więc jest w tym przypadku?…