10
10

Perl 脳のための Python メモ

Last updated at Posted at 2021-06-26

こういうのは Python が書けると書けないかもしれないので書けないうちに書いておく。入出力やオブジェクト指向など不足部分はたくさんあるが、とりあえずできたところまで。当たり前だが、Python が書ける人には役に立たない。本当に知らない内容を調べながらなので、間違いは優しく指摘していただけるとありがたい。

英語が読める人は、まず最後の参考でも紹介している Python for Perl Programmers をご覧になるといいと思う。

バージョン

気にしだすとキリがないので Python 3 を対象にしている。Python 2 と Python 3 は、perl4 と perl5 ほどは違わないが、上位互換性はないと考えた方がいい。どちらでも動作するプログラムを書こうとしたら、ないとこどりをすることになるので、およそ現実的ではない。

perldoc

perldoc に相当するのは pydoc。perldoc の -f, -v, -q などに相当するオプションはないように見える。

関数を調べる: perldoc -f

pydoc3 open

ステートメントも調べられる

pydoc3 for

モジュールを調べる

pydoc3 fileinput

. でつなげることで、モジュール内のメソッドを指定することもできる。

pydoc3 fileinput.filename

FILES セクションにモジュールのパスが書かれている。

モジュールのパスを調べる: perldoc -l

pydoc 出力の FILES セクションに書かれている。

モジュールのソースを表示する: perldoc -m

不明。FILES セクションのファイルを直接開く。

perlrun

コマンド行オプション

-e, -E

コマンドラインでスクリプトを指定するのは -c オプション。

-M

モジュールは -m オプションで読み込む。

-d

デバッグはオプションではなく pdb というモジュールで行う。

python3 -m pbm a.py

デバッグではないが、-i オプションを指定すると、スクリプトの実行後に対話型モードに入る。

変数 / 名前空間

use / require

import を使って別モジュールを利用する。use のように自動的に名前空間に取り込まれることはないので require に近い。identifier に * を指定すると、__all__ があればその内容、なければすべてが import される。つまり Exporter の @EXPORT に相当するのが __all__ だが、あまり使われている気配はないし、* を import することは推奨されていない。

import foo                 # use foo(); / require foo;
from foo import *          # use foo;
import foo.bar.baz         # use foo::bar::baz();
import foo.bar.baz as fbb  # use aliased 'foo::bar::baz' => 'fbb';
                           # use Package::Alias 'fbb' => 'foo::bar::baz';
from foo.bar import baz    # use foo::bar qw(baz);
from foo import attr       # use foo qw(attr)

package

ファイルがモジュールに対応し、宣言する必要も手段もない1。グローバルというのは、モジュール内でグローバルという意味。別のモジュールを参照する場合は import する。Python にも「パッケージ」はあるが別のもの。

my

対応するものはない。関数内で代入すると自動的にローカルになる。グローバルで扱いたい場合には global 宣言する。また、関数以外のブロックスコープのようなものは存在しない2

参照する場合は、ローカルになければグローバル変数が参照される。しかし、関数内で代入が行われていると、それがどこであっても関数内のすべてのアクセスはローカルスコープになる3。つまり次のコードは i = 42 の行がなければグローバル変数を参照するので問題ないが、あることで実行時に未初期化エラーになる。

i = 0
def f():
    print(i)
    i = 42
f()

our

関数内での our 宣言は global に近い意味を持つが、Perl の場合トップレベルで my で宣言するのが普通なので、あまりそのような使い方はしない。

一般的な用法に対応するものはない。特に宣言しなくても import すれば、別モジュールのデータにアクセスできて、それを防ぐ手段はない。常に no strict 'vars' の状態か。

import される側で __all__ を定義してあると * で import されるのを防ぐことはできるが、直接指定すればアクセス可能。

local

Python はレキシカルスコープなので、ダイナミックスコープの変数を宣言することはできない。

nonlocal は、非ローカルスコープを参照するための宣言。

state

同じものはない。

ジェネレータで代用できることもある。

