Edited at

ruby でこう書くのは、python ならこう書く、のメモ

More than 1 year has passed since last update.

ruby に慣れていて python に慣れていないんだけど、python を書く機会が増えてきたので備忘録のような感じで。

python は完全に初心者。

python 3。python 2.x のことは気にしないことにした。

手元の処理系


  • ruby 2.4.0p0 (2016-12-24 revision 57164) [x86_64-darwin16]

  • Python 3.5.2 :: Anaconda 4.2.0 (x86_64)

で確認している


長さ


ruby

ary_len = [1,2,3].size # [1,2,3].length でもいい

hash_len = {a:1}.size # {a:1}.length でもいい
string_len = "hoge".size # "hoge".length でもいい

range_len = (1..9).size # Range#length はない。



python3

ary_len = len([1,2,3])

dic_len = len({"a":1})
string_len = len("hoge")
range_len = len(range(1,10)) # 1〜9

まあこの辺りは簡単。


Object の周辺



ruby

[].class #=> Array

1.class #=> Integer
[].is_a?(Enumerable) #=> true
Enumerable===[] #=> true
Enumerable==="" #=> false
[1,1.0,1r,1i].map{ |x| x.is_a?( Numeric ) } #=> [true, true, true, true]


python3

type([]) #=> <class 'list'>

type(1) #=> <class 'int'>

ruby の Enumerable かどうかに相当しそうな処理は、以下の通り:


python3

from collections import Iterable

isinstance([], Iterable) #=> True
isinstance("", Iterable) #=> True

