作成者別アーカイブ: python-tksssk

HTTPのPOST/GETリクエストをする

requestsモジュールを使う(Python2系, 3系共通)

pythonの標準ライブラリでもHTTPリクエストを送ることはできるが、煩雑で使いにくいためrequestsというサードパーティ製のライブラリが使われることが多い。
requestsモジュールを使った場合は直感的で簡単なコードでHTTPリクエストができる。

requestsモジュールのインストール。

$ pip install requests

GETリクエスト(requests版)

getメソッドでURLを指定してリクエスト。

import requests

response = requests.get('http://www.example.com')
print(response.status_code)    # HTTPのステータスコード取得
print(response.text)    # レスポンスのHTMLを文字列で取得

POSTリクエスト(requests版)

POSTの場合はpostメソッドでリクエストしてdata属性にdictを渡す。

import requests

response = requests.post('http://www.example.com', data={'foo': 'bar'})
print(response.status_code)    # HTTPのステータスコード取得
print(response.text)    # レスポンスのHTMLを文字列で取得

python3系の標準ライブラリ urllibを使う

python3とpython2では標準ライブラリのパッケージが異なるので注意(使い方はほぼ同じ)

requestsモジュールと違って、送信データ受信データをバイナリで送って、バイナリから読み込むという操作が必要になる。

GETリクエスト(python3 urllib版)

得られるレスポンスはbyte文字列なので、適切なエンコーディングでデコードする必要がある。(汎用的につくるならレスポンスヘッダを確認するなどする)

import urllib.request

response = urllib.request.urlopen('http://www.example.com')
print(response.getcode())
html = response.read()
print(html.decode('utf-8'))

POSTリクエスト(python3 urllib版)

POSTリクエストする場合はRequestオブジェクトを作ってdataを渡す必要がある。
dataはURLエンコードをしなければならないし、送信データ文字コードを指定してバイト文字列を作る必要がある。

import urllib.request
import urllib.parse

data = urllib.parse.urlencode({'foo': 'bar'}).encode('utf-8')
request = urllib.request.Request('http://www.example.com', data)
response = urllib.request.urlopen(request)
print(response.getcode())
html = response.read()
print(html.decode('utf-8'))

python2系の標準ライブラリ urllib2を使う

python2の場合はpython3のurllibとほぼ同じだが、パッケージ構成が異なるので注意。
python2の時はurllibが複数あったが、python3で整理されている。

python2では文字列リテラルはバイト文字列になるため、エンコードが不要なようにみえるがユニコード文字列を扱いたい場合にはやはりエンコード・デコードが必要にある。

GETリクエスト(python2 urllib2版)

import urllib2
response = urllib2.urlopen('http://www.example.com')
print(response.getcode())
html = response.read()
print(html)

POSTリクエスト(python2 urllib2版)

import urllib
import urllib2

data = urllib.urlencode({'foo': 'bar'})
request = urllib2.Request('http://www.example.com', data)
response = urllib2.urlopen(request)
print(response.getcode())
html = response.read()
print(html)

python3.5の型ヒントでジェネリクス型を定義

型ヒントによる構文チェックの基本については、こちらを参照。Python3.5以降のみ使用可能。

python3.5以降の型ヒント(typeing)の使用 – 基本

ジェネリクス(コンテナ内の型明示)

Java言語のようにジェネリクスを使ってコンテナ内のオブジェクトの型情報を明示することができる。

from typing import List

a:List[str] = []   # 文字列を格納するlistであることを明示
a.append("hello")
a.append(1)        #! Argument 1 to "append" of "list" has incompatible type "int"; expected "str"

型変数

TypeVarを使って型変数を定義できる。

intのListを渡すと返り値はintになるので型チェックできる。

from typing import TypeVar, Sequence
T = TypeVar('T')
def firstElement(l: Sequence[T]) -> T:   # List[T]の最初の要素を返すので戻り値はT型
    return l[0]
firstElement([1, 2, 3])+" " # Unsupported operand types for + ("int" and "str")

クラス定義時のジェネリッククラス

型変数をクラス定義に設定し、インスタンス生成時に型定義を明示する。

from typing import TypeVar, Generic

T = TypeVar('T')
class MyContainer(Generic[T]):   # MyContainerはT型のジェネリックスクラスとする
    def set(self, v:T):
        self.value:T = v
    def get(self) -> T:
        return self.value

container = MyContainer[int]()   # int型のMyContainerを生成
container.set(1)
container.get() + 3

container.set("Hello")      # ! Argument 1 to "set" of "MyContainer" has incompatible type "str"; expected "int" 
container.get().index("l")  # ! "int" has no attribute "index"