# sub nextint { state $i++ }
def nextint(next=0):
    while 1:
        yield next
        next += 1

クロージャー

ローカルスコープの関数を作って、それを返すことでクロージャーを実現できる。lambda 式はあるが無名関数はないので、必要なくても名前はつけなければならない。関数の外側のスコープを参照するために nonlocal 宣言する。

# do { my $i; sub { ++$i } }
# sub { ++(state $i) } }
def counter():
    i = 0
    def c():
        nonlocal i
        i += 1
        return i
    return c

文字列マッチ: m//

単純なマッチ: m//

正規表現を使うには re モジュールを使う。パターンで使用する r で始まるのは raw string と呼ばれ、バックスラッシュをエスケープする必要がないだけで、普通の文字列リテラル。

re.search

import re
if (re.search(r'\d+', '123-4567')):  # if ("123-4567" =~ /\d+/) {
    print("found")

m// に相当するのは re.match ではなく re.searchre.match は、文字列の先頭からマッチする。re.Match オブジェクトが返る。

str.find

固定文字列の場合は、str オブジェクトのメソッドが使える。

if ("abcde".find("bcd") > 0):
    print("found")

in

文字列に対して in オペレータを使うこともできる。

if ('bcd' in 'abcde'):
    print("found")

str.endswith

末尾マッチは str.endswith でも可能。

name = 'a.out'
if name.endswith('.out'): print("executable")

キャプチャーグループ: (...)

m = re.search(r'(\d+)-(\d+)', "123-4567")
m.group(0)  # 123-4567
m.group(1)  # 123
m.group(2)  # 4567

繰り返しマッチ: m//g

re.finditer

re.Match オブジェクトを返すイテレータ。

# while ('123-4567' =~ /\d+/g) {
for m in re.finditer(r'\d+', '123-4567'):
    print(m.group(0))

re.findall

文字列のリストを返す。

# @all = '123-4567' =~ /\d+/g;
all = re.findall(r'\d+', '123-4567')       # ['123', '4567']
all = re.findall(r'(\d)(\d*)', '123-4567') # [('1', '23'), ('4', '567')]

%+, %LAST_PAREN_MATCH

re.Match オブジェクトの re.Match.start, re.Match.end メソッドを使う。

for m in re.finditer(r'\d+', '123-4567'):
    print(m.start(0), m.end(0))
# 0 3
# 4 8

pos()

相当するものが見当たらない。re.finditer などの引数でマッチする範囲を指定することはできる。参照であれば、前述の re.Match.end で代用は可能か。そもそも Perl でも pos を使ったことがある人は少ないだろう。

複数行マッチ: m//m

3番目の引数に re.M あるいは re.MULTILINE を指定する。

# while (/^\d+/m) {
for m in re.finditer(r'^\d+', "123\n4567\n", re.M):
    print(m.group(0))

これは re.finditer(r'^\d+', "123\n4567\n", flags=re.M) と書いても同じだ。詳しくは Python の名前付き引数について調べるべし。

flags のパラメータは、1文字であれば Perl 互換なのでそれを使うのがわかりやすい。しかし re.Sre.DOTALLre.Xre.VERBOSE という具合で、ロングネームはちょっと違う。

大文字小文字を無視: m//i

re.I あるいは re.IGNORECASE を指定する。フラグを複数指定するためには | で連結する。

# while (/^abc/im) {
for m in re.finditer(r'^abc', 'abc\nABC', re.I|re.M):
    print(m.group(0))

qr/.../

事前にコンパイルするためには re.compile を使う。

# my $startdigit = qr/^\d+/m;
import re
startdigit = re.compile(r'^\d+', re.M)
print(re.findall(startdigit, "123-4567\n234-5678")) # ['123', '234']

文字列置換: s///

Python では、文字列データを編集することはできず、常に新しい文字列を生成する。s///r がデフォルト動作だと思えばいい。

str.replace

固定文字列の置換は str オブジェクトの replace メソッドでできる。複数ある場合はすべて置換され、3番目の引数で回数を指定できる。

