Python メモ

2020年12月27日

モジュールパス

モジュールパスの設定は、環境変数 PYTHONPATH で行う。

export PYTHONPATH=~/mymodule:$PYTHONPATH

モジュールのパスは sys.path で取得できる。

モジュールのキャッシュを作成させない

モジュールをインポートしたときに作成されるキャッシュを作成させないようにするには、Python をオプション "-B" を指定して起動するか、環境変数 PYTHONDONTWRITEBYTECODE に 1 (値はなんでもよい) を設定すればよい。

できてしまったキャッシュを削除するには、Linux なら以下のようにする。

$ find -name __pycache__ | xargs rm -r

ソースコードの文字コード

文字コードに UTF-8 以外を用いるときは、ファイルの先頭に以下のように書く。

# -*- coding: shift-jis -*-

main

if __name__ == "__main__":
    main()

コマンドライン引数

import sys
argv = sys.argv
argc = len(argv)

コマンドライン引数の処理

ArgumentParser でコマンドライン引数の処理を行える。

from argparse import ArgumentParser

def get_args():
    parser = ArgumentParser()

    parser.add_argument("path", type=str, help="file path")
    parser.add_argument("--enable_opt", action="store_true",
            help="enable option")

    return parser.parse_args()

def main(args):
    path = args.path
    enable_opt = args.enable_opt
    ...

if __name__ == "__main__":
    args = get_args()
    main(args)

スクリプトの場所の取得

import sys
from pathlib import Path
path = Path(sys.argv[0]).resolve().parent

sys.argv[0] にはスクリプト実行時のカレントディレクトリからの相対パスが入り、resolve() は関数実行時のカレントディレクトリのパスをくっつけるので、カレントディレクトリが変更されていないことが前提になる。sys.argv[0] の代りに __file__ を用いる方法もあるが、Cython を用いた時にうまくいかない。

末尾の空白文字を削除

# line は文字列
line = line.rstrip() # 末尾の空白文字を削除
line = line.lstrip() # 先頭の空白文字を削除
line = line.strip()  # 先頭と末尾の空白文字を削除

ファイルから 1 行読み込んだときに改行文字を削除する場合は rstrip() を使えば良い。

文字を指定することもできる。たとえば、"xxx" から両側の引用符を取り除くには次のようにする。

line = line.strip("\"")

改行文字だけを取り除きたい場合は、次のようにすればよい。

line = line.strip("\r\n")

辞書のキーと値のリストの取得

# d は辞書
key = list(d.keys())
value = list(d.values())

パスの結合

import os
os.path.join(path1, path2)

あるいは

from pathlib import Path
Path(path1).joinpath(path2)

ディレクトリ名とベース名の取得

import os
dirname, basename = os.path.split(path)
dirname = os.path.dirname(path)
basename = os.path.basename(path)

あるいは

from pathlib import Path
dirname = Path(path).parent
basename = Path(path).name

拡張子の取得

import os
basename, ext = os.path.splitext(path)

あるいは

from pathlib import Path
basename = Path(path).stem
ext = Path(path).suffix

正規表現

検索

import re
r = re.search(pattern, str)

たとえば、次のように使う。

>>> s = '<a href="xxx">xxx</a>'
>>> r = re.search('href=".+"', s)
>>> r.group(0)
'href="xxx"'

href の値を取り出すには次のようにする。

>>> r = re.search('href="(.+)"', s)
>>> r.group(0)
'href="xxx"'
>>> r.group(1)
'xxx'

検索では、最も長いものが取られる。

>>> r = re.search('<.+>', s)
>>> r.group(0)
'<a href="xxx">xxx</a>'

短いものを取るには、"?" をつける。

>>> r = re.search('<.+?>', s)
>>> r.group(0)
'<a href="xxx">'

何度も同じ検索を行うなら、コンパイルも使える。

>>> m = re.compile('href="(.+)"')
>>> r = m.search(s)
>>> r.group(1)
'xxx'

置換も行える。

str2 = re.sub(pattern, replace, str)

次のようになる。

>>> re.sub('href="(.+)"', 'href="yyy"', s)
'<a href="yyy">xxx</a>'

