コンテンツにスキップ

Python

tool

JSON整形

$ ... | python -m json.tool

httpサーバ

$ python3 -m http.server 8080
$ python2 -m SimpleHTTPServer 8080

インフラ系エンジニアが唯一覚えるべきPythonの使い方 - Qiita

実装

演算子

比較(==)

Pythonは文字列も==を使う。
以下の比較処理は真。

string = "foobar"

if string == "foobar":
    print("string is 'foobar'")

配列の比較も==で行う。以下はtrue

obj_list = [1, 2, 3, "abc"]

if obj_list == [1, 2, 3, "abc"]:
    print("true")

というか==はオブジェクトの比較に使えるってことかな。

制御構文

for

foreach式のループ処理は以下。

for item in items:
    # itemにリストの要素がセットされる

ワンライナーでも書ける。
ループ処理した結果をリストにセットしたい場合など。
その際は[...]で囲む。

result = [ ..itemに対する処理.. for item in items]

BeautifulSoupを使ったスクレイピングの記述例

links = [li.find('a').get('href') for li in s.find_all('li', class_='toctree-l2')]
print(links)

break

ループを途中で抜けるにはbreak

continue

次のループへ飛ぶにはcontinue

変数

環境変数の参照

import os

aws_access_key = os.environ.get("AWS_ACCESS_KEY_ID")
if aws_access_key is None:
    print("AWS_ACCESS_KEY_ID not defined")

型の確認

type(value)

None判定

if data is not None:
    # dataがNoneでない場合

引数

コマンドライン引数

sys.argvを使う

import sys

args = sys.argv
print(args)
print(args[0])
print(args[1])
$ ./argv.py hello
['./argv.py', 'hello']
./argv.py
hello

関数

main

#!/usr/bin/python3

def func():
    print("begin func()")

if __name__ == "__main__":
    print("begin main()")
    func()

assert

特にimportは不要で使用可能

# aの値が10であること
assert(a == 10)

リスト処理

リストの定義

items = [
    'foo',
    'bar',
    'baz',
]

空リスト

items = []

要素の追加

リストオブジェクトのappend()メソッドを使う。

items.append('qux')

リストの結合

複数のリストを結合(1つのリストに全要素をまとめる)するには+を使う

list1 = ['foo', 'bar', 'baz']
list2 = [1, 2, 3]

print(list1 + list2)

実行結果は['foo', 'bar', 'baz', 1, 2, 3]になる。
+= で代入も可能。

length

array = []
array.append('2')
array.append('3')
array.append('4')

print(len(array))

3になる。

要素のindex

array = ['curry', 'beef', 'chicken', 'vegetable']

beef_index = array.index('chicken')
print(beef_index)

引数に指定した要素が配列中の何番目にあるかを返す。上記の出力は2
存在しない場合はValueErrorになる。

array = ['curry', 'beef', 'chicken', 'vegetable']

beef_index = array.index('zzz')
print(beef_index)
$ python3 index.py
Traceback (most recent call last):
  File "index.py", line 3, in <module>
    beef_index = array.index('zzz')
ValueError: 'zzz' is not in list

要素の有無

inを使う。

array = ['curry', 'beef', 'chicken', 'vegetable']

print('curry' in array)
print('pork' in array)

出力は以下になる。

True
False

スライス

array = ['curry', 'beef', 'chicken', 'vegetable']

print(array[2:])
print(array[:3])
print(array[1:2])

実行結果は以下

$ python3 slice.py
['chicken', 'vegetable']
['curry', 'beef', 'chicken']
['beef']

書式はarray[始点:終点]。省略時は最初からor最後まで。
始点はindexを「含む」が、終点はindexを「含まない」ので注意。
(要素のindexというより、区切り位置のindexと見なすと良い)

連番の定義

range(開始, 終了)を使う

r = range(0, 9)

これでr[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]となる。
正確にはrange型のためリストではないが、list(r)を使うとリストになる。

1引数の場合は開始0とみなされる。(0開始の場合は終了の値のみでOK。↑はrange(9)と同値)

辞書

辞書の定義

dict1 = {"key1": "value1", "key2": "value2"}
dict2 = dict(key1="value1", key2="value2")

この場合、dict1dict2の中身は同じ。
dict1 == dict2の結果もtrueになる。

辞書要素のループ(キーと値の組み合わせを取得)

sample_dict = {
    "key1": "value1",
    "key2": "value2",
    "key3": "value3",
}

for k, v in sample_dict.items():
    print("key: " + k + ", value: " + v)

辞書.items()で、キーと値のペアのリストを得られるので、これでループできる。
上のコードの実行結果は以下の通り。

key: key1, value: value1
key: key2, value: value2
key: key3, value: value3

辞書->YAML化

import yaml

print(yaml.dump(dict_data, default_flow_style=False))

