argius note

プログラミング関連

Python3メモ 2017年・冬

ここ半年くらいで参考になった情報のメモ。

環境

Pythonは、一部を除いて現時点での最新バージョンを使用していますが、そんなに古くなければたぶんOKです。
また、動作はWindowsで確認していますが、明示した箇所以外には特にOSに依存するところはないと思います。

ログインした状態でWebページ取得

ログインしないと見られないページを、Beautiful Soupでパースしたいときなどに使いました。

この例は、Requests という追加モジュールが必要です。Anacondaフルパッケージには入っています。
今回は、バージョン2.13.0を使用しました。


import requests

logindata = {
    "user": "user",
    "password": "password"
}

s = requests.Session()
res = s.post("https://example.com/login", data=logindata)
res = s.get("https://example.com/user/private")
print(res.text)
# Windowsの場合
# print(res.text.encode("cp932", "ignore").decode("cp932"))


Windowsだと、printするときに、たまにUnicodeEncodeErrorが発生して面倒なことになります。
これについては次項で補足します。

標準出力への出力時のエンコードエラーを回避

Python3では、Python2のときの文字列がらみのツラさが緩和されているものの、それでも他のスクリプト言語と比較すると扱いにくいと思うところがあります。
特にWindowsだと、コンソール出力が(未だに)CP932なので、そこでハマることがあります。

出力内容に厳密さを求めないのであれば、変換できないものは無視してしまえば上手く行きます。

…と思ったら、3.6.1では下記は再現しませんでした。
下記は3.5.2の例です。

>>> s = "始\xa0終"
>>> print(s)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
UnicodeEncodeError: 'cp932' codec can't encode character '\xa0' in position 1: illegal multibyte sequence
>>> print(s.encode("CP932", "ignore").decode("CP932"))
始終


標準出力のフラッシュ(flush)

標準出力に出力した文字列が、バッファリングされてすぐに出力されないことがあります。
この場合、バッファリングされた文字列を即時出力する操作を行う必要があります。
この操作のことを、「(出力を)フラッシュする」と言います。

Pythonの場合は、sys.stdout.flush()でフラッシュ出来ます。
Python3.3からは、print("...", flush=True)が使えるようになっています。
さらに、起動オプションに-uをつけることで、常にバッファーさせない(unbuffered binary stdout and stderr)ようにもできます。

標準エラー出力についても同様です。stdoutstderrに読み替えて下さい。


対話環境で出力をフックする

sys.displayhookを使うと、対話環境で値の評価をしたときの出力処理をフックできます。
これは、Python2の時にマルチバイト文字を表示すると文字コードで表示されてしまうので、それを一時的に回避したい場合に使用しました。

以下の例は、Python2.7.12で確認したものです。

>>> import sys
>>> default_displayhook = sys.displayhook
>>> default_displayhook
<built-in function displayhook>
>>>
>>> def mydisp(obj):
...     if type(obj) is unicode: # unicodeの時だけ文字列としてprintする
...         print "(unicode)", obj
...     else:
...         print repr(obj)
...
>>> sys.displayhook = mydisp
>>> s = u"テスト"
>>> s
(unicode) テスト
>>> sys.displayhook = default_displayhook # デフォルトに戻す
>>> s
u'\u30c6\u30b9\u30c8'


文字列でない配列をjoinする

Pythonにおけるjoinは、文字列クラスstrのメソッドになっていて、引数もstrの配列になっていないといけません。
配列と書いていますが、シーケンス全般で同様です。

>>> a = [1, 2, 3]
>>> ", ".join(a)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: sequence item 0: expected str instance, int found


文字列以外の場合は、文字列のシーケンスに変換してからjoinするようにします。
mapを使うと比較的簡潔に書けます。
strで変換できないケースであれば、変換する関数を定義してそれを指定すれば良いです。

>>> a = [1, 2, 3]
>>> ", ".join(map(str, a))
'1, 2, 3'
>>> def f(o):
...   return "#%s#" % o
...
>>> ", ".join(map(f, a))
'#1#, #2#, #3#'

ジェネレーター式でカッコを省略できるケース

ジェネレーター式(ジェネレーター内包表記)は、ジェネレーターオブジェクトを返す式です。
単独では、丸カッコで囲む必要があります。

>>> (x for x in range(3))
<generator object <genexpr> at 0x00000000027F3410>
>>> x for x in range(3)
  File "<stdin>", line 1
    x for x in range(3)
        ^
SyntaxError: invalid syntax


関数に渡す場合は、単独であればカッコを省略できます。
ジェネレーター式以外の引数をつけて渡すと、「ジェネレーター式は単独の引数でない場合はカッコで囲む必要がある」(拙訳)というエラーメッセージが出ます。

>>> sum(x for x in range(1, 5))
10
>>> sum(x for x in range(1, 5), 7)
  File "<stdin>", line 1
SyntaxError: Generator expression must be parenthesized if not sole argument


ドキュメントには、

関数の唯一の引数として渡す場合には、丸括弧を省略できます。

と書かれています。


正規表現

JavaAPIに似ているので、とっつきやすいです。

import re

ptn = re.compile('class="([-\./\w]+?)"')
m = ptn.search('<div class="xyz">AAA</div>', 0)
if m:
  print(m.group(1)) # => xyz



(おわり)