Viele Aktivitäten lassen dir die Wahl zwischen Ruby und Python, etwa Lauflängen, Huffman, LZW, Steganographie, Einmalcodes, Automaten, der Taschenrechner, der Routenplaner und der Webserver. Hier steht dasselbe in beiden Sprachen nebeneinander: zum Nachschlagen in der eigenen, und um zu sehen, wie es in der anderen aussieht. Wie man ein Programm überhaupt schreibt und startet, zeigen die Aktivitäten Ruby und Python.
Die beiden Sprachen sind fast gleich alt, beide startet man ohne Compiler direkt aus dem Quelltext, und beide wollen, dass sich Programme gut lesen. Sie unterscheiden sich vor allem in der Schreibweise: Ruby schließt Blöcke mit end und ruft Methoden an fast allem auf (text.length), Python rückt Blöcke ein und hat für vieles eingebaute Funktionen (len(text)).
Guido van Rossum (geboren 1956), niederländischer Programmierer. Im Dezember 1989 suchte er ein Hobbyprojekt für die Woche um Weihnachten, wenn sein Büro am Forschungsinstitut CWI in Amsterdam geschlossen war, und begann Python; 1991 erschien die erste Fassung. Dass in Python die Einrückung bestimmt, welche Zeilen zu einer Schleife gehören, übernahm er aus der Sprache ABC, an der er vorher mitgearbeitet hatte.
Den Namen hat Python nicht von der Schlange, sondern von der britischen Comedy-Truppe Monty Python. Bis 2018 entschied er als »Benevolent Dictator For Life«, wohlwollender Diktator auf Lebenszeit, über jede Änderung an der Sprache.
Foto: Doc Searls, 2006, CC BY-SA 2.0, zugeschnitten (Wikimedia Commons)
Yukihiro Matsumoto (geboren 1965), japanischer Programmierer, den alle »Matz« nennen, veröffentlichte Ruby 1995. Er wollte eine Sprache, mit der Programmieren Freude macht: »Ich hoffe, dass Ruby jedem Programmierer auf der Welt hilft, produktiv zu sein, Spaß am Programmieren zu haben und glücklich zu sein.« Auch die Website des Workspace läuft mit Ruby.
Den Namen wählte er 1993 in einem Chat mit dem Programmierer Keiju Ishitsuka, noch bevor er eine Zeile der Sprache geschrieben hatte: Der Rubin war der Geburtsstein eines Kollegen.
| Was | Ruby | Python |
|---|---|---|
| starten | ruby prog.rb |
python3 prog.py |
| ausprobieren, Zeile für Zeile |
irb, beenden mit exit
|
python3, beenden mit exit()
|
| ausgeben |
puts x; p x zeigt x, wie man es schreiben würde, mit Anführungszeichen |
print(x); print(repr(x)) mit Anführungszeichen |
| ohne Zeilenende ausgeben | print x |
print(x, end='') |
| einlesen | gets.chomp |
input() |
| Kommentar |
# bis zum Zeilenende, in beiden gleich |
|
| Argumente beim Start |
ARGV[0] ist das erste |
import sys, dann sys.argv[1]; sys.argv[0] ist der Name des Programms |
| eine andere Datei laden | require_relative 'graph' |
import graph oder from graph import build_graph
|
| nur, wenn die Datei selbst gestartet wird |
if __FILE__ == $0 … end
|
if __name__ == '__main__': |
| mit einer Meldung abbrechen | abort('Datei fehlt') |
sys.exit('Datei fehlt') |
Ein Block endet in Ruby mit end; die Einrückung ist nur für Menschen. In Python ist die Einrückung der Block: Nach if, for, def und while steht ein Doppelpunkt, und alles, was weiter eingerückt ist, gehört dazu.
if count > 3 puts 'viele' else puts 'wenige' end
if count > 3: print('viele') else: print('wenige')
Ein elsif heißt in Python elif, && || ! heißen and or not (Ruby kennt beide Schreibweisen), unless x heißt if not x.
| Was | Ruby | Python |
|---|---|---|
| nichts | nil |
None |
| wahr, falsch |
true, false
|
True, False
|
7 / 2 |
3: zwei ganze Zahlen ergeben eine ganze, abgerundet |
3.5: / gibt immer eine Kommazahl |
| ganzzahlig teilen |
7 / 2 → 3 |
7 // 2 → 3 |
| mit Komma teilen |
7.0 / 2, 7.fdiv(2) → 3.5 |
7 / 2 → 3.5 |
| Rest, Potenz |
7 % 3 → 1, 2 ** 10 → 1024, in beiden gleich; auch -7 % 3 ist in beiden 2 und -7 / 2 bzw. -7 // 2 −4 |
|
| Text zu Zahl |
'42'.to_i, '3.5'.to_f; 'x'.to_i ist 0, ohne Fehler |
int('42'), float('3.5'); int('x') ist ein ValueError
|
| Zahl zu Text | 42.to_s |
str(42) |
| Werte in Text einsetzen |
"#{name} hat #{count}", nur in doppelten Anführungszeichen |
f'{name} hat {count}' |
| auf zwei Stellen | format('%.2f', x) |
f'{x:.2f}' |
Was als falsch gilt, ist verschieden. In Ruby sind nur nil und false falsch, alles andere ist wahr, auch 0, '' und []. In Python sind auch 0, 0.0, '', [], {} und None falsch. if list: heißt in Python also »wenn die Liste nicht leer ist«; in Ruby schreibt man dafür unless list.empty? oder if list.any?.
| Was | Ruby | Python |
|---|---|---|
| Länge | s.length |
len(s) |
| ein Zeichen, das letzte |
s[0], s[-1], in beiden gleich |
|
| ein Stück |
s[1, 3]: ab 1, drei Zeichen; s[1..]: ab 1 bis zum Ende |
s[1:4]: ab 1 bis vor 4; s[1:]: ab 1 bis zum Ende |
| enthält, beginnt, endet |
s.include?('ll'), s.start_with?('Ha'), s.end_with?('o')
|
'll' in s, s.startswith('Ha'), s.endswith('o')
|
| groß, klein |
s.upcase, s.downcase
|
s.upper(), s.lower()
|
| Leerraum und Zeilenende weg |
s.strip; nur das Zeilenende: s.chomp
|
s.strip(); nur das Zeilenende: s.rstrip('\n')
|
| teilen, zusammenfügen |
'a,b'.split(','), list.join(',')
|
'a,b'.split(','), ','.join(list)
|
| Zeichen für Zeichen |
s.each_char { |c| … }, als Array s.chars
|
for c in s:, als Liste list(s)
|
| wiederholen |
'ab' * 3 → 'ababab', in beiden gleich |
|
| vorn auffüllen |
s.rjust(8, '0'), in beiden gleich |
|
| Nummer eines Zeichens und zurück |
'A'.ord → 65, 65.chr
|
ord('A') → 65, chr(65)
|
| mit einem regulären Ausdruck |
s.scan(/(\d+)(\D)/), s =~ /\d/
|
import re, dann re.findall(r'(\d+)(\D)', s), re.search(r'\d', s)
|
In Ruby lässt sich ein String verändern (s << 'x' hängt an), in Python nicht: Jede Methode gibt einen neuen String zurück, und s.upper() allein, ohne s = …, ändert nichts.
In Ruby heißt eine Liste Array, in Python Liste (list). Geschrieben wird sie in beiden gleich: [3, 1, 2].
| Was | Ruby | Python |
|---|---|---|
| Länge, enthält |
a.length, a.include?(2)
|
len(a), 2 in a
|
| hinten anhängen, wegnehmen |
a << 4 oder a.push(4), a.pop
|
a.append(4), a.pop()
|
| vorn wegnehmen | a.shift |
a.pop(0) |
| das erste, das letzte, ab dem zweiten |
a.first, a.last, a[1..]
|
a[0], a[-1], a[1:]
|
| der Reihe nach | a.each { |x| … } |
for x in a: |
| mit Index | a.each_with_index { |x, i| … } |
for i, x in enumerate(a): |
| von 0 bis 9 |
10.times { |i| … }, (0...10).each
|
for i in range(10): |
| jedes umrechnen | a.map { |x| x * 2 } |
[x * 2 for x in a] |
| nur manche | a.select { |x| x > 1 } |
[x for x in a if x > 1] |
| Summe, Minimum, Maximum |
a.sum, a.min, a.max
|
sum(a), min(a), max(a)
|
| das mit dem kleinsten Wert von … | a.min_by { |p| p[:cost] } |
min(a, key=lambda p: p['cost']) |
| sortiert |
a.sort, a.sort_by { |x| -x }; a.sort! sortiert a selbst |
sorted(a), sorted(a, key=lambda x: -x); a.sort() sortiert a selbst |
paarweise: [1, 2, 3, 4] → (1, 2), (3, 4) |
a.each_slice(2) |
zip(a[0::2], a[1::2]) |
| Nachbarn: (1, 2), (2, 3), (3, 4) | a.each_cons(2) |
zip(a, a[1:]) |
| auspacken |
first, *rest = a, in beiden gleich |
|
In Ruby gibt fast jede dieser Methoden einen neuen Wert zurück; ein ! am Ende (sort!, map!) heißt, dass sie das Array selbst ändert. Für Paare und feste Gruppen, die sich nicht ändern, nimmt man in Python ein Tupel, (3, 'B'), in Ruby einfach ein Array, [3, 'B'].
Was zu einem Schlüssel gehört, steht in Ruby in einem Hash, in Python in einem Dictionary (dict).
| Was | Ruby | Python |
|---|---|---|
| anlegen | { 'car' => 50, 'bike' => 15 } |
{'car': 50, 'bike': 15} |
| nachschlagen |
h['car']; fehlt der Schlüssel, nil
|
h['car']; fehlt der Schlüssel, ein KeyError
|
| mit einer Vorgabe, wenn er fehlt | h.fetch('bus', 0) |
h.get('bus', 0) |
| gibt es den Schlüssel? | h.key?('car') |
'car' in h |
| eintragen, entfernen |
h['bus'] = 30, h.delete('bus')
|
h['bus'] = 30, h.pop('bus')
|
| alle durchgehen | h.each { |key, value| … } |
for key, value in h.items(): |
| nur Schlüssel, nur Werte |
h.keys, h.values
|
list(h.keys()), list(h.values())
|
| zählen |
counts = Hash.new(0), dann counts[x] += 1
|
counts[x] = counts.get(x, 0) + 1, oder collections.Counter
|
| zu jedem Schlüssel eine Liste | Hash.new { |hash, key| hash[key] = [] } |
edges.setdefault(id, []).append(…) |
| zwei Werte als Schlüssel |
h[['q0', 'a']], ein Array |
h[('q0', 'a')], ein Tupel; eine Liste geht nicht |
In Ruby gibt es neben Strings Symbole als Schlüssel: { car: 50 } ist dasselbe wie { :car => 50 } und wird mit h[:car] gelesen, nicht mit h['car']. Python hat keine Symbole; {'car': 50} sieht ähnlich aus, der Schlüssel ist aber ein String.
Eine Menge enthält jeden Wert höchstens einmal: in Ruby Set['q0', 'q1'], in Python {'q0', 'q1'} (eine leere Menge ist set(), denn {} ist ein leeres Dictionary). a | b vereinigt, a & b schneidet, x in s bzw. s.include?(x) fragt. Als Schlüssel eines Dictionary braucht Python eine Menge, die sich nicht ändern kann: frozenset(s).
def distance(a, b, scale = 1) Math.hypot(a[0] - b[0], a[1] - b[1]) * scale end
import math def distance(a, b, scale=1): return math.hypot(a[0] - b[0], a[1] - b[1]) * scale
| Was | Ruby | Python |
|---|---|---|
| Rückgabe | der Wert der letzten Zeile, auch ohne return
|
nur mit return; sonst None
|
| zwei Werte zurückgeben |
return a, b gibt ein Array, x, y = f packt aus |
return a, b gibt ein Tupel, x, y = f() packt aus |
| aufrufen ohne Argumente |
f oder f()
|
immer f(); f allein ist die Funktion selbst |
| eine kleine Funktion als Wert |
square = ->(x) { x * x }, aufrufen mit square.(4)
|
square = lambda x: x * x, aufrufen mit square(4)
|
| ein Stück Code mitgeben | ein Block: a.map { |x| x * 2 }, über mehrere Zeilen do |x| … end
|
eine Funktion oder ein lambda: sorted(a, key=lambda x: -x)
|
| eine Variable von außen ändern | nur globale mit $: $visits += 1
|
global visits in der Funktion, dann visits += 1
|
| Was | Ruby | Python |
|---|---|---|
| eine Textdatei lesen | File.read('a.txt') |
open('a.txt', encoding='utf-8').read() |
| Zeile für Zeile, ohne Zeilenende | File.readlines('a.txt', chomp: true) |
for line in open('a.txt', encoding='utf-8'):, dann line.rstrip('\n')
|
| schreiben | File.write('a.txt', text) |
open('a.txt', 'w', encoding='utf-8').write(text) |
| gibt es die Datei? | File.exist?('a.txt') |
from pathlib import Path, dann Path('a.txt').exists()
|
| eine Datei als Bytes lesen |
data = File.binread('bild.gif'), ein String |
data = open('bild.gif', 'rb').read(), vom Typ bytes
|
| Bytes als Zahlen |
data.bytes, data.each_byte { |b| … }
|
list(data), for b in data:
|
| Zahlen zu Bytes |
[65, 66].pack('C*') → 'AB'
|
bytes([65, 66]) → b'AB'
|
| Text zu Bytes und zurück |
text.b; zurück data.force_encoding('UTF-8')
|
text.encode(); zurück data.decode()
|
Python trennt Text (str, 'AB') und Bytes (bytes, b'AB') streng: 'A' + b'B' ist ein Fehler, und ein Byte aus data[0] ist eine Zahl. In Ruby ist beides ein String; File.binread und .b sorgen nur dafür, dass er Byte für Byte gelesen wird. Wie man Zahlen aus mehreren Bytes liest und mit Bits rechnet, steht unter Bits und Bytes.
| Was | Ruby | Python |
|---|---|---|
| einen Fehler melden | raise ArgumentError, 'leer' |
raise ValueError('leer') |
| einen Fehler abfangen |
begin … rescue ZeroDivisionError => e … ensure … end
|
try: … except ZeroDivisionError as e: … finally:
|
Ruby nennt in der Meldung zuerst Datei und Zeile, Python zeigt einen Traceback, bei dem die wichtigste Zeile ganz unten steht. Häufige Meldungen:
undefined method 'upcase' for nil (NoMethodError), Python AttributeError: 'NoneType' object has no attribute 'upper'. Ein Schlüssel, den es nicht gibt, eine Methode, die nichts zurückgibt, oder in Python ein vergessenes return.undefined local variable or method 'x' for main (NameError), Python NameError: name 'x' is not defined. Vertippt, oder die Variable steht in einer anderen Funktion.String can't be coerced into Integer (TypeError), Python TypeError: unsupported operand type(s) for +: 'int' and 'str'. Erst umwandeln, mit to_i oder int(…).no implicit conversion of Integer into String (TypeError), Python TypeError: can only concatenate str (not "int") to str. Einsetzen mit "#{x}" oder f'{x}'.divided by 0 (ZeroDivisionError), Python ZeroDivisionError: division by zero. Oft durch die Länge einer leeren Liste.syntax error, unexpected end-of-input: ein end fehlt. Python IndentationError: expected an indented block after 'if' statement …: Nach dem Doppelpunkt ist nichts eingerückt.7 / 2 eine 3. Eine Kommazahl braucht 7.0 / 2 oder to_f.0 wahr. if count fragt dort nur, ob count nicht nil ist.return None zurück, auch wenn die letzte Zeile ein Ergebnis ausrechnet.'zwölf'.to_i ist in Ruby einfach 0. Python meldet bei int('zwölf') einen Fehler.[[]] * 3 in Python und Array.new(3, []) in Ruby sind drei Verweise auf ein und dieselbe Liste; was man an die erste anhängt, steht in allen. Drei eigene Listen geben [[] for _ in range(3)] und Array.new(3) { [] }.String, Array, Hash und so weiter, auf Englisch.