参考:26.1.4. ジェネリクス

python3.5以降の型ヒント(typeing)の使用 – 基本

python3.5以降から使用可能な型ヒントを使う。これを使えばpythonでも静的言語なるように見えるが、実行時には完全に無視されチェックはなにも行われない。
現状では型ヒントのチェックツールでの構文チェックや、PyCharmなどの開発環境でのコード補完のためのヒントとなる点に注意する。

チェックツールmypyのインストール

これを使わないと型ヒントの情報はなにも活かせない。

$ pip install mypy
$ mypy -V
mypy 0.511

型ヒントなしのコード

型ヒントなしのコードとして、こんな関数があったとする。

def concat(a, b):
    return a + b

a,bの双方に文字列を渡すケースを想定しているが、数値を渡すと(意図した操作ではないが)加算した結果が得られる。文字列と数値を渡すと実行時エラーになる。

concat("Hello", "World")    # "HelloWorld"
concat(1, 2)                # 3
concat("s", 2)              # TypeError: must be str not int

型ヒントの付加

意図してない操作を除外したい。実行する前に静的に構文エラーとしたい。そのために型ヒントをつける。このように記述できるのはpython3.5以降。

def concat(s:str, n:str)->str:
    return s + n

引数の型。関数の戻り値の型が情報として付加された。

mypyでの静的チェック

これをmypyでチェックすることで、プログラムの実行前に関数の使用法のエラーとして検出可能になる。

$ mypy type-hint.py
type-hint.py:8: error: Argument 1 to "concat" has incompatible type "int"; expected "str"
type-hint.py:8: error: Argument 2 to "concat" has incompatible type "int"; expected "str"
type-hint.py:9: error: Argument 2 to "concat" has incompatible type "int"; expected "str"

PyCharmなどのIDEでは型ヒントの情報から、あらかじめ警告表示するサポートがある。

また戻り値が文字列であることが分かるため、関数の実行結果に対しての操作が、文字列に対する操作になるように補完される。

その他にもジェネリック型など他の静的言語と比べても遜色ない型ヒント情報が指定できる。

参考:26.1. typing — 型ヒントのサポート

pythonでのflatmap(flattenする)

他の言語で言うところのflatMapだとかflattenする方法。

複数のイテレータなどから連続した要素を得る場合の方法

itertools.chainで複数のiterableなオブジェクトを連結

import itertools

r1 = range(0, 3)
r2 = range(3, 6)

stream = (r1, r2)
print(stream)


flat = itertools.chain(r1, r2)
for n in flat:
    print(n)

実行結果

(range(0, 3), range(3, 6))
0
1
2
3
4
5

chainの場合は複数のiterableなオブジェクトを列挙して指定するが、すでにそれがiterableなコンテナに入っている場合は、
itertools.chain.from_iterableが使用可能。

stream = (r1, r2)
for n in itertools.chain.from_iterable(stream)
    print(n)

参考

10.1. itertools — 効率的なループ実行のためのイテレータ生成関数

pythonで並列処理 (thread)

threadを使って簡易的に並列処理を実装することができる。

threading.Threadクラスの生成時にtarget引数に指定する関数を、別スレッドで並列的に実行できる。

※並列して処理が動作するため、複数のスレッドでデータを共有する場合にはロックなどを使用してスレッドセーフにする必要がある。

# 1秒おきと2秒おきにprintする処理をthreadで実行
import threading
import time

def worker(interval):
    for n in range(3):
        time.sleep(interval)
        print("%s --> %d" % (threading.current_thread().name, n))

th1 = threading.Thread(name="a", target=worker, args=(1,))
th2 = threading.Thread(name="b", target=worker, args=(2,))

th1.start()
th2.start()

参考:17.1. threading — スレッドベースの並列処理

※threadで実行しても、完全に同時に2つの処理が動作するわけではなく、CPython実装ではマルチコアを活かした並行処理はmultiprocessingを使わないとできない。

可変長引数の設定と呼び出し

通常の引数を可変長引数に

pythonで可変長引数を指定するには、仮引数に*を指定する。関数の実装側では、仮引数をタプルとして扱える。

def func1(a, *others):
    print(a, others)

func1(1)        # 1, ()
func1(1, 2)     # 1, (2,)
func1(1, 2, 3)  # 1, (2, 3)

逆の使い方として、タプルやリストなどのシーケンス型に*をつけると実引数に展開して呼び出すことができる。

以下の例ではa, b, cの引数をそれぞれ指定するのではなく、リストの要素を展開してa,b,cに割り当てている。要素数が引数の数と合わない場合はエラーになる(多くても少なくても)。

