O textu…
Autor: Michal Molhanec (michal AT molhanec DOT net)
Aktuální verze vždy dostupná přes http://www.molhanec.net/
Ruby a Python si jsou jazyky v mnohém podobné. Pokud pracujeme střídavě v obou jazycích, nebo přecházíme z jednoho na druhý je to samozřejmě v první řadě výhoda. Ve druhé řadě nás může překvapit pár záludností. A právě proto vznikl tento text.
Nezabývám se úplnými základy. Takových porovnání je celá řada:
http://hyperpolyglot.org/scripting
http://kronosapiens.github.io/blog/2014/05/10/from-ruby-to-python.html
http://mitsuhiko.pocoo.org/pythonruby.html
http://rosettacode.org/
http://pleac.sourceforge.net/
http://rigaux.org/language-study/syntax-across-languages.html
http://c2.com/cgi/wiki?PythonVsRuby
Spíše jsem se snažil najít zvláštnosti, se kterými jsem se setkal.
Knihy
🕮 FLANAGAN, David a MATSUMOTO, Yukihiro. 2008. The Ruby programming language. 1. vyd. O'Reilly. ISBN 978-0-596-51617-8.
🕮 PERROTTA, Paolo. 2011. Metaprogramming Ruby: Program Like the Ruby Pros. 1. vyd. The Pragmatic Programmers. Pragmatic Bookshelf. ISBN 19-343-5647-6.
🕮 SHAUGHNESSY, Pat. 2014. Ruby Under a Microscope: An Illustrated Guide to Ruby Internals. 1. vyd. No Starch Press. ISBN 1-59327-527-7.
O stránce
Styly inspirovány stránkou ECMAScript 6 — New Features: Overview & Comparison Ralfa S. Engelschalla.
Font Andada od Huerta Tipográfica.
Font Roboto Christiana Robertsona.
Nezajímavý úvod aneb tl;r
Tenhle text je vlastně výtah z mojí diplomky, která se zabývala překladem Ruby do Pythonu. Proto je asi trochu více zaměřen na směr od Ruby k Pythonu, ale myslím si, že může být zajímavý i pokud vás zajímá opačný směr. Vlastně bych byl docela rád, kdyby zaujal programátor(k)y, které používají jenom jeden z těchto jazyků.
Osobně se rád nořím do rozdílů mezi jazyky. Člověk je součást přírody, takže není divu, že existuje-li možnost jak jednu věc udělat různými způsoby, tak že se tak také stane. A myslím si, že je to poučné. Mezi programátory se až překvapivě často setkávám s až fanatickým zaujetím pro jeden jazyk či platformu, aniž by vlastně něco jiného znali. Výsledkem je klasické „když mám v ruce kladivo, tak všechno vypadá jak hřebík“. Aneb jak už kdysi napsal Ed Post: Mimo to, opravdový programátor umí psát FORTRANské programy v kterémkoliv jazyce..
Pokud by někoho zajímala diplomka jako taková, je možné si ji přečíst, případně
výsledek spustit:
https://www.dropbox.com/s/sry7v9fpayralw2/porovnani_python3_ruby.pdf?dl=0
https://github.com/molhanec/rb2py/
Neznámá hodnota
Oba jazyky mají speciální hodnotu, a jí odpovídající speciální třídu, pro vyjádření neznámé hodnoty.
V Ruby se neznámá hodnota nazývá nil
a jde o jedinou instanci třídy NilClass
. Idiomatickým testem na neznámou hodnotu je použití metody nil?
. Vzhledem k tomu, že nil
je standardní objekt, není s použitím metody žádný problém.
V Pythonu se neznámá hodnota nazývá None
a jde o jedinou instanci třídy NoneType
. Idiomatickým testem na neznámou hodnotu je použití operátoru identity is
, tedy:
Python 🐍
objekt is None objekt is not None
V obou jazycích samozřejmě funguje i běžné porovnání operátorem ==
.
Logické hodnoty
Oba jazyky umí samozřejmě i vyjádřit pravdivostní hodnotu. Zatímco v Pythonu jde nepřekvapivě
o dvě možné hodnoty třídy bool
, Ruby je zvláštní tím, že tam má každá ze dvou logických
hodnot svou vlastní třídu, TrueClass
a FalseClass
.
Konverze na logické hodnoty
Následující tabulka udává hodnoty, které jsou považovány za nepravdu.
V obou jazycích platí, že vše ostatní je považováno za pravdivou hodnotu.
V tabulce je zahrnut ještě sloupeček s hodnotami pro blank?
. To je
rozšíření, které přidává
Active Support
původem z Ruby on Rails, ale které je možno používat i samostatně.
Ruby | Ruby Active Support .blank?
|
Python |
---|---|---|
false |
false |
False |
nil |
nil |
None |
prázdný řetězec | prázdný řetězec | |
řetězec obsahující pouze prázdné znaky (whitespace) dle definice Unicode | ||
prázdný hash | prázdný slovník | |
prázdné pole | prázdné pole | |
prázdná n-tice | ||
nula libovolného číselného typu | ||
objekty definující metodu empty? , která vrátí pravdu |
U vlastních tříd je v Pythonu možné, aby se jejich objekty konvertovaly na logickou hodnotu,
pokud definují alespoň jednu z metod __bool__()
nebo __len__()
.
Logické operátory and a or
V obou jazycích logické operátory and
a or
vracejí přímo jednu z testovaných hodnot. Tedy zápis:
or
pravá strana
-
pokud je pravda levá strana
- hodnotou je levá strana
-
jinak
- je hodnotou pravá strana
Obdobně platí pro and
:
and
pravá strana
-
pokud je pravda levá strana
- hodnotou je pravá strana
-
jinak
- je hodnotou levá strana
Čísla
Hierarchie čísel v Ruby Flanagan a Matsumoto, 2008, s. 42:
S tím, že od Ruby 2.4 jsou Fixnum
a Bignum
jednoduše aliasy pro Integer
.
V Pythonu striktně vzato hierarchie číselných typů není, int, float a další typy jsou přímo potomky základní třídy object. V Pythonu existují jakési „falešné“ třídy, tzv. abstraktní základní třídy, Abstract Base Classes, pro testování příslušnosti objektů k třídám. Pro čísla se nachází v modulu numbers
, kromě dokumentace popsáno též
v PEP 3141: A Type Hierarchy for Numbers.
(V Pythonu se návrhy na rozšíření nazývají PEP – Python Enhancement Proposals, viz https://www.python.org/dev/peps/).
Hierarchie v ní je následující:
Celá čísla
V Ruby před verzí 2.4 reprezentovala celá čísla dva potomci třídy Integer
, Fixnum
a Bignum
.
Fixnum
pro čísla, která se vejdou do nativního slova platformy,
kde program běží, a Bignum
pro prakticky neomezená čísla vyjádřená
ve dvojkovém doplňku, resp. omezená jen velikostí dostupné paměti.
Konverze mezi Fixnum
a Bignum
je obousměrně automatická.
Jak jsem zmiňoval už výše, od Ruby 2.4 jsou Fixnum
a Bignum
jednoduše aliasy pro Integer
.
Viz také http://blog.bigbinary.com/2016/11/18/ruby-2-4-unifies-fixnum-and-bignum-into-integer.html.
V Pythonu celá čísla reprezentuje typ int
, který obdobně zvládá čísla libovolné velikosti.
Podporované prefixy číselných literálů (velikost písmen ani v jednom jazyce není důležitá):
Ruby | Python |
---|---|
desítková celá čísla | |
0d |
|
šestnáctková celá čísla | |
0x |
0x |
osmičková celá čísla | |
0 |
|
0o |
0o |
dvojková celá čísla | |
0b |
0b |
Čísla zapsaná bez prefixu se považují za desítková. V Pythonu 3 není možné, aby nenulové číslo bez prefixu začínalo číslicí 0, protože to v řadě jiných jazyků, mj. i v Pythonu 2 a Ruby, znamená osmičkový zápis.
V Ruby je dále možné používat podtržení v číselném literálu pro seskupování
číslic (např. PSČ = 141_00
).
V Pythonu je to možné od verze 3.6.
Reálná čísla
Oba jazyky se spoléhají na reprezentaci reálných čísel poskytovaných hostitelskou platformou, dnes v drtivé většině odpovídající IEEE 754. Zápis literálů je prakticky stejný, oba jazyky podporují i semilogaritmický tvar.
Další typy čísel
Oba jazyky podporují i komplexní čísla, racionální čísla, tj. zlomky,
a reálná čísla vyjádřená na desítkovém základě.
Od Ruby 1.9 jsou komplexní čísla a zlomky přímo součástí jazyka,
v předcházejících verzích byly k dispozici v modulu complex
,
resp. rational
. Reálná čísla o desítkovém základu jsou pak ve
standardní knihovně bigdecimal
. V Pythonu 3 jsou komplexní čísla přímo součást jazyka, zlomky jsou pak k dispozici ve standarním balíku fractions
a desítková reálná čísla v balíku decimal
.
Běžné operace s čísly
Ruby | Python |
---|---|
unární znaménka | |
+, - |
+, - |
sčítání, odečítání, násobení | |
+, -, * |
+, -, * |
reálné dělení | |
/ pokud alespoň jeden operand, je reálné číslo |
/ |
metoda div |
|
celočíselné dělení | |
/ pokud jsou oba operandy celá čísla |
// |
metoda fdiv |
|
zbytek po celočelném dělení | |
% |
% |
celočíselné dělení spolu se zbytkem | |
metoda a.divmod(b) |
funkce divmod(a, b)
|
mocnina | |
** |
** |
funkce pow |
Ruby a Python celočíselné dělení (a tím pádem i zbytek po dělení) zaokrouhlují směrem k negativnímu nekonečnu, tj. -7/3=-3
. Naproti tomu jazyky jako C nebo Java zakrouhlují směrem k 0, tj. -7/3=-2
Flanagan a Matsumoto, 2008, s. 45.
Symboly
Hezkým, méně tradičním typem v Ruby jsou symboly. Symbol je vlastně internovaný neměnný řetězec. Za internované považujeme řetězce tehdy, platí-li o nich, že pokud si jsou rovny, tak jsou i reprezentovány stejným objektem. Tedy pro symboly v Ruby platí:
a == b
právě když a.object_id == b.object_id
V Ruby je použití symbolů hojné, ať už jako klíče slovníků a pojmenovaných parametrů funkcí, tak i jako prvky výčtu apod. V zásadě kdekoli, kde potřebujeme označení něčeho (např. dny v týdnu) aniž bychom dané označení potřebovali spojit s nějakou hodnotou.
V Pythonu přímo symboly nejsou, ale při běžném použití je lze jednoduše nahradit řetězci. Jako optimalizaci bychom je mohli internovat pomocí funkce sys.intern()
.
Řetězce
Neměnnost
Zásadní rozdíl je, že v Pythonu jsou řetězce neměnné (podobně jako třeba v Javě), zatímco v Ruby se měnit dají.
Od verze Ruby 3 budou neměnné řetezcové literály: https://bugs.ruby-lang.org/issues/11473.
Obyčejné řetězce nicméně měnitelné zůstanou. To si lze snadno vyzkoušet pomocí:
Ruby 💎
# frozen_string_literal: true str = "absdef".dup puts "obyčejný řetězec" puts str.sub! "b", "B" # OK puts "literál" puts "abcdef".sub! "b", "B" # Chyba
Speciální komentář na prvním řádku zapne toto chování i v aktuálních verzích Ruby.
Podobně lze použít přepínač –enable-frozen-string-literal
.
Smyslem je snížit alokaci paměti a zvýšit rychlost.
Řetězce se dají samozřejmě zamrazit i ručně pomocí metody freeze
.
Kódování jazyka
V obou jazycích došlo se zavedením verzí Ruby 1.9, resp. Python 3 k velké změně co se týče řetězců. Do té doby byly považovány prakticky za pole bajtů, zatímco v současných verzích jde již o pole znaků, resp. správněji tzv. kódových bodů (code points). Kódový bod je číslo, které reprezentuje daný znak v konkrétní znakové sadě. Kromě bežných znaků mohou mít např. v Unicode vlastní kódové číslo i některé formátovací značky nebo diakritická znaménka. Konkrétně v Unicode je např. možné znak s diakritikou vyjádřit buď jako jeden kódový bod, nebo jako dvojici kódového bodu znaku bez diakritiky a kódového bodu samostatného diakritického znaménka. . Tj. jeden znak může být reprezentován větším počtem bajtů.
Implementace se mírně liší. V Ruby má řetězec explicitně přidružené kódování, které lze zjišťovat, a nemusí jít o kódování znakové sady Unicode (pomiňme, že každá znaková sada se dá považovat za podmnožinu Unicode). V Pythonu řetězec vždy může obsahovat libovolný znak Unicode znakové sady, interně pak používá jedno z kódování latin1, UCS-2, nebo UCS-4, podle toho, jaké znaky obsahuje. Detaily implementace popisuje PEP 393 Flexible String Representation. Důležité je, že jednotlivé kódové body mají vždy hodnoty odpovídající znakové sadě Unicode.
Řetězec v Ruby nemusí být platný v přidruženém kódování. To lze otestovat metodou valid_encoding?
. Metoda force_encoding()
pak změní kódování přidružené k řetězci aniž by prováděla jeho konverzi. Jinak řečeno, změní pouze interpretaci bajtů, které ho tvoří. Například knihovna Prawn toho používá pro zjišťování, zda je řetězec platný v kódování UTF-8:
Ruby 💎
def is_utf8?(str) str.force_encoding(::Encoding::UTF_8) str.valid_encoding? end
Nejprve tedy řekne, aby Ruby chápalo bajty v řetězci jako kódování UTF-8 a pak se zeptá, jestli je tento řetězec platný.
Binární data
Z výše uvedeného plyne i další rozdíl. Pro čistě binární se i v nových verzích Ruby používají řetězce, pouze mají kódování nastavené buď na speciální hodnotu ASCII_8BIT
., nebo prostě nejsou v daném kódování platné.
V Pythonu oproti tomu existují samostatné třídy bytes
(podobně jako řetězce neměnitelné), bytearray
(měnitelné), případně lze užít obecnější třídy, jako je array
ze stejnojmenného modulu, nebo prosté pole čísel. Obyčejné pole čísel je překvapivě praktický způsob realizace. Porovnání různých způsobů implementace měnitelných binárních řetězců z různých hledisek prezentoval Brandon Rhodes na montrealské konferenci PyCon 2015, viz záznam: https://youtu.be/z9Hmys8ojno.
Jednotlivý znak
V obou jazycích neexistuje speciální typ pro znak, v případě potřeby je reprezentován řetězcem o délce jednoho znaku.
Zápis literálů
Oba jazyky mají bohaté možnosti zápisu literálů. Ruby tradičně podporuje interpolaci řetězců, tj. možnost vkládat do řetězců libovolné výrazy, které budou vyhodnoceny a výsledná hodnota nahradí výraz v řetězci. V Pythonu jde o novinku verze 3.6, podrobnosti viz PEP 498 Literal String Interpolation.
Konverze na řetězec
Oba jazyky nabízejí snadnou možnost konverze objektů na řetězec. Základní konverzi nabízejí metody objekt.to_s()
v Ruby, resp. objekt.__str__()
v Pythonu, kde ji bývá zvykem volat funkčním zápisem jako str(objekt)
.
Oba jazyky nabízejí i druhou možnost konverze objektů na řetězec. Je to konverze zvláště vhodná k účelům jako je logování a ladění.
V Ruby slouží k tomuto účelu metoda objekt.inspect()
. Výchozí implementace vypíše třídu objektu, identifikátor objektu a rekurzivně vypíše jednotlivé složky objektu:
Ruby 💎
class A def initialize @x = 1 @y = 'ahoj' @z = Time.now end end a = A.new() puts a.inspect()
vypíše:
#<A:0xa2577a4 @x=1, @y="ahoj", @z=2016-05-18 14:11:05 +0200>
V Pythonu existuje metoda objekt.__repr__()
, resp. funkce repr()
, která také vypisuje podrobnější infomace o objektu. Výchozí implementace vypíše třídu objektu a jeho identifikátor. Je ovšem doporučeno, aby tam, kde je to možné, vypisovala řetězec, který se blíží zápisu vytváření ekvivalentního objektu v syntaxi Pythonu, nebo to dokonce přímo takový zápis byl. V ideálním případě tedy repr()
vrátí text, který je možné předat standardní funkci eval()
, která ho vyhodnotí jako Pythonovský kód a vrátí objekt reprezentující stejnou hodnotu, jako objekt původní.
Řada tříd ve standardní knihovně Pythonu se tím samozřejmě řídí, hezkým příkladem je standardní třída datetime
reprezentující datum a čas:
Python 🐍
import datetime nyní = datetime.datetime.now() # běžná konverze na řetězec print(str(nyní)) # => 2016-05-18 14:17:46.376346 # print() volá str() implicitně, takže ho není potřeba uvádět print(nyní) # => 2016-05-18 14:17:46.376346 # podrobná reprezentace objektu print(repr(nyní)) # => datetime.datetime(2016, 5, 18, 14, 17, 46, 376346) print(eval(repr(nyní)) == nyní) # => True
Regulární výrazy
Ruby má po vzoru Perlu integrovány regulární výrazy přímo v jazyce, zatímco v Pythonu jsou běžnou součástí knihovny. Pro implementaci regulárních výrazů tyto jazyky používají různé knihovny. Zatímco Python používá jako výchozí svoji vlastní implementaci, Ruby nejprve od verze 1.9 začalo používat knihovnu Oniguruma a od verze 2.0 její fork Onigmo.
Reálné regulární výrazy v programovacích jazycích mají dvě části:
- Vzor. To, čím se vlastně zabývá teorie regulárních jazyků. Popisuje, jaké kombinace znaků jsou povoleny. Základní syntaxe je naštěstí jednotná.
- Příznaky. Ovlivňují, jakým způsobem bude interpretr regulárních výrazů provádět jejich zpracování. Ruby zná tyto příznaky:
-
i
– Nerozlišovat velikost písmen. V Pythonu tomu odpovídáre.I
, resp.re.IGNORECASE
. -
m
– Tečka bude zástupným znakem také pro konec řádku. V Pythonu tomu odpovídáre.S
, resp.re.DOTALL
. -
x
– Takzvané rozšířené regulární výrazy. Prázdné znaky ve vzoru jsou ignorovány a může obsahovat komentáře. V Pythonu tomu odpovídáre.X
, resp.re.VERBOSE
. -
o
– Týká se interpolace řetězců ve vzorech regulárních výrazů. Python nemá obdobný příznak. -
e, n, s, u
– Týkají se kódování řetězců. V Pythonu 3 řetězce vždy mohou obsahovat všechny znaky Unicode.
Python zná tyto příznaky:
-
re.ASCII
– Zástupné znaky\w
,\W
,\d
,\D
,\s
a\S
odpovídají pouze klasickým ASCII znakům, nikoli kategoriím Unicode. To v Ruby platí vždy. -
re.DEBUG
– Tiskne ladicí informace. -
re.DOTALL
– Tečka bude zástupným znakem také pro konec řádku. -
re.IGNORECASE
– Nerozlišovat velikost písmen. -
re.LOCALE
– Zástupné znaky budou respektovat aktuální locale. Dnes v podstatě jen pro zpětnou kompatibilitu. Pro moderní aplikace je výhodnější používat Unicode. -
re.MULTILINE
– Zástupné znaky^
, resp.$
odpovídají začátku a konci nejen řetězce, ale též každého obsaženého řádku. To platí v Ruby vždy. Všimněme si terminologické nejednotnosti, v Ruby znamená multiline to, co v Pythonu dotall. -
re.VERBOSE
– Rozšířené regulární výrazy.
-
Když půjdeme za ráměc základní syntaxe, nalezneme samozřejmě rozdílů v podobě regulárních výrazů spoustu. Každopádně to už radši ponechám na dokumentaci :)
Rozsahy
Zajímavým typem je rozsah (range). Představuje interval. Jakkoli mají rozsahy v obou jazycích mnohem širší možnosti, popíšeme pouze ty zdaleka nejužívanější a to celočíselné.
V Ruby existují dva typy celočíselných rozsahů:
- První typ rozsahu se zapisuje se dvěma tečkami:
a..b
a obsahuje všechna celá čísla oda
pob
včetně obou hranic. - Druhý způsob je se třemi tečkami
a...b
a obsahuje všechna čísla oda
pob-1
, tj. obsahuje pouze počáteční hranici.
Alternativně lze rozsahy v Ruby vytvářet konstruktorem třídy Range, jehož parametry jsou počáteční a koncová hodnota a nepovinný příznak, zda se má zahrnout i koncová hodnota.
V Pythonu se rozsahy tvoří explicitně konstruktorem třídy range, který má jako parametry počáteční a koncovou hodnotu a krok. Pokud není počáteční hodnota uvedena, předpokládá se nula, pokud není uveden krok, předpokládá se krok o velikosti jedničky.
Pole
Oba jazyky mají jako základní typy kolekcí integrovaných přímo v jazyku pole a slovníky. V Pythonu existují také n-tice (tuple), což jsou defakto neměnitelná pole. Vracíme-li z metody více hodnot, pak v Ruby budou vráceny jako měnitelné pole, zatímco v Pythonu jako neměnná n-tice.
Vybrané operace s poli
Získání prvku, nil/None v případě jeho neexistence
Ruby: pole[index]
Python: pole[index] if index in pole else None
Získání prvku, výjimka v případě jeho neexistence
Ruby: pole.fetch(index)
Python: pole[index]
Získání prvku, náhradní hodnota v případě jeho neexistence
Ruby: pole.fetch(index, default)
Python: pole[index] if index in pole else default
Délka pole
Ruby: pole.length
, pole.size
, případně pole.count
bez parametrů. Existují i varianty metody count
, které počítají počet výskytů daného objektu v poli, resp. počet prvků pole splňujících zadaný predikát.
Python: len(pole)
len(objekt)
je v zásadě idiomatický způsob zápisu objekt.__len__()
.
První prvek, nebo nil/None
Ruby: pole.first
Python: pole[0] if pole else None
Poslední prvek, nebo nil
Ruby: pole.last
Python: pole[-1] if pole else None
Je pole prázdné?
Ruby: pole.empty?
Python: stačí otestovat objekt pole
, neboť prázdné pole se vyhodnotí jako False
Vyjmutí posledního prvku, nebo nil/None
Ruby: pole.pop
Python: pole.pop() if pole else None
Vložení prvku na konec pole
Ruby: pole.push(prvek)
(vrátí pole
)
Python: pole.append(prvek)
(nevrací nic)
Vložení prvku před daný index
Ruby: pole.insert(index, prvek)
(vrátí pole
)
Python: pole.insert(index, prvek)
(nevrací nic)
Rozdíl polí se zachováním pořadí
Tato operace odstraní z prvního pole všechny prvky, které se vyskytují alespoň jednou i ve druhém. Nemění pořadí prvků pole, zachovává duplicity. Počet výskytů ve druhém poli není důležitý.
Ruby: první_pole - druhé_pole
Python: [prvek for prvek in první_pole if prvek not in druhé_pole]
Pokud by počet prvků polí byl větší, lze získat lepší asymptotickou časovou složitost pomocí:
s = set(druhé_pole)
[prvek for prvek in první_pole if prvek not in s]
To je ve skutečnosti i optimalizace, jakou interně používá Ruby MRI, podíváme-li se přímo do implementace:
Ruby MRI 2.3.1, array.c
static VALUE rb_ary_diff(VALUE ary1, VALUE ary2) { VALUE ary3; VALUE hash; long i; hash = ary_make_hash(to_ary(ary2)); ary3 = rb_ary_new(); for (i=0; i<RARRAY_LEN(ary1); i++) { if (st_lookup(rb_hash_tbl_raw(hash), RARRAY_AREF(ary1, i), 0)) continue; rb_ary_push(ary3, rb_ary_elt(ary1, i)); } ary_recycle_hash(hash); return ary3; }
Z toho také plyne důležitá (dokumentovaná) vlastnost, že v takovém případě je potřeba, aby objekty v poli korektně implementovaly metody, které jsou potřeba pro hashování. V případě Ruby jde o metody hash()
a eql?
, v případě Pythonu pak o __hash__()
a __eq__()
.
Vykrojení (slice)
Oba jazyky podporují operaci vykrojení. Tato operace nám umožňuje určit část sekvence, typicky pole nebo řetězce. Za limitní případ vykrojení lze považovat prosté indexování, které určuje jeden prvek.
V Ruby se k vykrojení nejčastěji používá operátoru hranatých závorek. Výsek lze určit buď pomocí rozsahů (viz Rozsahy), nebo jako dvojici počáteční prvek a počet prvků, které jsou odděleny čárkou:
Ruby 💎
pole = [1, 2, 3, 4, 5] # prosté indexování => jeden prvek puts pole[2] # => 3 # počátek, délka puts pole[2, 2] # => [3, 4] # inkluzivní rozsah puts pole[2..4] # => [3, 4, 5] # exkluzivní rozsah puts pole[2...4] # => [3, 4]
Lze používat i záporné indexy, které se počítají odzadu, tj. -i = délka - i
:
Ruby 💎
# záporné indexy pole = [1, 2, 3, 4, 5] # prosté indexování => jeden prvek puts pole[-2] # => 4 # počátek, délka puts pole[-2, 2] # => [4, 5] # inkluzivní rozsah puts pole[-2..4] # => [4, 5] # exkluzivní rozsah puts pole[-2...4] # => [4]
Python nabízí jedinou syntaxi, která je obdobou exkluzivního rozsahu, zapisuje se nicméně s dvojtečkou „:“
místo dvou teček „..“
:
Python 🐍
pole = [1, 2, 3, 4, 5] # prosté indexování => jeden prvek print(pole[2]) # => 3 print(pole[-2]) # => 4 # exkluzivní rozsah print(pole[2:4]) # => [3, 4] print(pole[-2:4]) # => [4]V Pythonu vynechání posledního prvku znamená automaticky poslední prvek:
Python 🐍
pole[-4:]
Procházení pole
Pro procházení všech prvků pole existují v Ruby v zásadě dva způsoby. Ten používanější je volání metody each()
s blokem, která je zavolán pro každý prvek:
Ruby 💎
['a', 'b', 'c'].each() {|hodnota| puts(hodnota)}
Méně používaným způsobem v Ruby, zato hojně v Pythonu, je konstrukce for-in
. Tato konstrukce je popsána v části věnované cyklům.
Slovníky
Dalším základním typem, který mají oba jazyky, je slovník. V Ruby je představován třídou Hash
a v Pythonu třídou dict
.
Procházení
Pokud chceme procházet všechny prvky slovníku v Ruby, tak podobně jako v případě jiných kolekcí můžeme použít metodu each()
, resp. její varianty. Záleží totiž na tom, jestli chceme procházet klíče, hodnoty, nebo dvojice klíč-hodnota. K procházení klíčů slouží metoda each_key()
, k procházení hodnot each_value()
, k procházení dvojic klíč-hodnota lze použít buď explicitně pojmenovanou each_pair()
, nebo běžnou metodu each()
, která se chová stejně.
V Pythonu slouží k získání klíčů slovníku metoda keys()
, k získání hodnot values()
a k získání dvojic klíč-hodnota metoda items()
. Tyto metody vrací pohledy, které lze procházet cyklem for-in
. Pokud explicitní metodu nepoužijeme a cyklu for-in
předáme vlastní objekt slovníku, získáme klíče stejně jako u metody keys()
. Následující dva zápisy jsou tedy ekvivalentní:
Python 🐍
for klíč in slovník: print(klíč) for klíč in slovník.keys(): print(klíč)
V Ruby od verze 1.9 jsou prvky slovníku procházeny v pořadí, které odpovídá pořadí, v jakém byly do slovníku vloženy. Python má ekvivalent ve třídě OrderedDict
standardního modulu collections
.
Definice modulů
V Ruby je modul, tj. instance třídy Module, základní stavební prvek programu, mimo jiné už proto, že třída, Class, je přímým potomkem Module.
V Ruby slouží moduly ke dvěma hlavním účelům:
- Jako jmenný prostor pro logicky související funkce a třídy.
- Jako tzv. mixin, tj. kolekci metod, kterou je možné vložit do třídy. Jde vlastně o určitou formu vícenásobné dědičnosti.
V Pythonu se terminologicky rozlišují moduly (module) a balíky (package). Balík je vlastně modul obsahující další moduly či vnořené balíky (subpackage).
V Ruby není hierarchie modulů svázána s umístěním souborů na disku, ale uvádí se ve vlastním kódu pomocí klíčového slova module
. Oproti tomu v Pythonu je hierarchie dána umístněním souborů. Balíkům odpovídají adresáře a koncovým modulům soubory. Pokud má balík nějaký kód, tzv. řádný (regular) balík, pak je umístěn v adresáři balíku v souboru __init__.py
. Pokud balík slouží jen k rozlišení jmenné hierarchie, tak tento soubor nemusí obsahovat. Pak jde o tzv. jmenný (namespace) balík.
Standardní mixiny v Ruby
Termín mixin byl již popsán v části věnované modulům. Mixiny jsou v Ruby vkládány do třídy pomocí klíčového slova include
.
Mixin Comparable
V sekci o operátorech je popsán porovnávací operátor <=>
.
Vložíme-li do třídy mixin Comparable, tak doplní naši třídu o operátory
<
, <=
, >
, >=
, ==
a metodu between?
, které budou implementovány pomocí našeho operátoru <=>
.
Python nabízí velmi podobnou funkčnost ve standardním modulu functools v podobě dekorátoru třídy total_ordering
. Dekorátory zatím nebyly popisovány. Jde o zvláštní syntaxi pro volání funkce s objektem třídy nebo metody. Název dekorátoru se zapisuje se zavináčem před definici dekorovaného objektu. Například zápis:
Python 🐍
@total_ordering class Třída: ...
je zcela ekvivalentní:
Python 🐍
class Třída: ... Třída = total_ordering(Třída)
Dekorátor total_ordering pak pro třídu, která definuje alespoň jeden z operátorů <
, <=
, >
, >=
, automaticky doplní definice těch ostatních.
Mixin Enumerable
Tento mixin je o poznání složitější. Pro třídu implementující metodu each
(některé metody tohoto mixinu vyžadují i <=>
) přídává 51 dalších metod (platí pro Ruby 2.2.3).
Řada z nich je obdobou funkcionálního programování, například:
-
all?(blok)
Vrací pravdu, pokud blok vrátí pro všechny prvky pravdivou hodnotu. -
any?(blok)
Vrací pravdu, pokud blok vrátí alespoň pro jeden prvek pravdivou hodnotu. -
map(blok)
Vrací pole, kde každý prvek je hodnota, kterou vrátil blok pro hodnotu prvku.
Python v těchto případech používá funkcionální zápis, tj. map(funkce, sekvence)
místo sekvence.map(blok)
.
Pro Pythonovské funkce, jako je zmiňovaná map()
, musí být objekt tzv. iterovatelný. Co to znamená je popsáno v části věnované for-in
cyklu, viz iterovatelnost. V zásadě můžeme říci, že Python třídu implementující metodu __iter__()
, která předává hodnoty pomocí yield
považuje za iterovatelnou.
Python je pak schopen takovou třídu procházet for-in
cyklem, používat ji s funkcemi all()
, map()
a podobně.
Třídy
V Ruby je základem hierarchie tříd od verze 1.9 třída BasicObject
Flanagan a Matsumoto, 2008, s. 235.
Ta implementuje jen naprosté minimum metod, a byla přidána primárně pro usnadnění implementace delegace, tj. pro situace, kdy přeposíláme zprávy jinému objektu.
Pro běžné účely, a do verze 1.9 i skutečným, základem hierarchie je třída Object
. Ta je také použita jako předek implicitně, neuvedeme-li při definici třídy jinou třídu. Třída Object
také vkládá mixin Kernel
, který implementuje standardní „globální funkce“. Technicky jde o metody modulu, ale protože se téměř vždy nacházíme v rámci objektu třídy, která je potomkem třídy Object
, tak jsou nám tyto metody automaticky přímo dostupné.
V Pythonu 3 je situace jednodušší, základem hierarchie je třída object
.
Pro zbytek kapitoly o třídách je nutné pečlivě rozlišit:
- Obyčejný objekt, tj. instanci třídy.
- Objekt třídy, tj. objekt který reprezentuje třídu samotnou. V Ruby jde o instanci třídy
Class
.
Elementární funkčnost tříd je v obou jazycích stejná:
- Je možné vytvářet instance. V Ruby to znamená zavolat metodu objektu třídy
new
. Python je zvláštní tím, že instanci třídy vytvoříme zápisem, kdy jméno třídy použijeme jako funkci. Napříkladobjekt = datetime()
. - Třída může obsahovat metody, které její instance sdílejí. Při volání pak proměnná
self
(v Pythonu explicitní parametr metody) obsahuje instanci, vůči které metodu voláme. - Objekty mohou obsahovat instanční proměnné. Ty definují interní stav objektu. V Ruby jejich názvy začínají znakem zavináč (
@proměnná
) a jsou vždy soukromé.
Volání metody předka
Předefinujeme-li metodu v potomkovi a chceme zavolat zděděnou stejnojmennou metodu předka, slouží nám v obou jazycích metoda super()
. V Pythonu vrátí podobjekt předka, na kterém metodu voláme, v Ruby přímo zavolá stejnojmennou metodu předka. Ruby má jednu zvláštnost. Neuvedeme-li explicitně žádné argumenty při volání metody super
, Ruby automaticky předá všechny argumenty aktuální metody metodě předka. To znamená, že chceme-li zavolat metodu předka bez jakýchkoli argumentů, musíme explicitně uvést prázdné závorky.
Příklad na použití metody super()
:
Ruby 💎
class Předek def metoda puts('Předek') end end module Modul1 def metoda puts('Modul1') super() end end module Modul2 def metoda puts('Modul2') super() end end module Modul3 def metoda puts('Modul3') super() end end class E < Předek include Modul1 def metoda puts('E') super() end end class F < E include Modul2 include Modul3 def metoda puts('F') super() end end objekt = F.new objekt.metoda()
vypíše:
F Modul3 Modul2 E Modul1 Předek
Tomu odpovídá v Pythonu:
Python 🐍
class Předek: def metoda(self): print('Předek') class Modul1: def metoda(self): print('Modul1') super().metoda() class Modul2: def metoda(self): print('Modul2') super().metoda() class Modul3: def metoda(self): print('Modul3') super().metoda() class E(Modul1, Předek): def metoda(self): print('E') super().metoda() class F(Modul3, Modul2, E): def metoda(self): print('F') super().metoda() objekt = F() objekt.metoda()
Všimněme si, že v Pythonu nevolá super()
přímo metodu předka, ale vrací vlastně jeho „podobjekt“, na kterém ji musíme zavolat sami.
Proměnné a metody objektu třídy
V obou jazycích mohou proměnné existovat i na úrovni objektu třídy, tedy proměnné sdílené všemi instancemi. V Ruby je situace poněkud komplikovanější, protože kromě běžných proměnných třídy, jejichž názvy začínají dvojicí zavináčů (@@proměnná
), mohou existovat i instanční proměnné objektu třídy. Ty se naštěstí používají výjimečně a v Pythonu nemají přímý ekvivalent. Pro případné detaily viz
Flanagan a Matsumoto, 2008, s. 230; Shaughnessy, 2014, s. 120.
Kromě metod volaných vůči instanci třídy umožňují oba jazyky definovat i metody na úrovni třídy, které lze volat bez nutnosti vytvoření její instance. V Pythonu je potřeba rozlišovat mezi metodou třídy (classmethod), které je jako první argument předán objekt třídy, podobně jako instanční metodě je předána instance, od statických metod (staticmethod), které toto nemají.
Metody
Funkce a metody
Do značné míry formálně má Ruby pouze metody, zatímco Python má jak metody, tak i obyčejné funkce. Rozdíl je v tom, že funkce v Pythonu nemá parametr self odkazující na aktuální instanci nad kterou je metoda volána. V Pythonu se self předává explicitně, je potřeba uvést ho v seznamu parametrů metod na prvním místě. Tím pádem může mít i jiný název, byť konvenci self je dobré dodržovat. V Ruby se self, jako ve většině objektových jazyků, předává implicitně a existuje vždy.
V Ruby můžeme rozlišit několik situací speciálních situací:
- Metody přidané do modulu Kernel. Protože tento modul je vkládán do třídy Object, automaticky to znamená, že jeho metody jsou k dispozici odkudkoli bez nutnosti explicitního určení. Jinak řečeno, tento idiom se používá právě tam, kde potřebujeme „klasickou“ globální funkci. Python přímý ekvivalent globálních funkcí nemá, všechny funkce patří do nějakého modulu a je buď potřeba je při volání kvalifikovat jménem modulu, resp. jménem pod kterým byl importován, nebo je explicitně vložit do jmenného prostoru současného modulu.
- Rozšiřování existujících tříd. V Pythonu není problém rozšiřovat běžné třídy, na rozdíl od Ruby nelze ale rozšiřovat základní třídy typu object nebo str (řetězec).
- Eigenmetody, tj. metody, které jsou přidruženy přímo konkrétní instanci, takže je objekty stejné třídy nesdílejí. Ruby v takovém případě vytvoří proxy třídu, která dané metody obsahuje, v Pythonu mohou objekty obsahovat metody přímo.
Názvy
Hezkou zvláštností Ruby oproti většině programovacích jazyků je možnost, aby název metody končil znakem ?
, !
či =
.
Metody končící otazníkem typicky zjišťují pravdivost predikátu, tj. mají návratovou hodnotu, kterou lze testovat v podmínce (false nebo nil v případě nepravdy a cokoli jiného pro pravdivou hodnotu). Příkladem může být metoda empty?
, která zjišťuje, zda je pole prázdné.
V Pythonu mají takové metody typicky název začínající is_
. Stejná metoda by se tedy jmenovala is_empty()
.
Metody končící vykřičníkem slouží ke zdůraznění určité „nebezpečnosti“, často tak jsou označovány metody, které mění objekt, na rozdíl od stejnojmenné metody bez vykřičníku, která vrací objekt nový. Kupříkladu metoda pole sort()
vrací nové seřazené pole, zatímco metoda sort!
změní pole původní.
Metody, jejichž název končí znakem =
slouží jako settery, jinak řečeno Ruby přeloží kód:
Ruby 💎
objekt.vlastnost = hodnota
jako:
Ruby 💎
objekt.vlastnost=(hodnota)
Pro gettery žádná zvláštní syntaxe potřeba není, protože v Ruby lze při volání metody vynechat závorky, tj.:
Ruby 💎
objekt.vlastnost()
je to samé jako:
Ruby 💎
objekt.vlastnost
Operátory
Při předefinování operátorů pak v Ruby používáme přímo symboly, tj. například plus definujeme jako:
Ruby 💎
class Třída def +(druhý_operand) ... end end
Z tohoto existují dvě výjimky:
- Pro odlišení unárních a binárních operátorů mají unární operátory připojen znak zavináč.
- Operátor indexování stojící na levé straně má tvar
[]=
.
V Pythonu se používají speciálně pojmenované metody. Následující tabulka ukazuje převod jmen metod operátorů Ruby na názvy metod v Pythonu:
Ruby | Python |
---|---|
unární operátory | |
+@ |
__pos__ |
-@ |
__neg__ |
@ |
__invert__ |
!@ |
přímo není |
indexy | |
[] |
__getitem__ |
[]= |
__setitem__ |
binární operátory | |
+ |
__add__ |
- |
__sub__ |
* |
__mul__ |
** |
__pow__ |
/ |
__truediv__ |
% |
__mod__ |
& |
__and__ |
^ | __xor__ |
<< |
__lshift__ |
>> |
__rshift__ |
== |
__eq__ |
!= |
__ne__ |
=== |
přímo není |
=~ |
přímo není |
!~ |
přímo není |
<=> |
přímo není |
< |
__lt__ |
<= |
__le__ |
> |
__gt__ |
=> |
__ge__ |
Python nemá operátor !
, pro negaci používá not
. Ten přímo předefinovat nejde, lze ale definovat speciální metody __bool__()
či __len__()
pro konverzi na logické hodnoty a tím pádem i ovlivnit chování not
. Co se týče metody __len__()
, ta slouží zejména k tomu, aby objekt, který představuje kolekci, informoval o počtu svých prvků. Připomeňme, že Python vyhodnocuje jako nepravdu i prázdné kolekce. Viz část Konverze na logické hodnoty.
U operátorů indexování povoluje Ruby na rozdíl od Pythonu několikanásobný klíč. Například:
Ruby 💎
class C def [](a, b) a * 100 + b end end c = C.new() puts c[2, 3]
vypíše 203
.
V Pythonu se zápis vyhodnotí jako jeden parametr typu n-tice:
Python 🐍
class C: def __getitem__(self, index): a, b = index return a * 100 + b c = C() print(c[2, 3])
Operátor <<
se v Ruby také hojně používá jako připojovací, dají se s jeho pomocí připojovat znaky k řetězci, do výstupních proudů nebo třeba prvky do pole:
Ruby 💎
řetězec = "Ahoj " pole = [1, 2] výstupní_proud = $stdout řetězec << "světe\n" pole << 3 << 4 výstupní_proud << řetězec << pole
vypíše:
Ahoj světe [1, 2, 3, 4]
Všimněme si, že v případě pole vrátí operátor jako svoji hodnotu výsledné změněné pole a v případě proudu tento proud, takže operátor lze řetězit.
Operátor ===
se používá v konstrukci case-when, kterou Python nemá a neobsahuje tudíž ani odpovídající operátor. Podrobnosti o tomto operátoru jsou uvedeny
v části věnované operátorům porovnávání a v části popisující konstrukci case-when.
Operátory končící vlnovkou slouží pro implementaci regulárních výrazů. V Pythonu jsou regulární výrazy implementovány jako běžná knihovna, takže Python tyto operátory přímo neobsahuje.
Operátor <=>
, tzv. spaceship operátor, slouží k porovnávání, vrací hodnotu menší než nula pokud je levý operand menší než pravý, hodnotu větší než nula, pokud je levý operand větší a nulu pokud si jsou operandy rovny. V Pythonu 3 již není (existoval v Pythonu 2). Pro převod kódu z Pythonu 2, kdy máme funkci odpovídající chováním tomuto operátoru, lze využít funkci cmp_to_key()
z modulu functools
, která dokáže takovouto funkci převést na funkci klíče pro porovnávání, tj. na funkci, která pro každý porovnávaný objekt vrátí hodnotu (typicky číslo), kterou lze pak jednoduše porovnat.
Například funkce v Pythonu 3 locale.strcoll(první_řetězec, druhý_řetězec)
porovnává dva řetězce v závislosti na aktuálním lokálním prostředí právě podle pravidel popsaných pro spaceship operátor. Pokud budeme chtít vypsat prvky pole seřazené touto funkcí můžeme využít cmp_to_key()
následujícín způsobem:
Python 🐍
for prvek in sorted(pole, key=cmp_to_key(locale.strcoll)): print(prvek)
Parametry metod
V obou jazycích máme čtyři základní typy parametrů, v Ruby pak ještě speciální pátý:
- poziční parametry,
- poziční sběrný parametr,
- pojmenované parametry,
- pojmenovaný sběrný parametr a
- v Ruby existuje ještě speciální parametr, kterým je blok.
Hezkou vlastností Pythonu je, že poziční a pojmenované parametry se nerozlišují při deklaraci metody, ale při volání se můžeme rozhodnout, jestli je chceme volat pozičně, nebo pomocí jména. Výjimkou jsou některé funkce implementované v C, které lze volat pouze pozičně (např. abs()
).
Pro úplnost dodejme, že v Pythonu existuje zápis umožňující vynucení užití parametru pomocí jména.
Sběrné parametry slouží k tomu, aby „posbíraly“ parametry, explicitně nevyjmenované, do pole (Ruby), resp. n-tice (Python), v případě pozičních, resp. do slovníku v případě pojmenovaných parametrů. Oba jazyky používají stejný zápis s hvězdičkou (tzv. splat), kdy jedna hvězdička se používá pro parametry poziční, a dvě hvězdičky pro parametry předávané jménem.
Tato syntaxe se používá jak v seznamu parametrů, tak i pro volání. Ilustrujme si na příkladu v Ruby.
Metoda p()
vypisuje svoje parametry na konzolu, obdobně jako puts()
, ale na rozdíl od ní převádí tyto parametry na řetězce metodou inspect()
a nikoli to_s()
. Rozdíl je popsán v sekci věnované řetězcům.
Ruby 💎
def fun(*poziční, **pojmenované) p(poziční) p(pojmenované) end pole = [1, 2, 3] slovník = {a: 7, b: 8, c:9} fun(*pole, **slovník)
vypíše:
[1, 2, 3] {:a=>7, :b=>8, :c=>9}
Výchozí hodnoty parametrů
V Pythonu existuje v souvislosti s výchozí hodnotou parametrů jedna zvláštnost. Jsou vyhodnocovány už v okamžiku, kdy interpretr narazí na definice funkce a jsou po celou dobu své existence spojeny s tímto jedním konkrétním objektem. V Ruby jsou oproti tomu vyhodnocovány při volání. To má zásadní důsledky v situaci, kdy je výchozí hodnota parametru měnitelná, často třeba prázdné pole nebo slovník.
V Pythonu následující kód:
Python 🐍
def výchozí(a=[]): a.append(1) print(a) výchozí() výchozí() výchozí()
vypíše:
[1] [1, 1] [1, 1, 1]
Ekvivalent v Ruby:
Ruby 💎
def výchozí(a=[]) a << 1 p(a) end výchozí() výchozí() výchozí()
oproti tomu zobrazí:
[1] [1] [1]
Tzn. zatímco v Pythonu se výchozí hodnota změní trvale i pro příští volání metody, v Ruby se vždy vytvoří nové pole.
Řešení nabízí tradiční Pythonský idiom: používat jako výchozí hodnoty vždy None a na začátku metody provést inicializaci ručně:
Python 🐍
def výchozí(a=None): if a is None: a = [] a.append(1) print(a)
Použití tohoto idiomu je nutné pouze v případech, kdy je výchozí hodnota měnitelná. To znamená, že můžeme ponechat hodnoty jako jsou čísla, True, False, nebo None.
Protože se výchozí hodnoty vyhodnocují v Ruby až při volání, znamená to také, že můžeme použít hodnotu předchozího parametru jako výchozí hodnotu dalšího:
Ruby 💎
def výchozí(a, b=a) puts(a, b) end
I v tomto případě by v Pythonu bylo potřeba použít například:
Python 🐍
def výchozí(a, b=None): if b is None: b = a print(a, b)
Návratová hodnota
Zatímco v Pythonu, pokud neuvedeme explicitní příkaz return
, je návratovou hodnotou None, v Ruby je v takovém případě návratovou hodnotou hodnota posledního vykonaného příkazu v těle metody.
Oba jazyky také podporují vracení více hodnot. V případě Pythonu budou vráceny jako n-tice, v případě Ruby jako pole.
Bloky
V Ruby existuje speciální parametr typu blok. Jde vlastně o klauzuru, která je předána funkci. Parametr tohoto typu má dosti výjimečné postavení:
- Může být jen jeden.
- Může být uveden explicitně v seznamu parametrů, ale nemusí. Pokud je uveden, musí jeho název začínat znakem
&
a musí jít o poslední parametr. Není-li uveden, lze zjišťovat voláním metodyblock_given?
, zda byl předán, a zavolat ho příkazemyield
. - Při volání metody se blok typicky zapisuje za zápis volání (tj. až za uzavírající kulatou závorku) mezi klíčová slova
do
aend
, resp. mezi složené závorky.
Ukažme si blok na příkladu:
Ruby 💎
def fun if block_given? yield else puts('Žádný blok nebyl předán') end end fun() # vypíše: Žádný blok nebyl předán fun() { puts 'Ahoj' } # vypíše: Ahoj
Blokům lze dále předávat parametry a získat návratovou hodnotu. Velmi typické je použití bloků pro procházení kolekce metodou each()
:
Ruby 💎
[1, 2, 3].each {|hodnota| puts(hodnota)}
Python přímý ekvivalent bloků nemá. Co se týče procházení kolekce pomocí each()
lze výše uvedený zápis nahradit konstrukcí for-in
:
Python 🐍
for hodnota in [1, 2, 3]: print(hodnota)
Mimochodem, tato konstrukce existuje i v Ruby, ale skoro se nepoužívá. Od verze Ruby 1.9 se nicméně konstrukce for-in
od metody each()
liší jednou vlastností: proměnné bloku (v našem případě hodnota) jsou lokální pro blok, tj. hodnota není vidět mimo blok (viz též pasáž věnovaná cyklu for-in
).
Přiřazení z vnořené funkce do proměnné obsahující funkce
Uvažujme kód:
Ruby 💎
def funkce lokální_proměnná = 1 čtverce = [1, 2, 3].map do |hodnota| lokální_proměnná = hodnota hodnota**2 end p(lokální_proměnná, čtverce) end funkce()
vypíše:
3 [1, 4, 9]
Zdánlivý ekvivalent v Pythonu:
Python 🐍
def funkce(): lokální_proměnná = 1 def _blok(hodnota): lokální_proměnná = hodnota return hodnota**2 čtverce = list(map(_blok, [1, 2, 3])) print(lokální_proměnná) print(čtverce) funkce()
vypíše:
3 [1, 4, 9]
Důvodem je, že přiřazení lokální_proměnná = hodnota
vytvoří novou lokální proměnnou funkce _blok
. Pro správnou funkčnost je potřeba přidat příkaz nonlocal
:
Python 🐍
def funkce(): lokální_proměnná = 1 def _blok(hodnota): nonlocal lokální_proměnná lokální_proměnná = hodnota return hodnota**2 čtverce = list(map(_blok, [1, 2, 3])) print(lokální_proměnná) print(čtverce) funkce()
Porovnávání
Zatímco Python obsahuje jen dva porovnávací operátory, klasické ==
a pak operátor identity is
, Ruby má pro porovnávání objektů hned několik možností s mírně odlišnými významy Flanagan a Matsumoto, 2008, s. 76:
-
Metoda equal?
Klasická identita objektů.
a.equal?(b)
je vlastně totéž coa.object_id == b.object_id
Flanagan a Matsumoto, 2008, s. 76. Odpovídá operátoruis
v Pythonu. -
Operátor ==
Běžné porovnání. Pokud ho třída, nebo některý její předek nedefinuje, pak výchozí implementací je metoda
equal?
, tedy identita objektů. Stejné je to i v Pythonu. -
Metoda eql?
Není-li předefinována, pak funguje stejně jako
equal?
. Doporučuje se, aby u vlastních tříd šlo o přísnější verzi operátoru==
, která neprovádí konverzi typů Flanagan a Matsumoto, 2008, s. 77. Důležité je, že standardní slovník v Ruby (třída Hash) používáeql?
pro porovnání klíčů. -
Operátor ===
Tento operátor slouží primárně pro řídicí konstrukci case-when. Ve výchozí implementaci předá porovnání operátoru
==
. Řada standardních tříd ale implementuje tento operátor jinak:- třída zjišťuje, jestli je parametr její instancí,
- regulární výraz zjišťuje, jestli parametr vyhovuje danému vzoru,
- rozsah zjišťuje, zda hodnota parametru v něm leží a podobně.
Řídicí výrazy
Zvláštnostní Ruby je, že skoro vše je výraz, včetně konstrukcí jako je if nebo while, což pro Python neplatí. Nejenom, že vrací hodnotu, kterou lze například přiřadit proměnné, ale také to znamená, že je-li například if
posledním příkazem v těle metody, pak se jeho hodnota stane implicitně návratovou hodnotou této metody.
Další, na co je potřeba si dát v této souvislosti pozor je skutečnost, že tím pádem mohou tyto výrazy stát v podmínce. Například typické je přiřazení v podmínce konstrukce while
:
Ruby 💎
while řádek = vstupní_proud.čti_řádek() ... end
Takovýto kód čte ze vstupního proudu řádky a zároveň každý přečtený řádek uloží do proměnné řádek
. V Pythonu není přiřazení výraz a je potřeba podobný kód nahradit:
Python 🐍
řádek = vstupní_proud.čti_řádek() while řádek: ... řádek = vstupní_proud.čti_řádek()
Podmínky (if, unless)
Ruby má běžnou podmínku if-elsif-else, která má přímou obdobu v Pythonu, který se liší jen tím, že místo klíčového slova elsif
používá elif
.
Kromě toho má Ruby navíc oproti Pythonu také klíčové slovo unless
, které má obrácený pravdivostní test, takže je ekvivalentní výrazu if not
. Výraz unless
může být spojen s else
, ale není možné ho kombinovat s elsif
.
Hodnotou výrazu if
nebo unless
v Ruby je hodnota posledního provedeného výrazu v těle vybrané varianty.
Pokud žádný proveden není, tedy v případě, že podmínka nebyla splněna a chybí else
, nebo tělo vybrané varianty neobsahuje žádné výrazy, pak je hodnotou podmínky nil
.
Podmínky ve formě modifikátorů
Na rozdíl od Pythonu má Ruby ještě jednu zvláštní formu podmínek, kdy if
nebo unless
může stát napravo od příkazu, který se vykoná v případě (ne)splnění podmínky:
Ruby 💎
příkaz if podmínka příkaz unless podmínka
Ternární operátor
Oba jazyky mají i ternární operátor. Zatímco v Ruby má podobu běžnou i v jiných jazycích vycházející ze syntaxe jazyka C:
Ruby 💎
podmínka ? výraz_pro_splněnou_podmínku : výraz_pro_nesplněnou_podmínku
v Pythonu má dosti netradiční formu s jiným pořadím jednotlivých částí:
Python 🐍
výraz_pro_splněnou_podmínku if podmínka else výraz_pro_nesplněnou_podmínku
Několikanásobný výběr (case, when)
Ruby má na rozdíl od Pythonu několikanásobný výběr, tj. konstrukci známou z programovacích jazyků pod různými názvy, jako select, switch a podobně. V Ruby se používá dvojice klíčových slov case
a when
.
Tato konstrukce může mít dvě podoby. Ta jednodušší je vlastně jen jiný zápis pro sekvenci několika if ... elsif ... elsif ... else. Například:
Ruby 💎
if den == :pondeli puts 'pondělí' elsif den == :utery puts 'úterý nebo středa' elsif den == :streda puts 'úterý nebo středa' else puts 'neplatný den' end
lze napsat i jako:
Ruby 💎
case when den == :pondeli puts 'pondělí' when den == :utery puts 'úterý nebo středa' when den == :streda puts 'úterý nebo středa' else puts 'neplatný den' end
Alternativy logické disjunkce můžeme kromě operátorů or
či ||
(svislítka) oddělit i čárkou:
Ruby 💎
when den == :utery, den == :streda puts 'úterý nebo středa'
Klasičtější forma case-when, více odpovídající jiným jazykům, testuje hodnotu jedné proměnné vůči několika variantám. Předchozí příklad bychom tedy mohli přepsat jako:
Ruby 💎
case den when :pondeli puts 'pondělí' when :utery, :streda puts 'úterý nebo středa' else puts 'neplatný den' end
Vidíme, že i v tomto případě můžeme čárkou oddělovat disjunktní alternativy.
Ač v tomto případě bude poslední varianta fungovat stejně jako ty předchozí, je zde velký principielní rozdíl. Zatímco v předchozích variantách jsme explicitně uváděli porovnávací operátor, v tomto případě se vždy použije operátor ===
. Repektive v kódu:
Ruby 💎
case hodnota when varianta_1 ... when varianta_2 ... end
se nejprve provede varianta_1.===(hodnota)
a pak varianta_2.===(hodnota)
. Všimněme si, že objekt, jehož metoda ===
se volá, je objekt jmenovaný za when
a jeho parametrem je objekt jmenovaný za case
, tedy pořadí je vlastně opačné oproti pořadí uvedenému v zápisu zdrojového kódu.
Operátor ===
může být obecně definován jinak než ==
. Zejména to platí pro třídy, kdy operátor testuje příslušnost objektu k třídě, tedy vlastně Třída === objekt
je obdoba objekt.is_a?(Třída)
.
Konstrukce case-when
je v Ruby výraz. Vrací hodnotu posledního provedeného výrazu, nebo nil v případě, že žádná varianta nebyla zvolena a chybí průchod else
.
Cykly
for-in
Ruby nabízí několik typů cyklů. Pro průchod prvky kolekce lze použít konstrukci for-in
:
Ruby 💎
for prvek in [1, 2, 3] puts(prvek) end
Tuto konstrukci lze použít s libovolným objektem, který definuje metodu each()
. Cyklus:
Ruby 💎
for a, b, c in objekt ... end
je ekvivalentní:
Ruby 💎
objekt.each() do |a, b, c| ... end
s tím rozdílem, že v Ruby jsou od verze 1.9 parametry bloku (v našem případě a
, b
, c
) pro blok lokální, zatímco při použití for-in
cyklu jde o klasické lokální proměnné metody. Z toho také plyne, že pokud již bude existovat proměnná stejného jména, jaké má proměnná for-in
cyklu, a bude mít před jeho vykonáním nějakou hodnotu, bude tato hodnota přepsána, zatímco v případě použití each()
nikoli:
Ruby 💎
i = 5 puts(i) # => vypíše 5 # přepíše hodnotu i for i in [1, 2, 3]; end puts(i) # => vypíše 3 # hodnota i se nezmění [10, 20, 30].each() {|i|} puts(i) # => vypíše 3
Hodnotou výrazu for-in
je iterovaný objekt, nebo hodnota předaná příkazu break
, pokud byl použit k ukončení cyklu.
V Pythonu existuje stejnojmenná konstrukce, pravidla pro lokální proměnnou jsou shodná. Pravidla pro objekty, které lze takto procházet jsou v Pythonu poněkud složitější. Takové objekty se v Pythonu označují jako iterovatelné a musí implementovat patřičným způsobem buď metodu __iter__()
vracející objekt nazývaný iterátor, nebo metodu __getitem__()
vracející procházené hodnoty.
Python 🐍
i = 5 print(i) # => vypíše 5 # přepíše hodnotu i for i in [1, 2, 3]: pass print(i) # => vypíše 3
while, until
Oba jazyky nabízejí obecný cyklus while
:
Ruby 💎
i = 1 while i < 5 puts(i) i += 1 end
Python 🐍
i = 1 while i < 5: print(i) i += 1
Podobně jako existuje v Ruby dvojice if
a unless
, tak i standardní obecný cyklus while
má protějšek until
, který testuje podmínku na nepravdivou hodntu. Hodnotou výrazu while
nebo until
je nil, pokud nebyl ukončen příkazem break
, kterému byla předána hodnota.
Tyto cykly v Ruby mají, podobně jako podmínky, i tvar ve formě modifikátorů, které se píší za příkaz, který se má opakovat:
Ruby 💎
násobek *= 2 while násobek < 1000
Pro úplnost se zmiňme ještě o jedné podobě cyklů v Ruby. Pokud je while
nebo until
použito jako modifikátor obecného sledu příkazů ohraničeného klíčovými slovy begin
a end
, pak se tento sled příkazů vykoná vždy alespoň jednou:
Ruby 💎
begin puts('v těle cyklu') end while false
vypíše „v těle cyklu“. Vzhledem k příliš velké podobnosti s tvarem:
Ruby 💎
puts('v těle cyklu') while false
který neproběhne ani jednou, se nicméně formu begin-end-while/until
nedoporučuje používat
a Flanagan a Matsumoto, 2008, s. 128 upozorňuje na to, že je možné, že budoucí verze mohou dokonce tuto formu úplně zakázat. Hlavní tvůrce Ruby, Yukihiro Matsumoto, dokonce vyjádřil lítost, že tuto vlastnost do jazyka přidal a touhu jí z něho odstranit. Viz jeho zpráva v e-malové konferenci Ruby-Core z 23. 11. 2005.
Aktuální verze Ruby (2.3.0) ale při použití této konstrukce nevypisuje ani varování, byť ji lze chápat jako zastaralou (deprecated).
cykly s větví else
Python umožňuje mít u cyklů for-in
a while
větev else
, která se provede,
pokud není splněna podmínka cyklu. Je to vlastně stejné, jako pro if
.
Větev else
se provede, i když se tělo cyklu neprovede ani jednou.
Můžeme se na to dívat jako na větev nobreak. Tj. jako na kód, který se
provede, když cyklus není přerušen příkazem break
.
Python 🐍
while False: print("while False") else: print("while False: else") while True: print("while True") break else: print("while True: else") i = 1 while i < 3: print("while i < 3, i =", i) i += 1 else: print("while i < 3: else")
vypíše:
while False: else while True while i < 3, i = 1 while i < 3, i = 2 while i < 3: else
To se hodí překvapivě často. Kdykoli potřebujeme rozlišit, že cyklus skončil z nějakého důvodu předčasně:
Python 🐍
for element in pole: if není_platný(element): print(":-(") break else: print("Gratuluji, celé pole je platné!")
Kernel#loop
V případě cyklu loop
jazyka Ruby jde o jeden z příkladů toho, kdy se metoda implementovaná ve tříde Kernel
, a tudíž dostupná v každém objektu, tváří jako klíčové slovo jazyka. Jde o jednoduchý nekonečný cyklus, jehož parametrem je blok, který lze kromě obligátního break
také ukončit vyvoláním výjimky StopIteration
. Metoda loop
tuto výjimku zachytí, čímž zastaví její propagaci, a jednoduše se vrátí.
Návratová hodnota loop
je buď hodnota předaná příkazu break
, nebo nil v případě vyvolání výjimky StopIteration
.
Skoky (break, next, redo)
Blok nebo cyklus je možné v obou jazycích předčasně ukončit příkazem break
.
Rozdíl existuje v tom, že v Ruby lze příkazu break
předat hodnotu, která je pak dostupná buď jako hodnota výrazu cyklu, nebo jako výsledek volání cyklu metodou yield
.
Příkaz Ruby next
slouží k ukončení současné iterace a pokračováním další. V bloku pak může mít parametr, který bude použit jako návratová hodnota současné iterace. Python má pro cykly obdobný příkaz continue
.
Ruby má navíc ještě příkaz redo
, který zopakuje aktuální iteraci. Python ekvivalent nemá. V případě potřeby by se tento příkaz dal nahradit cyklem s příznakovou proměnnou:
Ruby 💎
puts 'Hádejte čísla mezi 1 a 10' číslo = rand 1..10 pokus = 1 hádané = nil until hádané == číslo print "#{pokus}. pokus: " # to_i vrátí 0, pokud nejde o platné celé číslo hádané = gets.strip.to_i if hádané < 1 or hádané > 10 puts 'Prosím zadejte číslo mezi 1 a 10' redo end puts "Tipuješ #{hádané}" pokus += 1 end puts "Uhádl jste na #{pokus - 1}. pokus"
Python 🐍
from random import randint print('Hádejte čísla mezi 1 a 10') číslo = randint(1, 10) pokus = 1 hádané = None while hádané != číslo: redo = True while redo: redo = False try: # int() vyvolá ValueError pokud vstup není platné celé číslo hádané = int(input('%d. pokus: ' % pokus).strip()) if hádané < 1 or hádané > 10: raise ValueError except ValueError: print('Prosím zadejte číslo mezi 1 a 10') redo = True print("Tipuješ %d" % hádané) pokus += 1 print("Uhádl jste na %d. pokus" % (pokus - 1))
Běžné idiomy
Hluboká a mělká kopie
První idiom se týká vytváření hlubokých kopií objektů. Než se dostaneme k samotnému idiomu, podívejme se na možnosti kopírování objektů v obou jazycích.
Oba jazyky používají klasický referenční objektový model, kdy proměnné obsahují pouze odkaz (referenci) na instanci třídy jinde v paměti. Běžné přiřazení mezi proměnnými ve výsledku tedy vede jen na to, že obě proměnné odkazují na stejný objekt.
Někdy je ale potřeba vytvořit skutečnou kopii objektu. Python podporuje vytváření kopie prostřednictvím standardního modulu copy. Jeho funkce copy()
umožňuje vytvářet mělké kopie, funkce deepcopy()
kopie hluboké.
Rozdíl mezi mělkou a hlubokou kopií se týká objektů, které obsahují odkazy na další objekty:
- Mělká kopie vytvoří kopii pouze prvního, vnějšího objektu. Jeho vnitřní odkazy povedou na původní objekty. Nový a původní objekt je tak budou sdílet.
- Hluboká kopie rekurzivně zkopíruje i odkazované objekty.
Rozdíl ilustruje obrázek.
Objekty v Pythonu mohou kontrolovat kopírování objektů implementováním metod __copy__()
, resp. __deepcopy__()
.
Ruby má přímo podporu jen pro mělké kopírování. Na rozdíl od Pythonu nabízí pro mělké kopie dokonce hned dvě metody: clone()
a dup()
. Mezi nimi jsou jemné rozdíly, které jdou už mimo rámec toho textu. Zájemci najdou popis rozdílů například v Flanagan a Matsumoto, 2008, s. 83.
Podobně jako v Pythonu mohou i v Ruby objekty svoje kopírování ovlivňovat. Kromě možnosti vlastní implementace metod clone()
a dup()
je zde i „kopírovací konstruktor“ initialize_copy()
. Pokud je definován, pak se použije místo běžného konstruktoru initialize()
a jako parametr dostane původní objekt.
Protože podpora hluboké kopie přímo v jazyku není, vytvořil se pro ni idiom:
Ruby 💎
nový_objekt = Marshal.load(Marshal.dump(původní_objekt))
Ten využívá standardních metod Marshal.dump()
, která převede objekt na binární řetězec a Marshal.load()
, která z tohoto řetězce vytvoří nový objekt. Mimochodem, Python interně používá obdobný postup, nicméně tím, že se vyhne serializaci na řetězec a jeho parsování zpátky, může být o dost rychlejší. Z mých zkušeností je tento idiom v Ruby opravdu pomalý a i velmi paměťově náročný.
Vraťme se ještě na chvíli k mělké kopii. Oba jazyky volí vzájemně protichůdný přístup. Zatímco v Ruby je initialize_copy()
metoda nového objektu, která dostane ten původní jako parametr, v Pythonu se __copy__()
volá na původním objektu a nový objekt vrací jako svou návratovou hodnotu.
Pozdní inicializace instanční proměnné
V Ruby je běžná pozdní inicializace instanční proměnné, také nazývaná Nil Guards Perrotta, 2011, s. 226, typu:
Ruby 💎
def metoda @proměnná ||= výchozí_hodnota end
Pokud instanční proměnná ještě neexistuje, je jí přiřazena výchozí hodnota a ta je i zároveň vrácena z metody. Pokud instanční proměnná existuje, je rovnou vrácena její hodnota.
To funguje díky tomu, že neexistující instanční proměnná se chová jakoby existovala a obsahovala hodnotu nil
.
Napsat přímý ekvivalent v Pythonu je problematické ze dvou důvodů:
- Neexistuje přímý ekvivalent operátoru
||=
. - Přístup k neexistující proměnné vyhodí výjimku AttributeError.
Z těchto důvodů je v Pythonu lepší vytvořit v metodě __init__()
tuto instanční proměnnou a inicializovat ji na None.
Test na nil/None
Další idiom byl již zmíněn v sekci o Neznámé hodnotě. Test na nil v Ruby:
Ruby 💎
proměnná.nil?
Test na None v Pythonu:
Python 🐍
proměnná is None
U instančních proměnných v Ruby pak opět nesmíme zapomenout, že nil znamená, že buď instanční proměnná existuje a byla explicitně nastavena na nil, nebo že zatím vůbec nebyla inicializována.
Ruby 💎
class C def f p @x end end o = C.new o.f() # vypíše nil