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)
で確認している
#長さ
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 はない。
ary_len = len([1,2,3])
dic_len = len({"a":1})
string_len = len("hoge")
range_len = len(range(1,10)) # 1〜9
まあこの辺りは簡単。
Object の周辺
型
[].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]
type([]) #=> <class 'list'>
type(1) #=> <class 'int'>
ruby の Enumerable
かどうかに相当しそうな処理は、以下の通り:
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
のようなクラスを使う例は下記:
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 すると基底クラスの名前にアクセスできるようになるということだろうか。
複製
copied = [1,2,3].dup
import copy
copied = copy.copy([1,2,3])
一般的なオブジェクトの複製には、python では import が必要。
とはいえ、リストのコピーは以下のイディオム、メソッド、コンストラクタが使える:
copied1 = [1,2,3][:] # たぶんこれが普通。
copied2 = [1,2,3].copy()
copied3 = list([1,2,3]) # たぶんコンストラクタ
dict にも copy メソッドがある:
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 メソッドを使うか、コンストラクタを使う。
比較
[]==[] #=> true 普通の比較
[].equal?([]) #=> false 同じオブジェクトを指しているかどうか
[]==[] #=> true 普通の比較
[] is [] #=> false 同じオブジェクトを指しているかどうか
文字列化
"hoge".to_s #=> 「hoge」
"hoge".inspect #=> 「"hoge"」
65.chr #=> "A"
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 への変換
(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]
list(range(1,11)) #=> [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
※ special thanks to あんちもん2 さん : http://twitter.com/antimon2/status/838192408873488385
部分を取り出す
[*10..100][10,3] #=> [20, 21, 22]
[*10..100][10..12] #=> [20, 21, 22]
list(range(10,101))[10:13] #=> [20, 21, 22]
他に書き方はないのかなぁ。
部分の変更
a=[*1..5]
a[2,2]=9 # a[2,2]=[9] でも同じ。
a # => [1, 2, 9, 5]
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 は上記の一種類だけかな。
最後
[1,2,3][-1] #=> 3
[1,2,3].last #=> 3
[1,3,5,7,9].last(2) #=> [7, 9]
[1,2,3][-1] #=> 3
[1,3,5,7,9][-2:] #=> [7,9]
python に last メソッドはないみたい。
push / unshift
a=[1,2,3]
a.push 9
a.unshift 8
a #=> [8,1,2,3,9]
a=[1,2,3]
a.append(9)
a.insert(0,8)
a #=> [8,1,2,3,9]
python には、先頭に追加する専用のメソッドはないみたい。
pop / shift
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]]
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
[1,2,3].map{ |x| x*2 } #=> [2,4,6]
%w( 1 2 3 ).map(&:to_i) #=> [1,2,3]
[x*2 for x in [1,2,3]] #=> [2,4,6]
list(map(int,["1","2","3"])) #=>[1,2,3]
inject / reduce
[1,2,3].inject(0){ |acc,x| acc+x } #=> 6
[1,2,3].inject(0, &:+) #=> 6
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
[1,2,3,4].select{ |x| x.even? } #=> [2, 4]
[x for x in [1,2,3,4] if x%2==0 ] #=>[2, 4]
最大値
[1,5,13,21].max #=> 21
[1,5,13,21].max_by{ |x| x % 10 } #=> 5
max([1,5,13,21]) #=> 21
max([1,5,13,21],key=lambda x:x%10 ) #=> 5
flatten
[1,[[2,3],4],5].flatten #=> [1,2,3,4,5]
python に flatten はないらしい。
see http://d.hatena.ne.jp/xef/20121027/p2
uniq
%w( f o o b a r b a z ).uniq #=> ["f", "o", "b", "a", "r", "z"]
list(set("foobarbaz")) #=> ['z', 'f', 'b', 'a', 'o', 'r'] 順番が保存されない
順番を保存したい場合は Python Tips:リストから重複した要素を削除したい のようにする
非破壊的 sort
[1,5,13,20].sort_by{ |x| x%10 } #=> [20,1,13,5]
sorted([1,5,13,20], key=lambda x:x % 10 ) #=> [20, 1, 13, 5]
python の sort は安定。ruby の sort は安定ではない。
逆順
a=[1,3,5,7]
a.reverse #=> [7,5,3,1] 非破壊的
a.reverse! # 破壊的に逆順
a #=> [7,5,3,1]
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
[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]]
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
のコンストラクタに渡すとタプルのリストになる。
乱数
サンプリング
[1,2,3].sample #=> 1 or 2 or 3
[1,3,5].sample(2) #=> [5,3], [1,5] など。重複しない。
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未満の浮動小数点数
rand
import random
random.random()
0以上10未満の整数
rand(10)
あるいは
rng=Random.new
rng.rand(10)
rng.rand(0..9)
rng.rand(0...10)
import random
random.randint(0,9)
python の範囲指定は両端含む。
#制御構造
if文
a=if 1.even?
"foo"
elsif 1.odd?
"bar"
else
"baz"
end
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 に相当する制御構造はない。
繰り返し
10.times do |num|
do_something
end
loop do # 無限ループ
break if some_condition
end
for num in range(10):
do_something()
while True: # 無限ループ
if some_condition():
break
リテラル
全般的に、ruby はリテラルがたくさんあるけど python には(rubyとくらべると)あまりない。
有理数
a=1.3r #=> (13/10)
import fractions
fractions.Fraction(13,10) #=> 13/10 に相当する値
有理数リテラルは python にはない。
正規表現
/.\s./.match("hello, world")[0] #=> ", w"
import re
re.search( r".\s.", "hello, world" ).group(0) #=> ', w'
正規表現リテラルは python にはない。r"" の は生の文字列という意味の r。
文字列リテラル内の式展開
"3**10=#{3**10}" #=> "3**10=59049"
"3**10=%d" % 3**10 #=> "3**10=59049"
python に式展開はない( 3.6 にはあるようです。special thanks to norioc さん)。printf のようなことをする必要がある。
入出力
バッファのフラッシュ
print("hoge")
$stdout.flush()
print("hoge", end="", flush=True)
import sys
として sys.stdout.flush()
としてもよい。
print と puts
print("hoge") #=> 末尾の改行なしで出力
puts("hoge") #=> 末尾の改行ありで出力
print("hoge", end="") #=> 末尾の改行なしで出力
print("hoge") #=> 末尾の改行ありで出力
ファイルやディレクトリの存在
File.exist?("foo") #=> foo が file または directory なら true
File.file?("foo") #=> foo が file なら true
File.directory?("foo") #=> foo が directory なら true
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
a=File.open("hoge.txt"){ |f| f.read(10) }
with open("hoge.txt") as f:
a=f.read(10)
python は、with を使うとファイルを自動的に閉じてくれる。
ファイルへの書き込み。
f
は open
で取れるファイルだとして:
f.puts("hoge") # 末尾に改行がつく
f.print("fuga") # 末尾に改行がつかない
f.write("piyo") # 末尾に改行がつかない
print("piyo", file=f) # 末尾に改行がつく
f.write("hoge") # 末尾に改行がつかない
f.write("piyo\n") # 末尾に改行をつけるもう一つの方法
python は、f.print(略)
とは書けない。
print(略, file=f)
は、すごく意外だった。
未分類
条件演算
cond=true
val = cond ? "iftrue" : "iffalse"
cond=True
val = "iftrue" if cond else "iffalse"
python の条件演算は、いまのところ全然慣れられそうにない。
combination
[1,2,3].combination(2).to_a #=> [[1, 2], [1, 3], [2, 3]]
import itertools
list(itertools.combinations([1,2,3],2)) #=> [(1, 2), (1, 3), (2, 3)]
python は複数形。タプルになるところがポイント。
permutations
もある。
Thread
Array.new(4){ |ix| Thread.new{ print ix } }.each(&:join) #=> 2103 とか
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
require 'securerandom'
SecureRandom.uuid #=> => "72568c47-e6e0-4f21-a8b5-dad3d72831b2"
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 じゃなくて、配列の要素をコンマとかでつなげるやつ。
[1,"hoge",2.34].join(",") #=> "1,hoge,2.34"
",".join([str(x) for x in [ 1, "hoge", 2.34 ] ] ) #=> '1,hoge,2.34'
ruby は join すると勝手に文字列になるけど、python は文字列しか join できない。
レシーバが逆なのは知っていたけど、勝手に文字列化してくれないのは知らなかった。
##文字列の split
"a,b,c".split(",") # => ["a", "b", "c"]
"a-b+c".split(/\W/) # => ["a", "b", "c"]
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") )
とは書けないところが残念。
最後に
加筆予定。
もっと良い書き方があるよ! という意見募集。