def func2(a, b, c):
    print("a={}, b={}, c={}".format(a, b, c))

params = [1, 2, 3])
func2(*params)    # a=1, b=2, c=3

キーワード引数を可変長引数に

pythonでは引数を名前付きで指定することもできる。任意のキーワードで引数を受け付けるには、**をつける。

この場合はdictで扱える。

def func3(a, **others):
    print(a, others)

func3(1, b=2, c=3)   # 1 {'b':2, 'c': 3}

通常の引数と同じように実引数のほうに **をつけることで、dict型をキーワード引数に展開できる。

def func2(a, b, c):
    print("a={}, b={}, c={}".format(a, b, c))

params = {'a':1, 'b':2, 'c':3}
func2(**params)    # a=1, b=2, c=3

任意の引数を受け取れる関数

以下のように リスト形式の可変長引数*argsと、 dict形式の引数**kwargs を受け取るようにすると、どのような引数でも受け付けられるようになる。

def func(*args, **kwargs):
    print(args, kwargs)

文字列からbool値に変換する

文字列からbool値に変換するのは、一見bool()関数でできそうですが、これは真偽値判断をしてくれる関数であって、空文字列がFalse。それ以外がTrueになってしまいます。

単純に文字列→boolとなる関数は標準ライブラリには用意されていないようです。

実際に変換したいのはいろいろな文字列→bool値の仕様があると思いますが、以下のような仕様での実装例を考えてみます。

文字列からboolへの変換の例

文字列 bool値
TRUE True
true True
T True
yes True
1 True
それ以外 False

Trueとなる文字列を列挙してマッチするかどうか

def str2bool(s):
     return s.lower() in ["true", "t", "yes", "1"]

# 確認コード
for s in ["TRUE", "FALSE", "true", "false", "T", "F", "yes", "no", "1", "0"]:
    print(s, str2bool(s))

True対象でなければFalseとなるので、これで文字列からboolへの変換が実装できます。

distutils.util.strtoboolを使用

標準ライブラリにはないと書きましたが、別の用途で使用するモジュールにひっそり似たようなものがあります。
これを使用するのも楽です。

from distutils.util import strtobool

# 確認コード
for s in ["TRUE", "FALSE", "true", "false", "T", "F", "yes", "no", "1", "0"]:
    print(s, " ==> ", strtobool(s))    # Trueのとき1, Falseのとき0を返す

1と0の数値で返却されますが、数値の真偽値判別では0はFalse, 0以外はTrueとなるので問題ありません。先ほど挙げた変換例ともマッチするようです。

dictを値でソートして出力する

dictオブジェクトを走査するときなどに値でソートされた結果を得たい場合は、
sortedのkey引数に値を返す関数を指定すれば良い。(key引数はソートしたい値を返す関数を指定できるので、ここを実装すれば任意のソート方法でソートできる)

d = {"a": 3, "b": 2, "c": 1}
for k in sorted(d, key=lambda k:d[k]):
    print(k, d[k])    #c,b,aの順で出力

参考

ソート HOW TO

外部コマンドの実行

外部コマンドを実行するにはsubprocessモジュールを使用する。

subprocess.callで実行できるが、python3ではsubprocess.runを使用することが推奨される。(callはまだ使えるが将来的には廃止の予定)

python3の場合 subprocess.run

たくさんのオプション引数を指定できるので詳細は公式ドキュメント参照。
subprocess.run

import subprocess
subprocess.run(["ls", "-l"])

python2の場合 subprocess.call

たくさんのオプション引数を指定できるので詳細は公式ドキュメント参照。
subprocess.call

import subprocess
subprocess.call(["ls", "-l"])

標準入力や、標準出力をキャプチャしたり、タイムアウトを設けたり、実行エラーをハンドリングしたりといったことが引数を指定することで可能になる。

ファイル・ディレクトリの存在チェック

ファイルやディレクトリのパスが存在するかどうかをチェックするには、低レベルなos.pathや、オブジェクトライクな操作が可能なpathlib(python3)が使用可能。

os.path.exists

文字列でパスを指定。

import os
print(os.path.exists('/tmp/exist-file'))      # ==> True
print(os.path.exists('/tmp/not-exist-file'))  # ==> False

pathlib.Path

python3ではpathlibモジュールでオブジェクト指向的なアプローチが可能なPathオブジェクトから存在チェックができる。

import pathlib
p = pathlib.Path.home() / '.ssh' / 'config'     # $HOME/.ssh/config
print(p.exists())

existsメソッドはファイルでもディレクトリでもパスとして存在すればTrueになる。
ファイルとして、ディレクトリとして、チェックする場合はis_file(), is_dir()が使用可能。