子要素がフロースタイルになってブロックスタイルにならない場合はdefault_flow_styleのオプションをFalse指定する。

YAMLファイル->辞書化

import yaml

with open('config.yaml', 'r') as yml:
    config = yaml.safe_load(yml)

これでconfig変数にYAMLファイルに定義した情報が辞書型オブジェクトとしてセットされる。

辞書->JSON化

import json

print(json.dumps(dict_data))

JSON文字列->辞書(オブジェクト)

import json

obj = json.loads(json_strings)

YAMLと違ってメソッド名はdumps()sが付与される。
dump()は、ファイル出力を行うメソッドとして別に存在する。

JSONファイル->オブジェクト

STDINの場合は

import json

with sys.stdin as f:
    obj = json.load(f)

文字列

指定文字で始まるか

if string_value.startswith("kube"):
    # 文字列が"kube"で始まる場合

末尾1文字

sample_str[-1]

末尾1文字を削った文字列

sample_str[:-1]

splitlines()

テキストの末尾に改行がある場合、split('\n')を使うと末尾の改行もデリミタと認識し、リストの最終要素は空文字になるが、splitlines()であれば末尾の改行分は無視される。行ごとに処理したい場合はこちらが便利。

ゼロパディング

文字列のzfill()メソッドを使う。
引数の数値の桁数長にゼロパディングされた文字列を返す。

int_sample = '123'
str_sample = 'foo'
print(int_sample.zfill(8))
print(str_sample.zfill(8))

実行結果は以下。

00000123
00000foo

正規表現処理

re.search()で文字列判定
import re

if re.search(regexp, string):
    # stringがregexpにマッチ

re.match()は先頭からマッチを行うため、文字列の途中にはマッチしない。

re.sub()で置換
import re

string = '/path/to/sample.txt'

# s/a/A/
string = re.sub(r'a', 'A', string)
print(string)

# 末尾の.txtを削る
string = re.sub(r'\.txt$', '', string)
print(string)

# 't'と'p'を(t)か(p)に変換
string = re.sub(r'([tp])', r'(\1)', string)
print(string)
# r'...'を使わない場合は'\\1'と書く

# aを変換。ただし大文字小文字無視
string = re.sub(r'a', r'_', string, flags=re.IGNORECASE)
print(string)
# re.sub(pattern, repl, string, count=0, flags=0) なので、countを省略するためには"flags="という名前付き引数を使う

上記コードの実行結果は以下の通り。

$ python replace.py 
/pAth/to/sAmple.txt
/pAth/to/sAmple
/(p)A(t)h/(t)o/sAm(p)le
/(p)_(t)h/(t)o/s_m(p)le

出力

標準エラー出力

import sys

print("error", file=sys.stderr)

printで改行無し

print("sample", end="")

ただし行ごとにフラッシュされるため、プログレス表示的に使う場合は追加オプションが必要

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

ヒアドキュメント

print("""
東京・お台場にある、自由な校風と専攻の多様さで人気の高校「虹ヶ咲学園」。
スクールアイドルの魅力にときめいた普通科2年の高咲侑は、
幼馴染の上原歩夢とともに「スクールアイドル同好会」の門を叩く。
""")

この場合、「開始の"""」のあとの改行と、「終了の"""」の手前の改行も含まれる。

byte型

変数出力するとb'....'と表記され出力される。

byte_data = b'zzzzzzzz'

print(byte_data)
print(len(byte_data))
print(type(byte_data))

出力は以下。

b'zzzzzzzz'
8
<class 'bytes'>

byte -> string変換

string_data = byte_data.decode()

print(string_data)
print(type(string_data))

出力は<class 'str'>

string -> byte変換

byte_data = string_data.encode()
print(byte_data)
print(type(byte_data))

これで出力は<class 'bytes'>

path

ユーザーディレクトリを展開

os.path.expanduserを使う。

[zaki@cloud-dev2 ~]$ grep -e ^zaki -e ^root /etc/passwd
root:x:0:0:root:/root:/bin/bash
zaki:x:1000:1000:zaki:/home/zaki:/bin/bash

というユーザーが存在する場合、実行結果は以下の通り。

[zaki@cloud-dev2 ~]$ whoami 
zaki
[zaki@cloud-dev2 ~]$ python
Python 3.10.2 (main, Jan 17 2022, 00:00:00) [GCC 11.2.1 20211203 (Red Hat 11.2.1-7)] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import os.path
>>> print(os.path.expanduser('~zaki'))
/home/zaki
>>> print(os.path.expanduser('~root'))
/root
>>> print(os.path.expanduser('~'))
/home/zaki
>>> 

外部コマンド実行

subprocess.run()を使う。(os.systemはもう使わない)

import subprocess

ret = subprocess.run(["ls", "-l"], capture_output=True, text=True)
print(ret.stdout)