※ special thanks to 大樹さん( http://twitter.com/WniKwo/status/838737021719883776 )

ruby の文字列は Enumerable じゃないけど、python の文字列は Iterable なので要注意。

python で ruby の Numeric のようなクラスを使う例は下記:


python3

from numbers import Number

[isinstance(x,Number) for x in [1.0, 1, ""]] #=> [True,True,False]

※ special thanks to 大樹さん( http://twitter.com/WniKwo/status/838742967426809856 )

Number と Iterable。import すると基底クラスの名前にアクセスできるようになるということだろうか。


複製


ruby

copied = [1,2,3].dup



python3

import copy

copied = copy.copy([1,2,3])

一般的なオブジェクトの複製には、python では import が必要。

とはいえ、リストのコピーは以下のイディオム、メソッド、コンストラクタが使える:


python3

copied1 = [1,2,3][:] # たぶんこれが普通。

copied2 = [1,2,3].copy()
copied3 = list([1,2,3]) # たぶんコンストラクタ

dict にも copy メソッドがある:


python3

copied = {1:2}.copy()

copied = dict({1:2}) # たぶんコンストラクタ

※ special thanks to 大樹さん ( http://twitter.com/WniKwo/status/838745054864793600 )

※ special thanks to cielavenir さん( http://qiita.com/Nabetani/items/50b0f6533a15d8fb2ae5#comment-293cb5a474296a799d72 )

しかし。numpy の array は [:] でコピーにならない。

import numpy as np

a=np.array([1,2,3])
b=a[:]
b[2]=100
print(a) #=> [ 1 2 100]

罠だね。コピーしたい場合には b=a.copy() のように copy メソッドを使うか、コンストラクタを使う。


比較


ruby

[]==[] #=> true 普通の比較

[].equal?([]) #=> false 同じオブジェクトを指しているかどうか


python3

[]==[] #=> true 普通の比較

[] is [] #=> false 同じオブジェクトを指しているかどうか


文字列化


ruby

"hoge".to_s #=> 「hoge」

"hoge".inspect #=> 「"hoge"」
65.chr #=> "A"


python3

str("hoge") #=>「hoge」

repr("hoge") #=> 「'hoge'」
chr(65) #=> "A"

※ special thanks to cielavenir さん : http://qiita.com/Nabetani/items/50b0f6533a15d8fb2ae5#comment-5281280cd4820f63f52f


Array の周辺


range から list/array への変換


ruby

(1..10).to_a #=> [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

[*1..10] #=> [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]


python3

list(range(1,11)) #=> [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]


※ special thanks to あんちもん2 さん : http://twitter.com/antimon2/status/838192408873488385


部分を取り出す


ruby

[*10..100][10,3] #=> [20, 21, 22]

[*10..100][10..12] #=> [20, 21, 22]


python3

list(range(10,101))[10:13] #=> [20, 21, 22]


他に書き方はないのかなぁ。


部分の変更


ruby

a=[*1..5]

a[2,2]=9 # a[2,2]=[9] でも同じ。
a # => [1, 2, 9, 5]


python3

a=list(range(1,6))

a[2:4]=[9] # a[2:4]=9 とは書けない
a #=> [1, 2, 9, 5]

ruby は、a[2,2]=9 とか a[2..3]=[9] とか、いろいろ書き方があるけど、python は上記の一種類だけかな。


最後


ruby

[1,2,3][-1] #=> 3

[1,2,3].last #=> 3
[1,3,5,7,9].last(2) #=> [7, 9]


python3

[1,2,3][-1] #=> 3

[1,3,5,7,9][-2:] #=> [7,9]

python に last メソッドはないみたい。


push / unshift


ruby

a=[1,2,3]

a.push 9
a.unshift 8
a #=> [8,1,2,3,9]


python3

a=[1,2,3]

a.append(9)
a.insert(0,8)
a #=> [8,1,2,3,9]

python には、先頭に追加する専用のメソッドはないみたい。


pop / shift


ruby

a=[1,2,3,4]

b = a.pop
c = a.shift
[ a, b, c ] #=> [[2, 3], 4, 1]

d=[1,2,3,4]
e=d.pop(2) # 先頭の2個を e に移動
[d,e] #=> [[1,2],[3,4]]



python3

a=[1,2,3,4]

b = a.pop()
c = a.pop(0)
[ a, b, c ] #=> [[2, 3], 4, 1]

d=[1,2,3,4]
e=d[-2:]
d[-2:]=[]
[d,e] #=> [[1,2],[3,4]]

python は pop にどこから取ってくるかの引数があって、0 を指定すると shift の動きになる。

python の d[-2:]=[] は、del d[-2:] でもいい。

※ special thanks to cielavenir さん : http://qiita.com/Nabetani/items/50b0f6533a15d8fb2ae5#comment-25eff4b893d11a726f51

ruby の d.pop(2) に相当するメソッドは python にはない模様。


map


ruby

[1,2,3].map{ |x| x*2 } #=> [2,4,6]

%w( 1 2 3 ).map(&:to_i) #=> [1,2,3]


python3

[x*2 for x in [1,2,3]] #=> [2,4,6]

list(map(int,["1","2","3"])) #=>[1,2,3]


inject / reduce


ruby

[1,2,3].inject(0){ |acc,x| acc+x } #=> 6

[1,2,3].inject(0, &:+) #=> 6


python3

import functools

functools.reduce(lambda x,y:x+y, [1,2,3], 0) #=> 6
functools.reduce(int.__add__, [1,2,3], 0) #=> 6

上記の例は、int.__add__ ではなく、operator.add を使ったほうが良い。

special thanks to antimon2 さん:see http://qiita.com/Nabetani/items/50b0f6533a15d8fb2ae5#comment-bb3beb6fe012b4ebefe6


select


ruby

[1,2,3,4].select{ |x| x.even? } #=> [2, 4]



python3

[x for x in [1,2,3,4] if x%2==0 ] #=>[2, 4]



最大値


ruby

[1,5,13,21].max #=> 21

[1,5,13,21].max_by{ |x| x % 10 } #=> 5


python3

max([1,5,13,21]) #=> 21

max([1,5,13,21],key=lambda x:x%10 ) #=> 5


flatten


ruby

[1,[[2,3],4],5].flatten #=> [1,2,3,4,5]


python に flatten はないらしい。

see http://d.hatena.ne.jp/xef/20121027/p2


uniq


ruby

%w( f o o b a r b a z ).uniq #=>  ["f", "o", "b", "a", "r", "z"]



python3

list(set("foobarbaz")) #=> ['z', 'f', 'b', 'a', 'o', 'r'] 順番が保存されない


順番を保存したい場合は Python Tips:リストから重複した要素を削除したい のようにする


非破壊的 sort


ruby

[1,5,13,20].sort_by{ |x| x%10 } #=> [20,1,13,5]



python3

sorted([1,5,13,20], key=lambda x:x % 10 ) #=> [20, 1, 13, 5]


python の sort は安定。ruby の sort は安定ではない。


逆順


ruby

a=[1,3,5,7]

a.reverse #=> [7,5,3,1] 非破壊的
a.reverse! # 破壊的に逆順
a #=> [7,5,3,1]


python3

a=[1,3,5,7]

list(reversed(a)) #=>[7, 5, 3, 1] 非破壊的
a[::-1] #=> [7, 5, 3, 1] 非破壊的
a.reverse() # 破壊的に逆順
a #=> [7,5,3,1]

素人には「::-1」は思いつかない。

reversed の返戻値は、list_reverseiterator であって、list ではないので要注意。


zip


ruby

[1,2,3].zip(%w(a b c)) #=> [[1, "a"], [2, "b"], [3, "c"]]

[1,2,3].zip(%w(a b)) #=> [[1, "a"], [2, "b"], [3, nil]]


python3

list(zip([1,2,3],["a","b","c"])) #=> [(1, 'a'), (2, 'b'), (3, 'c')]

list(zip([1,2,3],["a","b"])) #=> [(1, 'a'), (2, 'b')]

ruby はレシーバに合わせられる。

python は短い方に合わせられる。

python は、zip しただけだと list にはならない。list のコンストラクタに渡すとタプルのリストになる。


乱数


サンプリング


ruby

[1,2,3].sample #=> 1 or 2 or 3

[1,3,5].sample(2) #=> [5,3], [1,5] など。重複しない。


python3

import random

random.choice( [1,2,3] ) #=> 1 or 2 or 3
random.sample( [1,3,5], k=2 ) #=> [5,3], [1,5] など。重複しない。


0以上1未満の浮動小数点数


ruby

rand



python3

import random

random.random()


0以上10未満の整数


ruby

rand(10)


あるいは


ruby

rng=Random.new

rng.rand(10)
rng.rand(0..9)
rng.rand(0...10)


python3

import random

random.randint(0,9)

python の範囲指定は両端含む。


制御構造


if文


ruby

a=if 1.even?

"foo"
elsif 1.odd?
"bar"
else
"baz"
end


python3

if 1%2==0:

a="foo"
elif 1%2==1:
a="bar"
else:
a="baz"

python は ruby と違って if 文は値を持たない。ruby の elsif は、python は elif。また、後置ifはない。


case〜when

python には、case〜when や switch〜case に相当する制御構造はない。


繰り返し


ruby

10.times do |num|

do_something
end

loop do # 無限ループ
break if some_condition
end



python3

for num in range(10):

do_something()

while True: # 無限ループ
if some_condition():
break



リテラル

全般的に、ruby はリテラルがたくさんあるけど python には(rubyとくらべると)あまりない。


有理数


ruby

a=1.3r #=> (13/10)



python3

import fractions

fractions.Fraction(13,10) #=> 13/10 に相当する値

有理数リテラルは python にはない。


正規表現


ruby

/.\s./.match("hello, world")[0] #=> ", w"



python3

import re

re.search( r".\s.", "hello, world" ).group(0) #=> ', w'

正規表現リテラルは python にはない。r"" の は生の文字列という意味の r。


文字列リテラル内の式展開


ruby

"3**10=#{3**10}" #=>  "3**10=59049"



python3

"3**10=%d" % 3**10 #=>  "3**10=59049"


python に式展開はない( 3.6 にはあるようです。special thanks to norioc さん)。printf のようなことをする必要がある。


入出力


バッファのフラッシュ


ruby

print("hoge")

$stdout.flush()

print("hoge", end="", flush=True)

import sys として sys.stdout.flush() としてもよい。


print と puts


ruby

print("hoge") #=> 末尾の改行なしで出力

puts("hoge") #=> 末尾の改行ありで出力


python3

print("hoge", end="") #=> 末尾の改行なしで出力

print("hoge") #=> 末尾の改行ありで出力


ファイルやディレクトリの存在

File.exist?("foo") #=> foo が file または directory なら true

File.file?("foo") #=> foo が file なら true
File.directory?("foo") #=> foo が directory なら true


python3

import os

os.path.exists("foo") #=> foo が file または directory なら true
os.path.isfile("foo") #=> foo が file なら true
os.path.isdir("foo") #=> foo が directory なら true

python には ruby の File.exist? に相当するメソッドはなさそう。

os.path.exists があることに気づいていなかった。

@tokyo_gs さん、ありがとうございます。


ファイル open/close


ruby

a=File.open("hoge.txt"){ |f| f.read(10) }



python3

with open("hoge.txt") as f:

a=f.read(10)

python は、with を使うとファイルを自動的に閉じてくれる。


ファイルへの書き込み。

fopen で取れるファイルだとして:


ruby

f.puts("hoge") # 末尾に改行がつく

f.print("fuga") # 末尾に改行がつかない
f.write("piyo") # 末尾に改行がつかない


python3

print("piyo", file=f) # 末尾に改行がつく

f.write("hoge") # 末尾に改行がつかない
f.write("piyo\n") # 末尾に改行をつけるもう一つの方法

python は、f.print(略) とは書けない。

print(略, file=f) は、すごく意外だった。


未分類


条件演算


ruby

cond=true

val = cond ? "iftrue" : "iffalse"


python3

cond=True

val = "iftrue" if cond else "iffalse"

python の条件演算は、いまのところ全然慣れられそうにない。


combination


ruby

[1,2,3].combination(2).to_a #=> [[1, 2], [1, 3], [2, 3]]



python3

import itertools

list(itertools.combinations([1,2,3],2)) #=> [(1, 2), (1, 3), (2, 3)]

python は複数形。タプルになるところがポイント。

permutations もある。


Thread


ruby

Array.new(4){ |ix| Thread.new{ print ix } }.each(&:join) #=> 2103 とか



python3

import threading

ts=[threading.Thread(target=(lambda x=x:print(x))) for x in range(4)]
for t in ts:
t.start()
for t in ts:
t.join()

ruby のブロックは変数をキャプチャする際、値の参照を利用するので普通(個人の感想です)に書けばうまくいく。

<2018/3/17 訂正>

ruby でうまくいくのは、ix という変数がスコープの中に閉じ込められていて毎回生成されるからだと思う。

</2018/3/17 訂正>

python の方は デフォルト引数が怪しげだけど、lambda を使うのなら、こうしたり、二重にしたりしないとうまくいかない。理由は、python の lambda は、変数の参照をキャプチャするから。(合ってるよね?)

これが気持ち悪い人は、lambda を使わず target にメソッドを指定すれば良い。

※ spechal thanks to antimon2 さん : see http://qiita.com/Nabetani/items/50b0f6533a15d8fb2ae5#comment-c27e1b9d252c25853f0b


uuid


ruby

require 'securerandom'

SecureRandom.uuid #=> => "72568c47-e6e0-4f21-a8b5-dad3d72831b2"


python3

import uuid

str(uuid.uuid4()) #=> '9414a2d6-b954-4519-bf88-47828d6d2577'

secrets というモジュールでセキュアな乱数を作れるが、 python 3.6 以降。しかも uuid をつくる I/F はない模様。

python の uuid は、uuid1(), uuid3(), uuid4(), uuid5() がある。

uuid4() が ruby の SecureRandom.uuid に相当すると思われる。


文字列の join

スレッドの join じゃなくて、配列の要素をコンマとかでつなげるやつ。


ruby

[1,"hoge",2.34].join(",") #=> "1,hoge,2.34"



python3

",".join([str(x) for x in [ 1, "hoge", 2.34 ] ] ) #=> '1,hoge,2.34'


ruby は join すると勝手に文字列になるけど、python は文字列しか join できない。

レシーバが逆なのは知っていたけど、勝手に文字列化してくれないのは知らなかった。


文字列の split


ruby

"a,b,c".split(",") # => ["a", "b", "c"]

"a-b+c".split(/\W/) # => ["a", "b", "c"]


python3

import re

"a,b,c".split(",") #=> ["a", "b", "c"]
re.split(r"\W", "a+b-c") #=> ["a", "b", "c"]
re.compile( r"\W" ).split( "a+b-c" ) #=> ["a", "b", "c"]

正規表現か否かで呼び方を変える必要がある。

"a,b,c".split( re.compile(r"\W") ) とは書けないところが残念。


最後に

加筆予定。

もっと良い書き方があるよ! という意見募集。