検索にマッチしたグループをそのまま使うこともできる。

>>> re.sub('href="(.+)"', r'href="\g<1>2"', s)
'<a href="xxx2">xxx</a>'

ファイルの読み込み

f = open(filename, "r")

for line in f:
    line = line.rstrip()
    print(line)

f.close()

あるいは

with open(filename, "r") as f:
    for line in f:
        line = line.rstrip()
        print(line)

文字コードを指定するには次のようにする。

f = open(filename, "r", encoding="utf-8")

ファイルの最後の行だけ取り出し

with open(file, "r") as f:
    lines = f.readlines()
    line = lines[-1].rstrip()
    print(line)

CSV ファイルの読み込み

import csv

with open(filename, "r") as f:
    reader = csv.reader(f)
    header = next(reader)
    for line in reader:
        print(line)

テキストファイルのコピー

from pathlib import Path

basename = Path(filename).stem
ext = Path(filename).suffix
filename_new = basename + "-new" + ext

with open(filename, "r") as f1:
    with open(filename_new, "w") as f2:
        for line in f1:
            f2.write(line)

テキストの検索と置き換え

from pathlib import Path
import re

filename = "test.txt"

basename = Path(filename).stem
ext = Path(filename).suffix
filename_new = basename + "-new" + ext

with open(filename, "r") as f1:
    with open(filename_new, "w") as f2:
        for line in f1:
            if re.search("adelie penguin", line) is not None:
                line = re.sub("adelie penguin", "emperor penguin", line)
            f2.write(line)

"adelie penguin" を "emperor penguin" に置き換える。

カレントディレクトリのファイルのリストを得る

カレントディレクトリのファイルのリストを得るにはつぎのようにする。

import glob
files = glob.glob("*")

たとえば、CSV ファイルのリストが欲しい場合は、"*.csv" とする。"*" ではファイルが対象になるが、"*/" とするとディレクトリが対象になる。recursive オプションを True とすると、サブディレクトリも対象になる。"**/" とするとサブディレクトリも含めたディレクトリのリストが得られる。recursive=True かつ "**" とすると、サブディレクトリ内も含めた全ファイルのリストが得られる。

あるいは

from pathlib import path
files = list(Path().glob("*"))

"**" を指定すると、サブディレクトリも含めたディレクトリのリストが得られる。"**/*" とすると、サブディレクトリ内も含めた全ファイルのリストが得られる。

カレントディレクトリ以下のファイルの巡回

import os

for root, dirs, files in os.walk("."):
   print(root)

   for file in files:
        print(os.path.join(root, file))

あるいは

import glob

for path in glob.glob("**", recursive=True):
    print(path)

あるいは

from pathlib import Path

for path in list(Path().glob("**/*")):
    print(path)

カレントディレクトリ以下のファイルのテキストの置き換え

import os
import re

def main():
    workdir = os.getcwd()

    for root, dirs, files in os.walk("."):
      print(root)

      for file in files:
        basename, ext = os.path.splitext(file)

        if ext == ".txt":
          filename1 = os.path.join(root, file)
          filename2 = os.path.join(root, basename + ".new" + ext)

          print(filename1)

          f1 = open(filename1, "r")
          f2 = open(filename2, "w")

          for line in f1:
            if re.search("adelie penguin", line) != None:
              line = re.sub("adelie penguin", \
                      "emperor penguin", line)
            f2.write(line)

          f1.close()
          f2.close()

          os.remove(filename1)
          os.rename(filename2, filename1)


if __name__ == "__main__":
    main()

カレントディレクトリ以下の ".txt" ファイルの中にある文字列 "adelie penguin" を "emperor penguin" に置き換える。

ファイルの操作

import os
os.path.exists(path) # ファイルの存在を調べる
os.remove(path) # ファイルの削除
os.rename(src, dist) # ファイル名の変更

あるいは

from pathlib import Path
Path(path).exists() # ファイルの存在を調べる
Path(path).unlink() # ファイルの削除
Path(path).rename(new_path) # ファイル名の変更

時刻

現在時刻の取得