リストでなく文字列を指定したい場合はshell=Trueを付与

subprocess.run("ls -l", shell=True)

通信

HTTP

Requestsを使うと楽。

import requests
r = requests.get("http://www.example.org")
POST

辞書型データをJSON形式のリクエストヘッダにするならjson.dumpsを使うと楽。

payload = json.dumps(dict_params)
headers = {
    'Content-Type': 'application/json',
    'Accept': 'application/json',
    'Authorization': 'Bearer ' + token
}
requests.post(url, data=payload, headers=headers)
DELETE

POSTと同じ要領

requests.delete(url)
リクエストヘッダ
headers = {
    'Accept': 'application/json',
    'Authorization': 'Bearer ********'
}

requests.get(url, headers=headers)
レスポンス
print(r.status_code)  # ステータスコード(200など)

print(r.request)      # Requestオブジェクト
print(r.request.url)  # アクセスしたURL

print(r.text)   # レスポンスbody

REST APIのようにレスポンスがJSON形式のテキストの場合は、r.json()を使うとオブジェクトとして値をとれる。

SSL検証無視
r = requests.get("https://www.example.org", verify=False)
Basic認証
requests.get(url, auth=HTTPBasicAuth(username, password))
接続タイムアウト設定
timeout_sec = 3
requests.get(url, timeout=timeout_sec)
接続エラーのハンドリング

例外が発生するのでそれをcatchする。

try:
    requests.post(url)
except (requests.ConnectionError, requests.ConnectTimeout) as e:
    print(e)

isinstance()で型チェック

bool_val = True
if (isinstance(bool_val, bool)):
    print("bool_val is bool")

int_val = 123
if (isinstance(int_val, int)):
    print("int_val is int")

list_val = [1, 2, 3]
if (isinstance(list_val, list)):
    print("list_val is list")

dict_val = {"key1": "value1", "key2": "value2"}
if (isinstance(dict_val, dict)):
    print("dict_val is dict")

実行結果は以下

bool_val is bool
int_val is int
list_val is list
dict_val is dict

例外

例外の捕捉

try,exceptを使う

try:
    # 例外が発生するコード
except RuntimeError as e:
    # エラーハンドリング処理
    # エラー情報は"e"に入っている
    print(e)

捕捉する例外が複数の場合は、exceptを複数並べるか、カッコでまとめる。

try:
    # 例外が発生するコード
except (RuntimeError, TypeError) as e:
    print(e)

例外を投げる(raise)

raise RuntimeError('application error: ' + error_value)

標準で使用できる組み込み例外は以下。
組み込み例外

部分適用(partial application)

こんな感じ。
https://github.com/ansible/ansible/blob/stable-2.10/lib/ansible/playbook/base.py#L105-L114

                    method = "_get_attr_%s" % attr_name
                    if method in src_dict or method in dst_dict:
                        getter = partial(_generic_g_method, attr_name)
                    elif ('_get_parent_attribute' in dst_dict or '_get_parent_attribute' in src_dict) and value.inherit:
                        getter = partial(_generic_g_parent, attr_name)
                    else:
                        getter = partial(_generic_g, attr_name)

                    setter = partial(_generic_s, attr_name)
                    deleter = partial(_generic_d, attr_name)

なるほどわからん

[Ansible] そのtag設定、想定通りに動いてますか? (継承機能とその実装を確認する) - zaki work log

メタクラス

クラスの定義をコードで動的に実装する感じ。

https://github.com/ansible/ansible/blob/stable-2.10/lib/ansible/playbook/base.py#L105-L114

                    method = "_get_attr_%s" % attr_name
                    if method in src_dict or method in dst_dict:
                        getter = partial(_generic_g_method, attr_name)
                    elif ('_get_parent_attribute' in dst_dict or '_get_parent_attribute' in src_dict) and value.inherit:
                        getter = partial(_generic_g_parent, attr_name)
                    else:
                        getter = partial(_generic_g, attr_name)

                    setter = partial(_generic_s, attr_name)
                    deleter = partial(_generic_d, attr_name)

環境

pip

バージョン指定

pip install ansible==9

「以上」の場合は以下

pip install ansible>=8

requirements.txtの利用

ansible==2.9.7
jmespath==0.10.0

という内容のrequirements.txtファイルを作成し、これで

$ pip install -r requirements.txt

※ ファイル名は任意

pipのアップデート

venvとか作った直後はpip自体のバージョンが古いので(目的にパッケージ入れるときにエラーになる前に先に)アップデートする
※ そもそもvenv作成時に --upgrade-deps をつけておけば自動更新されて環境が作成される。

$ pip install --upgrade pip

パッケージのダウングレード

基本的には古いバージョンを指定して以下でOK

$ pip install hogehoge=0.0.1

