LoginSignup
142
131

More than 5 years have passed since last update.

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

Last updated at Posted at 2017-03-05

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") ) とは書けないところが残念。

最後に

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

142
131
21

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
142
131