はじめに
普段はRubyを書いていて、これからPythonを書くという人向けの入門記事です。
Rubyのやり方でPythonで書くのではなく、自然にPythonを書いていると思えるようになることがこの記事のゴールです。
そのためこの記事では基本的な処理の実装をRubyとPythonで比較し、お互いの特徴を見ながらPythonでの考え方や書き方を明らかにしていきます。
Pythonは3.6以降を前提としています。
それではさっそく見ていきましょう。
基本要素
まずは数値や文字列など代表的な組み込み型の比較です。
Ruby
n = nil # NilClass
b = true || false # TrueClass, FalseClass
i = 100 # Integer
f = 1.5 # Float
a = [1, 2, 3] # Array
s = 'hello' " world" # String
sym = :symbol # Symbol
h = {a: 'apple', b: 'banana'} # Hash
Python
n = None # NoneType
b = True or False # bool
i = 100 # int
f = 1.5 # float
l = [1, 2, 3] # list
s = 'hello' " world" # str
# シンボルは存在しない。代わりに文字列を使う
h = {'a': 'apple', 'b': 'banana'} # dict
nil
やブール値の表記は違いますが、用途は同じです。
Rubyの配列がPythonではリストなど対応するクラス名に違いがあったりしますが、できることにあまり違いはありません。
できることは変わらなくてもやり方が違ったりするのですが、それについては引き続き見ていきます。
文字列への変数埋込
続いて変数の文字列への埋め込みです。
Ruby
s = 'apple'
i = 100
result = '#{s} #{i}'
puts(result)
# => #{s} #{i}
result = "#{s} #{i}"
puts(result)
# => apple 100
Python
s = 'apple'
i = 100
result = f'{s} {i}'
print(result)
# => apple 100
result = f"{s} {i}"
print(result)
# => apple 100
Rubyで文字列に値を埋め込む場合はダブルクォートの文字列にする必要がありますが、
Pythonの場合はシングルクォート、ダブルクォートどちらを利用しても構いません。
自作オブジェクトの作成
自作のクラス定義とオブジェクトの生成です。
Ruby
class Echo
def initialize(message)
@message = message
end
def hello
puts("hello #{@message}")
end
end
echo = Echo.new('world!')
echo.hello
# => hello world!
Python
class Echo:
def __init__(self, message):
self.message = message
def hello(self):
print(f"hello {self.message}")
echo = Echo('world!')
echo.hello()
# => hello world!
いくつか違いがあるのですが、注目したいのはイニシャライザの定義です。
イニシャライザの定義が他のメソッドと違いアンダースコア2個で囲まれていますが、これは特殊メソッドと呼ばれます。
この特殊メソッドは他にもたくさんあり、標準の型、組み込み関数と合わせて、データモデルとして定義されています。このデータモデルがPythonにおけるオブジェクトのモデリングやコーディングの中心的な役割を果たします。
特殊メソッドはPythonインタプリタから呼び出されることを前提としたもので、原則的に自分で直接呼び出しません。__init__()
であれば、オブジェクトのコンストラクタ呼び出しで、プログラマがオブジェクトを初期化すべきタイミングで呼び出されます。
文字列と数値の相互変換
次は型同士の変換を見ていきましょう。
Ruby
i = '100'.to_i
puts(i + 10)
# => 110
s = 100.to_s
puts(s + ' yen')
# => 100 yen
Rubyではオブジェクト自身が変換のメソッドを持ち、直接それを呼び出します。
Python
i = int('100')
print(i + 10)
# => 110
s = str(100)
print(s + ' yen')
# => 100 yen
Pythonでは str()
、int()
といった組み込み関数を利用します。
この組み込み関数は自作のオブジェクトとも協調して動作できるようになっています。
前述のイニシャライザ同様に、特殊メソッドをクラス内で定義することで、組み込み型と同じ振る舞いをさせることができます。
class MyNumber:
def __init__(self, num):
self.num = num
def __str__(self):
return f'my-number-{self.num}'
n = MyNumber(123)
str(n)
# => my-number-123
class MyString:
def __int__(self):
return 100
s = MyString()
int(s)
# => 100
配列/リストのサイズ取得
今度はリストのサイズ取得について見てみましょう。
Ruby
l = [1, 2, 3]
l.length
# => 3
l.size
# => 3
Rubyでは長さの取得に複数のインタフェースがあります。表現として自然な方を好みに応じて選ぶことができます。
Python
l = [1, 2, 3]
len(l)
# => 3
Pythonでは len()
という組み込み関数がまた登場します。
これも単に組み込みのリストのためだけでなく、Pythonでオブジェクトから__長さ__を取得するための共通のインタフェースになっています。
自作のオブジェクトでは __len__()
を定義することで len()
を適用できるようになります。
class MyCollection:
def __len__(self):
return 3
mc = MyCollection()
len(mc)
# => 3
データモデルを理解し、len()
のような組み込み関数(標準的なインタフェース)と協調して動作するオブジェクトを作り、それを使ってコーディングすることで、Pythonで書かれるコードの表現に一貫性を持たせることができます。
ブールコンテキストにおける評価
続いてif文や論理演算などのブールコンテキストに置ける値の評価を比較してみましょう。
ここはRubyとPythonで大きく違います。
Ruby
nil ? true : false
# => false
0 ? true : false
# => true
'' ? true : false
# => true
[] ? true : false
# => true
{} ? true : false
# => true
RubyではFalseClassとNilクラスのインスタンス(false
、nil
)のみが false
として評価されます。1
nil
が false
として評価されるので、ブール値評価をオブジェクトの存在チェックのイディオムとして使ったりしますね。
Python
True if None else False
# => False
True if 0 else False
# => False
True if 0.0 else False
# => False
True if '' else False
# => False
True if [] else False
# => False
True if {} else False
# => False
Pythonでは None
の他にも値がゼロとして評価されるものは False
として扱われます。
この違いは意識する必要があります。
リストや文字列のようなシーケンスであれば空シーケンスが False
となります。
特に空文字列や空リストかどうかのチェックはこの事実を利用することが推奨されています(PEP 8)。
# Yes:
if not seq:
if seq:
# No:
if len(seq):
if not len(seq):
そしてやはりこの場合も、自作のオブジェクトでも同じ振る舞いが持たせられるよう、特殊メソッド __bool__()
が定義されています。
リストのようなコレクションクラスとの一貫性を保つため、__len__()
が定義してあればこの戻り値が0の場合に False
として評価されるようになります。これにより、ブールコンテキストでの評価が標準のリストと同じ振る舞いになり一貫性が保たれます。
配列/リストの加工
次はリストに対する処理について見ていきます。
l = [1, 2, 3]
# マップ
l.map {|v| v * v }
# => [1, 4, 9]
# フィルター
l.select {|v| v.even? }
# => [2]
l.reject {|v| v.even? }
# => [1, 3]
# 縮約
l.reduce {|acc, v| acc + v}
# => 6
l.sum
# => 6
l.all? {|v| v.even? }
# => false
l.any? {|v| v.even? }
# => true
Rubyではブロック構文を使いながら、処理の対象となるオブジェクト(=配列)を起点として値の加工や判断を行います。
# マップ
[v * v for v in l]
# => [1, 4, 9]
# フィルター
[v for v in l if v % 2 == 0]
# => [2]
[v for v in l if v % 2 != 0]
# => [1, 3]
# 縮約
from functools import reduce
reduce(lambda acc, v: acc + v, l)
# => 6
sum(l)
# => 6
all([x % 2 == 0 for x in l])
# => False
any([x % 2 == 0 for x in l])
# => True
Pythonではリスト内包表記が中心的な役割を果たします。
マップやフィルタ処理もさることながら、all()
や any()
といった処理のインタフェースも評価関数を受け取らず、リスト内包表記を使えば簡潔に書けることを前提としたインタフェースになっていると思います。
配列/リストのソート
次はソートについてです。
l = [1, -2, 3, 8, -5]
# 昇順ソート(配列自身は変更しない)
l.sort
# => [-5, -2, 1, 3, 8]
l
# => [1, -2, 3, 8, -5]
# 降順ソート(配列自身は変更しない)
l.sort.reverse
# => [8, 3, 1, -2, -5]
# 配列自身を変更
l.sort!
# => [-5, -2, 1, 3, 8]
l
# => [-5, -2, 1, 3, 8]
# ソート基準指定
# https://ref.xaio.jp/ruby/classes/enumerable/sort_by
l = ["apple", "pineapple", "banana", "strawberry"]
l.sort_by {|e| e.length }
# => ["apple", "banana", "pineapple", "strawberry"]
こちらもすべて配列オブジェクトが起点として並べ替え処理を行っています。
# https://docs.python.jp/3/howto/sorting.html
# 昇順ソート(配列自身は変更しない)
l = [1, -2, 3, 8, -5]
sorted(l)
# => [-5, -2, 1, 3, 8]
# 降順ソート
sorted(l, reverse=True)
# => [8, 3, 1, -2, -5]
# 配列自身を変更
l.sort()
# => None
l
# => [-5, -2, 1, 3, 8]
# ソート基準指定
l = ["apple", "pineapple", "banana", "strawberry"]
sorted(l, key=len)
# => ['apple', 'banana', 'pineapple', 'strawberry']
Pythonでは sorted()
関数を使った場合はリスト自身を変更しませんが、リストの持つ sort()
メソッドを呼んだ場合はリスト自身を変更します。
同じインタフェースでRubyとは挙動が異なるので注意しましょう。
まとめ
いかがだったでしょうか。
Rubyではオブジェクトとそのメソッドが中心的な役割を果たしていることが分かります。
一方で、Pythonでは組み込み関数が主要なAPIとして存在し、それを活用することで組み込み型とも整合性の取れた一貫したコーディングができるようになっています。
オブジェクトのメソッドではなく関数を使うのが最初気持ち悪く感じたりもしますが、データモデルというデザインを理解することで違和感が減り、より自然に読み書きできるようになると思います。