'abcdefg'.replace('cde', '_')    # 'ab_fg'
'ababab'.replace('ab', 'xx')     # 'xxxxxx'
'ababab'.replace('ab', 'xx', 2)  # 'xxxxab'

re.sub

正規表現による文字列置換には re.sub を使う。

print(re.sub(r'\d+', 'XXXX', "12-345-6789"))  # s/\d+/XXXX/g
# XXXX-XXXX-XXXX

デフォルトで /g をつけたのと同じ動作になるので、回数を制限したければ、3番目の引数に指定する。

print(re.sub(r'\d+', 'XXXX', "12-345-6789", 1))  # s/\d+/XXXX/
# XXXX-345-6789
print(re.sub(r'\d+', 'XXXX', "12-345-6789", 2))
# XXXX-XXXX-6789

スクリプトの実行結果で置換する: s///e

置換文字列の部分には関数を指定することができる。関数には re.Match オブジェクトが渡され、その結果に置換される。

import re
def quote(m): return '[' + m.group(0) + ']'
print(re.sub(r'\d+', quote, "12-345-6789"))  # s/(\d+)/quote($1)/ge
# [12]-[345]-[6789]

当然、lambda 式も書ける。

print(re.sub(r'\d+', lambda m: '[' + m.group(0) + ']', "12-345-6789"))
# [12]-[345]-[6789]

が、lambda 式には式しか書けないので、複雑な処理を記述することはできない。