>>> str(datetime.datetime.now())
'2019-12-02 15:24:14.583495'
>>> "{:%Y-%m-%d %H:%M:%S}".format(datetime.datetime.now())
2019-12-02 15:28:02

UTC の変換

>>> from datetime import datetime
>>> dt = datetime.strptime("2019-07-30T00:00:00.000Z", "%Y-%m-%dT%H:%M:%S.%fZ")
>>> dt.strftime("%Y/%m/%d")
'2019/07/30'

GMT の変換

>>> from datetime import datetime
>>> dt = datetime.strptime("Tue, 09 Jun 2020 08:01:04 GMT", "%a, %d %b %Y %X %Z")
>>> print(dt)
2020-06-09 08:01:04

翌日

>>> import datetime
>>> datetime.datetime(2020, 10, 30) + datetime.timedelta(days=1)
datetime.datetime(2020, 10, 31, 0, 0)
>>> datetime.datetime(2020, 10, 31) + datetime.timedelta(days=1)
datetime.datetime(2020, 11, 1, 0, 0)

特定の日付を超えているか調べる

>>> import datetime
>>> dt0 = datetime.datetime.now()
>>> dt0
datetime.datetime(2019, 12, 14, 15, 22, 26, 404201)

>>> dt1 = datetime.datetime(2019, 12, 16)
>>> dt1 - dt0
datetime.timedelta(days=1, seconds=31053, microseconds=595799)

>>> dt1 = datetime.datetime(2019, 12, 15)
>>> dt1 - dt0
datetime.timedelta(seconds=31053, microseconds=595799)

>>> dt1 = datetime.datetime(2019, 12, 14)
>>> d = dt1 - dt0
datetime.timedelta(days=-1, seconds=31053, microseconds=595799)

>>> dt1 = datetime.datetime(2019, 12, 13)
>>> d = dt1 - dt0
datetime.timedelta(days=-2, seconds=31053, microseconds=595799)

ある日付の 0:00 を超えているか調べたければ、時刻どうしを引いて days の符号が負かどうかを調べればよい。

ファイルの日付チェック

ファイルを変換するかどうかをタイムスタンプを比較して判断したりする場合。

from pathlib import Path

files = ["fig01", "fig02", "fig03"]

for file in files:
    p_pdf = Path("%s.pdf" % file)
    p_png = Path("%s.png" % file)

    if p_png.exists():
        dt0 = datetime.fromtimestamp(p_pdf.stat().st_ctime)
        dt1 = datetime.fromtimestamp(p_png.stat().st_ctime)
        if(dt1 > dt0):
            continue

    ... 変換処理 ...

時刻表示を足す

"00:00:00" の形の時刻表示どうしを足し合わせる。

>>> import datetime
>>> dt1 = datetime.datetime.strptime("01:45:00", "%H:%M:%S")
>>> dt2 = datetime.datetime.strptime("0:20:00", "%H:%M:%S")
>>> dt = dt1 + datetime.timedelta(hours=dt2.hour, minutes=dt2.minute)
>>> dt.strftime("%H:%M:%S")
'02:05:00'

CSV から HTML のテーブルを作る

csv2table.py

import sys
import csv

def main():
    argv = sys.argv
    argc = len(argv)

    if argc < 2:
        print("usage: csv2table.py <CSV file>")
        exit()

    filename = argv[1]

    with open(filename, "r") as f:
        reader = csv.reader(f)
        header = next(reader)

        print("<html>")
        print("<body>")

        print("<table border="1">")
        print("<thead>")

        print("<tr>", end="")

        for r in header:
            r = r.strip()
            print("<th>%s</th>" % r, end="")

        print("</tr>")

        print("</thead>")
        print("<tbody>")

        for line in reader:
            print("<tr>", end="")

            for r in line:
                r = r.strip()
                print("<td>%s</td>" % r, end="")

            print("</tr>")

        print("</tbody>")
        print("</table>")

        print("</body>")
        print("</html>")


if __name__ == "__main__":
    main()

画像処理

画像の読み込みと表示

from PIL import Image
image = Image.open("image.jpg")
image.show()

image.show() はデフォルトのビューアーで表示される。Jupyter Notebook 内で表示するには、変数名だけ打てばよい。