requirements.txt記載の場合も、記載した指定バージョンがインストール済みのものより古ければ自動で更新される。
以下はrich==11.0.0がインストール済みの状態でrich==9.5.1をインストールした場合のログ。

Installing collected packages: typing-extensions, rich
  Attempting uninstall: rich
    Found existing installation: rich 11.0.0
    Uninstalling rich-11.0.0:
      Successfully uninstalled rich-11.0.0
Successfully installed rich-9.5.1 typing-extensions-3.10.0.2

パッケージアンインストール

$ pip uninstall docker
Found existing installation: docker 5.0.3
Uninstalling docker-5.0.3:
  Would remove:
    /home/ubuntu/.local/lib/python3.8/site-packages/docker-5.0.3.dist-info/*
    /home/ubuntu/.local/lib/python3.8/site-packages/docker/*
Proceed (y/n)? y
  Successfully uninstalled docker-5.0.3

-yを付ければ強制削除になるっぽい。

Uninstall Options:
  -r, --requirement <file>    Uninstall all the packages listed in the given requirements file.
                              This option can be used multiple times.
  -y, --yes                   Don't ask for confirmation of uninstall deletions.

全てアンインストール

$ pip uninstall -r <(pip freeze)

依存関係の確認

$ pip show ansible-core
:
:
Requires: cryptography, jinja2, packaging, PyYAML, resolvelib
Required-by: ansible

Required-byのansibleによってインストールされ、このパッケージによってcryptography, jinja2, packaging, PyYAML, resolvelibがインストールされる。

pyenv

pyenv/pyenv: Simple Python version management

インストール

curl https://pyenv.run | bash

設定追加

cat <<'__EOL__' >> $HOME/.bashrc
export PYENV_ROOT="$HOME/.pyenv"
command -v pyenv >/dev/null || export PATH="$PYENV_ROOT/bin:$PATH"
eval "$(pyenv init -)"
__EOL__
. $HOME/.bashrc

依存パッケージのインストール

以下はUbuntuの場合。

sudo apt update; sudo apt install build-essential libssl-dev zlib1g-dev \
libbz2-dev libreadline-dev libsqlite3-dev curl \
libncursesw5-dev xz-utils tk-dev libxml2-dev libxmlsec1-dev libffi-dev liblzma-dev

その他の環境はwiki参照: https://github.com/pyenv/pyenv/wiki#suggested-build-environment

Pythonインストール

pyenv install 3.11.3

インストール可能なPythonはpyenv install -lで確認

pythonの切替

pyenv global 3.11.3

切替可能なPythonはpyenv versionsで確認

venv

作成

$ python3 -m venv venv/ansible

作成時にpipとsetuptoolsを更新する

$ python3 -m venv ~/venv/dev --upgrade-deps

systemのパッケージパスも参照

$ python3 -m venv venv-sys --system-site-packages

pyvenv

RHEL8に入ってるやつ。

[zaki@rhel8-node ~]$ pyvenv-3 venv3
WARNING: the pyenv script is deprecated in favour of `platform-python -m venv`
[zaki@rhel8-node ~]$ . venv3/bin/activate
(venv3) [zaki@rhel8-node ~]$ 
(venv3) [zaki@rhel8-node ~]$ pip install --upgrade pip
Cache entry deserialization failed, entry ignored
Collecting pip
  Downloading https://files.pythonhosted.org/packages/47/ca/f0d790b6e18b3a6f3bd5e80c2ee4edbb5807286c21cdd0862ca933f751dd/pip-21.1.3-py3-none-any.whl (1.5MB)
    100% |████████████████████████████████| 1.6MB 641kB/s 
Installing collected packages: pip
  Found existing installation: pip 9.0.3
    Uninstalling pip-9.0.3:
      Successfully uninstalled pip-9.0.3
Successfully installed pip-21.1.3
(venv3) [zaki@rhel8-node ~]$ pip list
Package    Version
---------- -------
pip        21.1.3
setuptools 39.2.0

--system-site-packagesも併用可

[zaki@rhel8-node ~]$ pyvenv-3.6 venv --system-site-packages
WARNING: the pyenv script is deprecated in favour of `platform-python -m venv`
[zaki@rhel8-ansible-node ~]$ . venv/bin/activate
(venv) [zaki@rhel8-node ~]$ pip list
DEPRECATION: The default format will switch to columns in the future. You can use --format=(legacy|columns) (or define a format=(legacy|columns) in your pip.conf under the [list] section) to disable this warning.
cffi (1.11.5)
chardet (3.0.4)
configobj (5.0.6)
configshell-fb (1.1.28)
cryptography (3.2.1)
dbus-python (1.2.4)
decorator (4.2.1)
ethtool (0.14)
:
:

RHEL / Ansible

Ansible が利用する Python 実行環境 - 赤帽エンジニアブログ