パターンの方には r''' 文字列と re.X を使って s///x 相当のことはできるので、複雑なパターンを記述することはできる。

Unicode プロパティ

正規表現で Unicode プロパティを使うためには re ではなく、regex モジュールを使わなければならないようだ。日本語を処理する場合使えないと結構困る。

参考: 正規表現での漢字マッチをUnicodeプロパティーを使って綺麗に書く方法 in Python - Qiita

printf, sprintf

似たような機能がいくつもある。% 演算子は古くからあって f-文字列は比較的最近導入されたものらしい。

% 演算子

sprintf に近い感覚で使える。

"Hello %s" % "World!"
"%s %s!" % ("Hello", "World")
"%10s: %d (%3d%%)" % ("Tokyo", 22, 22/55*100)  #      Tokyo: 22 ( 40%)

str.format

{数字}: プレースホルダー

'Hello {}!'.format('World')
'{} {}!'.format('Hello', 'World')
'{1} {0}!'.format('World', 'Hello')
# Hello World!

キーワード引数

print("{say} {who}!".format(say='Hello', who='World'))

vars 関数

say='Hello'
who='World'
print("{say} {who}!".format(**vars()))

f-文字列

say='Hello'
who='World'
f'{say} {who}!'
# Hello World!

書式指定

%s

'Hello {:10s}!'.format('World')   # Hello World     !
'Hello {:<10s}!'.format('World')  # Hello World     !
'Hello {:>10s}!'.format('World')  # Hello      World!
'Hello {:^10s}!'.format('World')  # Hello   World   !

%d

'1+2={}'.format(1+2)       # 1+2=3
'1+2={:d}'.format(1+2)     # 1+2=3
'1+2={:3d}'.format(1+2)    # 1+2=  3
'1+2={:<3d}!'.format(1+2)  # 1+2=3  !
'1+2={:>03d}!'.format(1+2) # 1+2=003!
'1+2={:<03d}!'.format(1+2) # 1+2=300!

リスト操作

push, pop, shift, unshift

a.pop()         # pop @a
a.pop(0)        # shift @a
a.append(50)    # push @a, 50
a.insert(0,10)  # unshift @a, 10

splice

slice で同様のことができる。文字列と違って代入もできる。

s=list(range(10))
s[:2]  # [0, 1]
s[2:4] # [2, 3]
s[-2:] # [8, 9]
s[::2] # [0, 2, 4, 6, 8]

grep

grep 相当の filter と、そのままの map があるが、返すのはリストではなくイテレータオブジェクトであることに注意。内包表記 (comprehension) で書くこともできるし、その方が望ましいとされているらしい。

# grep { $_ % 2 == 0 } 0..9
list(filter(lambda x: x % 2 == 0, range(10)))
[ x for x in range(10) if x % 2 == 0 ]
# [0, 2, 4, 6, 8]

map

# map { $_ * 2 } 0..9
list(map(lambda x: x * 2, range(10)))
[ x * 2 for x in range(10) ]
# [0, 2, 4, 6, 8, 10, 12, 14, 16, 18]
[ x if x % 2 == 0 else 'odd' for x in range(10) ]
# [0, 'odd', 2, 'odd', 4, 'odd', 6, 'odd', 8, 'odd']

reverse

reversed もイテレータを返す。

list(reversed(range(10)))
# [9, 8, 7, 6, 5, 4, 3, 2, 1, 0]

array.reverse は、リストの内容を反転する。

l=list(range(10))
l.reverse()
# [9, 8, 7, 6, 5, 4, 3, 2, 1, 0]

sort

sorted を使う。リストの内容を操作するためには sort メソッドを使う。key 関数で比較対象を指定することができる。

sorted(n)                       # sort @n
sorted(n, reverse=True)         # sort { $b <=> $a } @n
sorted(s, key=len)              # sort { length($a) <=> length($b) } @s
sorted(s, key=str.lower)        # sort { lc($a) cmp lc($b) } @s
sorted(s, key=lambda x:x[1:])   # sort { substr($a,1) cmp substr($b,1) } @s

Perl で比較関数を指定する場合、効率を求めるなら Schwartzian transform とかを使うわけだが、Python の key 関数にはそのような問題はない。key 関数で複数の値を返すことで、複数キーでのソートも可能。

文字列操作

chop, chomp: 削除する

s = s[:-1]                      # 最後の文字を削除する
s.replace("\n", "")             # (すべての)改行を削除する
if s.endswith("\n"): s = s[:-1] # 最後の改行を削除する
re.sub(r"\n\Z", '', s)          # 最後の改行を削除する

なぜ \z ではないかというと、Python の正規表現に \z は存在しないから。"\n\n" で終わってる場合、どちらの改行が削除されるのかわからなくて気持ち悪くても我慢しよう 4

s.strip()   # 行頭と行末の空白をすべて削除する
s.lstrip()  # 行頭の空白をすべて削除する
s.rstrip()  # 行末の空白をすべて削除する

連結する

.: 文字列同士を連結する

'foo' + 'baz' # 'foo' . 'baz'

join: リストを文字列で連結する

'-'.join(['foo', 'bar', 'baz'])  # join('-', qw(foo bar baz))
# 'foo-bar-baz'

x: 繰り返す

'foo' * 100
# 'foo' x 100

split: 分割する

split '-', ...

固定文字列で分割するなら str の split メソッド。

'12-345-6789'.split('-')
# ['12', '345', '6789']

split /re/, ...

正規表現で分割するなら re を使う。

import re
re.split(r'[-+]', '12-345+6789')
# ['12', '345', '6789']
re.split(r'([-+])', '12-345+6789')
# ['12', '-', '345', '+', '6789']

split //, ...

str.split に空文字列を渡すとエラーになる。
文字列を1文字ずつ分割なら list に渡せばいい。

# split(//, "abcde")
list("abcde")
# ['a', 'b', 'c', 'd', 'e']

いろいろあってややこしい。

substr

slice を利用すれば、だいたい同じことができる。ただし、代入はできない。(offset, length) ではなく (start, end) であることに注意。

s = 'abcdef'
s[2]   " 'c'
s[:2]  # 'ab'
s[2:4] # 'cd'
s[-2:] # 'ef'
s[::2] # 'ace'

die, exit

quit(), exit()

quitexit を使えと書いてある資料が多いが、公式ドキュメントには使うなと書いてある。

sys.exit()

Perl の die に近いのは sys.exit か。内部的には例外を発生させるだけなので try 文で捕捉することもできる。

import sys
sys.exit(1)      # status 1 で終了
sys.exit("bye")  # メッセージを表示して status 1 で終了

os._exit()

直接的に終了したければ os._exit を使う。

raise

例外を発生させるためには raise を使う。

eval

eval, exec

Python の eval が対象とするのはだ。Python スクリプトを評価したい時には exec を使う。

eval('a = 1 + 2')  # ERROR!
exec('a = 1 + 2')  # OK
a = eval('1 + 2')  # OK

2番目以降の引数で名前空間を指定することができる。

変数

@INC

sys.path

@ARGV

sys.argv

モジュール

Getopt::Long

argparse

import argparse
import fileinput

def main():
    global args
    parser = argparse.ArgumentParser()
    parser.add_argument('files', nargs='*')
    parser.add_argument('--number', '-n', action='store_true')
    args = parser.parse_args()
    with fileinput.input(args.files) as f:
        process(f)

List::Util

min, max, sum

ママ

any

文字列がリストに含まれるかは in で調べられる。

# if (any { $_ eq 'bar' } qw(foo bar baz)) {
if ('bar' in [ 'foo', 'bar', 'baz' ]):
    print("yes")

Python には集合を扱うための set 型がある。

foobar = { 'foo', 'bar', 'baz' }
if ('bar' in foobar):
    print("yes")

reduce

functools.reduce を使う。

zip

zip はある。

pairmap

スマートだが、長さが違う場合には要注意。

# pairmap { say($a, $b) } @data 
# while (my($a, $b) = splice @data, 0, 2) {
data = [1,2,3,4,5,6]
for a,b in zip(data[0::2], data[1::2]):
    print(a, b)

参考

Python for Perl Programmers

これはよい。筆者が Python の参考書には Perl プログラマが知りたいことがちっとも書いてないことを不満に思い、第一章に書いとけという内容をまとめたというページ。量的には少ないのですぐ読めて役に立つ代わりに、リファレンスにはならない。

PerlPhrasebook

もっと長い文章が読める人は PerlPhrasebook という資料がある。Guido van Rossum、Tom Christiansen、Larry Wall も協力しているので内容に間違いはない。ただ、バージョン 2.4 の頃に書かれたものでかなり古い。また、データ構造に関する記述が多いためか、一通り目を通してはみたが案外求めることは書かれていない印象だ。ベースになっているのが Perl Data Structures Cookbook ということなので仕方ない。ただ、これを読む暇があったら、普通の入門資料を読んだ方が早いんじゃないだろうか。

PLEAC Project

PLEAC Project は、Perl Cookbook の内容を様々なプログラミング言語に翻訳するものだ。Python 版は pleac_python。しかし、内容はすべて Python になっているので、単なる Python の優れた文献となっているはず。両者を比較して読めば役に立つに違いないので、時間がある方はどうぞ。

PerlユーザのためのPython移行ガイド

まさにそのもずばりの本だが、いかんせん2002年と古すぎる。読んでないので、コメントあればお願いします。

Equivalents in Perl and Python

配列操作の比較表: Ruby, Python, JavaScript, Perl, C++

各言語について配列操作を比較したもの。逆引き的な使い方は簡単ではないが、参考にはなる。Perl 視点で整理したらいいかも。

  1. Perl の場合、複数のファイルで同じ package を宣言できるが、Python では必ずファイル名がモジュール名になってしまう。同様な管理をしたい場合は、ファイルを分割して import foo * することで、そのファイルの定義をすべて取り込むことができる。

  2. lambda 式や内包表記など明示的に指定するものは別。

  3. これは JavaScript で var が目の敵にされる変数の巻き上げと同質の現象だと思うのだが、なぜか Python 利用者はそれをあまり大きな問題とは感じていないようだ。Python 3 で何の対策も取らなかったのだから、開発コミュニティも同様なのだろう。そもそも宣言がないので、コードの途中でスコープが変わるという概念自体がないためだろうか。

  4. これについて調べてたら、こんな記事を発見して、ちょっと笑った :-)。「違いませんか」ってどういう意味だろう?日本語って難しい。

10
10
1

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