RubyistのためのPython入門

# はじめに

普段はRubyを書いていて、これからPythonを書くという人向けの入門記事です。

Rubyのやり方でPythonで書くのではなく、自然にPythonを書いていると思えるようになることがこの記事のゴールです。

そのためこの記事では基本的な処理の実装をRubyとPythonで比較し、お互いの特徴を見ながらPythonでの考え方や書き方を明らかにしていきます。

Pythonは3.6以降を前提としています。

それではさっそく見ていきましょう。

## 基本要素

まずは数値や文字列など代表的な組み込み型の比較です。

### Ruby

```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

```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

```ruby
s = 'apple'
i = 100

result = '#{s} #{i}'
puts(result)
# => #{s} #{i}

result = "#{s} #{i}"
puts(result)
# => apple 100
```

### Python

```python
s = 'apple'
i = 100

result = f'{s} {i}'
print(result)
# => apple 100

result = f"{s} {i}"
print(result)
# => apple 100
```

Rubyで文字列に値を埋め込む場合はダブルクォートの文字列にする必要がありますが、
Pythonの場合はシングルクォート、ダブルクォートどちらを利用しても構いません。

## 自作オブジェクトの作成

自作のクラス定義とオブジェクトの生成です。

### Ruby

```rb
class Echo
  def initialize(message)
    @message = message
  end

  def hello
    puts("hello #{@message}")
  end
end

echo = Echo.new('world!')
echo.hello
# => hello world!
```

### Python

```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個で囲まれていますが、これは特殊メソッドと呼ばれます。

この特殊メソッドは他にもたくさんあり、標準の型、組み込み関数と合わせて、[データモデル](https://docs.python.jp/3/reference/datamodel.html)として定義されています。このデータモデルがPythonにおけるオブジェクトのモデリングやコーディングの中心的な役割を果たします。

特殊メソッドはPythonインタプリタから呼び出されることを前提としたもので、原則的に自分で直接呼び出しません。`__init__()` であれば、オブジェクトのコンストラクタ呼び出しで、プログラマがオブジェクトを初期化すべきタイミングで呼び出されます。

## 文字列と数値の相互変換

次は型同士の変換を見ていきましょう。

### Ruby

```rb
i = '100'.to_i
puts(i + 10)
# => 110

s = 100.to_s
puts(s + ' yen')
# => 100 yen
```

Rubyではオブジェクト自身が変換のメソッドを持ち、直接それを呼び出します。

### Python

```python
i = int('100')
print(i + 10)
# => 110

s = str(100)
print(s + ' yen')
# => 100 yen
```

Pythonでは `str()`、`int()`といった組み込み関数を利用します。

この組み込み関数は自作のオブジェクトとも協調して動作できるようになっています。
前述のイニシャライザ同様に、特殊メソッドをクラス内で定義することで、組み込み型と同じ振る舞いをさせることができます。

```python
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

```ruby
l = [1, 2, 3]

l.length
# => 3

l.size
# => 3
```

Rubyでは長さの取得に複数のインタフェースがあります。表現として自然な方を好みに応じて選ぶことができます。

### Python

```python
l = [1, 2, 3]

len(l)
# => 3
```

Pythonでは `len()` という組み込み関数がまた登場します。
これも単に組み込みのリストのためだけでなく、Pythonでオブジェクトから__長さ__を取得するための共通のインタフェースになっています。

自作のオブジェクトでは `__len__()` を定義することで `len()` を適用できるようになります。

```python
class MyCollection:
  def __len__(self):
    return 3

mc = MyCollection()
len(mc)
# => 3
```

データモデルを理解し、`len()` のような組み込み関数（標準的なインタフェース）と協調して動作するオブジェクトを作り、それを使ってコーディングすることで、Pythonで書かれるコードの表現に一貫性を持たせることができます。

## ブールコンテキストにおける評価

続いてif文や論理演算などのブールコンテキストに置ける値の評価を比較してみましょう。

ここはRubyとPythonで大きく違います。

### Ruby

```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]

[^1]: https://docs.ruby-lang.org/ja/latest/class/FalseClass.html

`nil` が `false` として評価されるので、ブール値評価をオブジェクトの存在チェックのイディオムとして使ったりしますね。

### Python

```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](https://www.python.org/dev/peps/pep-0008/#programming-recommendations)）。

```python
# Yes:
 if not seq:
 if seq:

# No:
  if len(seq):
  if not len(seq):
```

そしてやはりこの場合も、自作のオブジェクトでも同じ振る舞いが持たせられるよう、特殊メソッド　`__bool__()` が定義されています。

リストのようなコレクションクラスとの一貫性を保つため、`__len__()` が定義してあればこの戻り値が0の場合に `False` として評価されるようになります。これにより、ブールコンテキストでの評価が標準のリストと同じ振る舞いになり一貫性が保たれます。


## 配列/リストの加工

次はリストに対する処理について見ていきます。

```rb
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ではブロック構文を使いながら、処理の対象となるオブジェクト（=配列）を起点として値の加工や判断を行います。

```python
# マップ
[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()` といった処理のインタフェースも評価関数を受け取らず、リスト内包表記を使えば簡潔に書けることを前提としたインタフェースになっていると思います。


## 配列/リストのソート

次はソートについてです。

```ruby
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"]
```

こちらもすべて配列オブジェクトが起点として並べ替え処理を行っています。


```python
# 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として存在し、それを活用することで組み込み型とも整合性の取れた一貫したコーディングができるようになっています。

オブジェクトのメソッドではなく関数を使うのが最初気持ち悪く感じたりもしますが、データモデルというデザインを理解することで違和感が減り、より自然に読み書きできるようになると思います。