image

あるいは、matplotlib を利用することもできる。

%matplotlib inline
from matplotlib import pyplot as plt
import numpy as np

fig, ax = plt.subplots()
ax.axis("off")
plt.imshow(np.array(image))

画像のサイズ

w, h = image.size

座標の色を取得

image.getpixel((0, 0))

2値化

image2 = image.convert('1')

グレースケール変換

image2 = image.convert('L')

画像のクリップ

image2 = image.crop((x1, y1, x2, y2))

画像の作成

image = Image.new("RGB", (w, h), (255, 255, 255))

画像の貼り付け

image.paste(image1, (x, y))

画像の保存

image.save("image.png")

画像の結合

from PIL import Image

w = 640
h = 320

image1 = Image.open("image1.jpg")
image2 = Image.open("image2.jpg")

image = Image.new("RGB", (w*2, h), (255, 255, 255))
image.paste(image1, (0, 0))
image.paste(image2, (w, 0))

image.save("image.jpg")1

画像への文字の書き込み

from PIL import Image, ImageDraw, ImageFont

w = 200
h = 50

image = Image.new("RGB", (w, h), (255, 255, 255))

draw = ImageDraw.Draw(image)
draw.font = ImageFont.truetype("Helvetica", 40)

text = "Penguin"
tw, th = draw.textsize(text)
draw.text(((w-tw)/2, (h-th)/2), text, (0, 0, 0))
image.show()

画像サイズの変更

w, h = image.size
size = (int(w*0.25), int(h*0.25))
image.resize(size, Image.ANTIALIAS)

画像のアスペクト比を維持したままの縮小の場合は、次のようにもできる。

w, h = image.size
size = (w*0.25, h*0.25)
image.thumbnail(size, Image.ANTIALIAS)

バックトレースの表示

import traceback
traceback.print_exc()

配列を文字列として連結

>>> ",".join(["a", "b"])
'a,b'

文字列処理

文字列の正規化

import unicodedata
s = unicodedata.normalize("NFKC", s)

アルファベット文字列かどうかチェック

def check_alphabet(s):
    alphabet = {chr(i) for i in range(ord("A"), ord("z")+1)}
    ss = set(s)

    if ss & alphabet == ss:
        return True
    else:
        return False

ひらがな文字列かどうかチェック

def check_hiragana(s):
    hiragana = {chr(i) for i in range(ord("ぁ"), ord("ん")+1)}
    ss = set(s)
    ss -= {"ー"}

    if ss & hiragana == ss:
        return True
    else:
        return False

ひらがなをカタカナに変換する

def convert_hira_to_kata(s):
    r = ""
    for c in s:
        i = ord("ァ") + ord(c) - ord("ぁ")
        if ord("ァ") <= i <= ord("ン"):
            r += chr(i)
        else:
            r += c
    return r

gnuplot を呼ぶ

import subprocess

with open(plot_file, "w") as f:
    # gnuplot のコマンドを書く
    ...

command = ["gnuplot", "-persist"]
proc = subprocess.Popen(command, stdin=subprocess.PIPE)

command = 'load "%s"\n' % plot_file
proc.stdin.write(command.encode("utf-8"))
proc.stdin.flush()

Ctrl-C を捕捉する

try:
    ...
except KeyboardInterrupt:
    ...

環境変数

環境変数を参照する

>>> import os
>>> os.environ["MYENV"]

環境変数があるかどうか調べる。

>>> "MYENV" in os.environ

ソケット

server.py

import socket

host = socket.gethostname()
port = 4321
buffer_size = 1024

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    try:
        s.bind((host, port))
    except OSError as e:
        print_log(e)
        exit()

    s.listen(1)
    print("waiting...")

    try:
        while True:
            (c, address) = s.accept()
            with c:
                print("connect from %s:%s" % address)

                while True:
                    msg = c.recv(buffer_size)

                    if not msg:
                        break

                    msg = msg.decode()
                    print("message: %s" % msg)

                    if msg == "end":
                        break

    except KeyboardInterrupt:
        print("exit")

server.py (Windows 用)

import socket

