Mezi Ruby a Pythonem 3

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, TrueClassFalseClass.

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 andor vracejí přímo jednu z testovaných hodnot. Tedy zápis:

levá strana or pravá strana
je ekvivalentní s: s tím, že levá strana je vyhodnocena pouze jednou.

Obdobně platí pro and:

levá strana and pravá strana
ekvivalence s: s tím, že levá strana je vyhodnocena pouze jednou.

Čísla

Hierarchie čísel v Ruby Flanagan a Matsumoto, 2008, s. 42:

S tím, že od Ruby 2.4 jsou FixnumBignum 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, FixnumBignum. 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 FixnumBignum je obousměrně automatická.

Jak jsem zmiňoval už výše, od Ruby 2.4 jsou FixnumBignum 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:

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ů:

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&lt;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()eql?, v případě Pythonu pak o __hash__()__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:

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:

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:

Elementární funkčnost tříd je v obou jazycích stejná:

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í:

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:

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í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ý:

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í:

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:

Ří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 casewhen.

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 ifunless, 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 beginend, 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-inwhile 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:

Rozdíl ilustruje obrázek.

Typy kopií

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()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()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ů:

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