host = socket.gethostname()
port = 4321
buffer_size = 1024

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    try:
        s.bind((host, port))
    except OSError as e:
        print_log(e)
        exit()

    s.listen(1)
    print("waiting...")

    s.settimeout(1)

    try:
        while True:
            try:
                (c, address) = s.accept()
            except IOError:
                continue

            with c:
                print("connect from %s:%s" % address)

                while True:
                    msg = c.recv(buffer_size)

                    if not msg:
                        break

                    msg = msg.decode()
                    print("message: %s" % msg)

                    if msg == "end":
                        break

    except KeyboardInterrupt:
        print("exit")

client.py

import socket

host = socket.gethostname()
port = 4321
buffer_size = 1024

try:
    while True:
        msg = input("message: ")

        with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
            s.connect((host, port))
            s.send(msg.encode())

        if msg == "end":
            break

except KeyboardInterrupt:
    print("exit")

ホスト名の取得

host = socket.gethostname()

IP アドレスの取得

address = socket.gethostbyname(host)

ソースコードを調べる

モジュールのソースコードを見ることができる。

import mymod
import inspect
print(inspect.getsource(mymod))

ただし、Cython を使うと見えなくなる。

UUID を作成する

import uuid
id = str(uuid.uuid4())

MAC アドレスを取得する

import uuid
mac = uuid.uuid1().hex[-12:]

ZIP ファイルを展開する

ファイル名が Shif-JIS のファイルを含む ZIP ファイルの展開 (パスワード対応)。

unzip.py

import sys
from zipfile import ZipFile
from getpass import getpass


def main(args):
    if len(args) <= 1:
        print("usage: unzip.py ")
        exit()

    filename = args[1]
    password = None

    with ZipFile(filename, "r") as f:
        for info in f.infolist():
            if info.flag_bits & 0x1:
                password = getpass().encode()
                break
        for info in f.infolist():
            info.filename = info.filename.encode("cp437").decode("cp932")
            print(info.filename)
            if password is not None:
                f.extract(info, path=".", pwd=password)
            else:
                f.extract(info, path=".")


if __name__ == "__main__":
    main(sys.argv)

使い方

$ python unzip.py filename

サイクルリスト

class CycleList:
    def __init__(self, lst, nitems=1):
        self.list = lst
        self.nitems = nitems

    def iterator(self, n=-1):
        i = 0
        while True:
            items = []
            for j in range(i, i + self.nitems):
                k = j % len(self.list)
                items.append(self.list[k])

            if len(items) == 1:
                yield items[0]
            else:
                yield tuple(items)

            i += 1
            if i == n:
                break

使い方

c = CycleList([0, 1, 2], 2)
for i in c.iterator(3):
    print(i)

出力

(0, 1)
(1, 2)
(2, 0)

ソートしたリストを得る

sorted_list = sorted(unsorted_list)

大文字小文字関係なしで並び替える。

sorted_list = sorted(unsorted_list, key=str.casefold)

"unsorted_list.sort()" という方法もあるが、これだとリストそのものの内容が変更されるので注意。

コマンドを呼ぶ

import sys
import subprocess as sp
import threading

def run_command(command, silent=False, func_log=None, background=False):         
    proc = sp.Popen(command,                                                     
            shell=True, stdout=sp.PIPE, stderr=sp.PIPE)                          
                                                                                 
    def wait():                                                                  
        stdout_data, stderr_data = proc.communicate()                            
                                                                                 
        if not silent:                                                           
            if func_log is None:                                                 
                sys.stdout.write(stdout_data.decode())                           
            else:                                                                
                func_log(stdout_data.decode())                                   
                                                                                 
        if proc.wait() != 0:                                                     
            if func_log is None:                                                 
                sys.stderr.write(stderr_data.decode())                           
            else:                                                                
                func_log(stderr_data.decode())                                   
            raise OSError("Command execution faied: %s" % command)               
                                                                                 
    if background:                                                               
        threading.Thread(target=wait).start()                                    
    else:                                                                        
        wait()

func_log に関数が設定されていたら、その関数を用いて出力を行う。

コマンドの存在の有無

import shutil as su
su.which("ls")