.rute1/

キャリア/心理学/読書/Pythonなどなど

『Pythonトリック』を読んだ

Pythonトリック』が良書だったので紹介します。

Pythonトリック

Pythonトリック

翔泳社のページからPDFでの購入もできます。

私は普段の業務ではRubyを使うことの方が多いのですが、Pythonにも興味があり趣味で書くこともあります。 pythonの言語の仕組みやより良いとされている書き方を勉強したかったのですがFluent Python ―Pythonicな思考とコーディング手法Effective Python 第2版 ―Pythonプログラムを改良する90項目は分厚すぎて尻込みしていたところ こちらの本はコンパクトで立ち読みしたところちょうどよく知りたいことが書かれていた感じだったので購入してみました。

目次

CHAPTER 1 はじめに
1.1 Pythonトリックとは何か
1.2 本書はどのように役立つか
1.3 本書の読み方

CHAPTER 2 よりクリーンなPythonのためのパターン
2.1 アサーションによる安全対策
2.2 無頓着なコンマの配置
2.3 コンテキストマネージャーとwith文
2.4 アンダースコアとダンダー
2.5 文字列のフォーマットに関する衝撃の事実
2.6 「The Zen of Python」の隠しコマンド

CHAPTER 3 効果的な関数
3.1 Pythonの関数はファーストクラスオブジェクト
3.2 ラムダは単一式の関数
3.3 デコレータの威力
3.4 *argsと**kwargs
3.5 引数のアンパック
3.6 ここから返すものは何もない

CHAPTER 4 クラスとオブジェクト指向プログラミング
4.1 オブジェクトの比較:”is”と”==”
4.2 文字列変換:すべてのクラスに_repr_が必要
4.3 カスタム例外クラスを定義する
4.4 趣味と実益を兼ねたクローンオブジェクトの作成
4.5 抽象基底クラスは継承に待ったをかける
4.6 名前付きタプルは何に役立つか
4.7 クラス変数とインスタンス変数の落とし穴
4.8 インスタンスメソッド、クラスメソッド、静的メソッドの謎を解く

CHAPTER 5 Pythonの一般的なデータ構造
5.1 ディクショナリ、マップ、ハッシュテーブル
5.2 配列
5.3 レコード、構造体、DTO
5.4 セットとマルチセット
5.5 スタック(LIFO)
5.6 キュー(FIFO)
5.7 優先度付きキュー

CHAPTER 6 ループとイテレーション
6.1 パイソニックなループの書き方
6.2 内包を理解する
6.3 リストのスライスとすし演算子
6.4 美しいイテレータ
6.5 ジェネレータは単純化されたイテレータ
6.6 ジェネレータ式
6.7 イテレータチェーン

CHAPTER 7 ディクショナリのトリック
7.1 ディクショナリのデフォルト値
7.2 趣味と実益を兼ねたディクショナリのソート
7.3 ディクショナリを使ってswitch/case文をエミュレートする
7.4 型破りなディクショナリ式
7.5 ディクショナリのいろいろなマージ法
7.6 ディクショナリの出力を整える

CHAPTER 8 パイソニックな生産性向上テクニック
8.1 Pythonのモジュールとオブジェクトを調べる
8.2 仮想環境を使ってプロジェクトの依存関係を分離する
8.3 バイトコードの裏側を覗く

CHAPTER 9 最後に
9.1 Python開発者のためのメールマガジン
9.2 PythonistaCafe:Python開発者のコミュニティ

2章は手取り早く役にたつTips集といった感じで、この章だけでも読むとこの本がどんな本かわかると思います。

3章以降は関数、クラス、イテレータ、データ構造などそれぞれのトピックを取り上げて色々なテクニックを紹介する内容になっています。 各章の内容は独立していて、どこから読んでも良いような内容になっているので、とりあえず興味のある章だけ読むという読み方をしても良いと思います。

プログラミング言語初心者でオブジェクト志向はなんとなくわかるというレベルなら4章から、すでに他の言語をある程度理解していてPythonならではの書き方や特徴を知りたいというかたは5~6章、関数型の書き方に興味があるという方は3章がおすすめです。

さらに、各節の最後には内容の簡単な要約もついているので、上級者の場合は最初に要約から読んで知らなった項目だけ読むということもできます。 (私の場合は70%くらいが初めて知る内容でした。)

with文に対応したObjectの作り方

ここからは私が特に面白いと感じた内容について簡単に紹介していきます。

with文はファイル操作などでよく見る書き方です。

with open('hello.txt', 'w') as f:
    f.write('hello, world')

この場合はwithブロックから抜けたらすぐに自動でf.close()を行うことで、安全にリソースを管理することができるようになっています。 with文に対応するオブジェクト(=コンテキストマネージャと呼ばれるらしい)は、このように必ず最初と最後に行いたい処理がある場合に便利な機能です。

実はこのようなコンテキストマネージャは自作クラスや関数でも簡単に作れるようです。
私が試しに考えてみた、環境変数を一時的に変更できる関数を紹介します。

import contextlib
import copy
from os import environ

@contextlib.contextmanager
def env(env: str, value):
    original = copy.copy(environ[env]) # 元の環境変数を保存
    environ[env] = value # 環境変数を上書き
    yield
    environ[env] = original # 元あった値に戻す

# 使い方
print(environ["USER"]) # tarou

with env("USER", "honako"):
    print(environ["USER"]) # hanako

print(environ["USER"]) # tarou

詳しい仕組みはぜひ書籍を読んでいただきたいですが、ざっくりと解説すると、@contextlib.contextmanagerというデコレータを使うことで、 自作のenv関数をwth文に対応させています。
関数の中のyieldまでがwith文の最初に処理される部分、yield以降がwithブロックから抜けた後に実行される処理です。
ここでは最初に既存の環境変数を別の変数に保存し、最後に再び元の環境変数にセットし直すことで一時的に環境変数を変更するということができました。

これ以外にも最初と最後になんらかの処理を実行したいという時にはwith文に対応した関数を使えば書くほうも読む方も楽になりそうなので、便利に使えそうです。

また、この本では関数ではなくオブジェクト(クラス)をwith文に対応させる方法も書かれています。 本の中で宿題として「time.time関数を使ってコードブロックの実行時間を計測するコンテキストマネージャーを実装してみてください」と書かれていたので、 自分なりに考えて実装してみました。

Python stop watch · GitHub

こちらはクラスベースの実装になっていますが、ネタバレになってしまう可能性もあるので心配な方はぜひ書籍を読んでから見てみてください。

Cloud9から既存EC2(tokyo)に初回設定のSSHができない問題を解決

Cloud9から既存EC2(tokyo)に初回設定のSSHができない

Tokyoリージョンに立てた開発用のEC2にCloud9(シンガポールリージョン)からの初期設定のSSHができなくて困りました。
以下のことを行うことで設定できました。

  • nodejsがEC2に入っていることを確認する
    • node -v
  • /home/user/.ssh/authorized_keys にコンソールからコピーした公開鍵を貼る
  • セキュリティーグループのTCP,PORT 22を一旦0.0.0.0/0に解放する
  • Environment pathはデフォルトで/home/ec2-userを見に行くため、ubuntucentosの場合やユーザー名を変更している場合は個別で設定する。
    • /home/ubuntu

セキュリティグループ をよりセキュアに設定したい

セキュリティグループ が0.0.0.0/0に解放したままでは危険のため、セキュリティグループ の設定をAWSシンガポールリージョンのCloud9のIPのみに設定します。
AWSのIP範囲はこちらから確認できます。
AWS IP アドレスの範囲 - アマゾン ウェブ サービス
このドキュメントの通りに、jsonからIPを抜き出します。

# 一旦jsonをファイルに保存
curl https://ip-ranges.amazonaws.com/ip-ranges.json > ip-ranges.json

# Linux等、jqが使える場合
jq -r '.prefixes[] | select(.region=="ap-southeast-1") | select(.service=="CLOUD9") | .ip_prefix' < ip-ranges.json

>>> 13.250.186.128/27
>>> 13.250.186.160/27

これで、13.250.186.128/27と13.250.186.160/27に対してセキュリティグループ のTCP, Port 22を解放すればOKです。
Cloud9のリージョンがシンガポール以外の場合は.region==""の部分を適宜変更してください。

AWS クロスアカウント権限管理 「リソースポリシーからrootに許可するパターン」編

クロスアカウントで色々いじっていて、IAMの権限管理についてのパターンがなんとなくわかってきたのでメモします。

クロスアカウントでの権限管理は色々なパターンがあるようですが、今回扱うのは「AアカウントのリソースポリシーでBアカウントのrootを許可→Bアカウントの中でポリシーアタッチ」のパターンです。

具体的に見ていきましょう。まずはよくあるS3のパターンです。

別アカウントのS3バケットにLambdaからアクセスする

ここではアカウントID:111111111111アカウント1からアカウントID:0000000000000000アカウント0のS3バケットにアクセスしたいとします。 f:id:rutei:20190105180724p:plain
アカウント0のコンソールで、S3のバケットポリシーを以下のように設定します。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "Example permissions",
            "Effect": "Allow",
            "Principal": {
                "AWS": "arn:aws:iam::1111111111111:root"
            },
            "Action": "s3:GetObject",
            "Resource": "arn:aws:s3:::BUCKET_NAME/*"
        }
    ]
}

ここではアカウント1のrootユーザーに対してS3バケットへのアクセスを許可しています。

次に、許可をもらったアカウント1のコンソールで以下のポリシーを作成し、S3バケットにアクセスするLambdaにアタッチします。

{
    "Version": "2012-10-17",
    "Statement": [ 
            {
            "Action": [
                "s3:GetObject"
            ],
            "Resource": "arn:aws:s3:::BUCKET_NAME/*",
            "Effect": "Allow"
        }
    ]
}

これで、ポリシーをアタッチされたLambdaはアカウント0のS3バケットに対してアクセスすることができるようになります。
まずはアカウントのrootユーザーに権限を与える → 権限を得たroot(admin)ユーザーがリソースに対して権限を与えるという流れでわかりやすいと思います。
もちろん、最初にバケットポリシーで許可したActionしかできないようになっているので、例えば今回の場合s3:GetObject以外の操作権限はLambda にもrootユーザーにも与えられていません。
LambdaからObjectを新たにPUTしたりするためには、再びアカウント0の管理者にバケットポリシーからs3:PutObjectを許可してもらう必要があります。

別アカウントのLambdaにLambdaからアクセスする

先ほどと同様にアカウント1のLambdaからアカウント0のLambdaにアクセスしたいとします。
f:id:rutei:20190105181018p:plain
Lambdaの場合はリソースポリシーというものがあるので、ここで、アカウント1のrootユーザーに権限を与えます。
参考: AWS Lambda でリソースベースのポリシーを使用する (Lambda 関数ポリシー) - AWS Lambda この操作は現在AWS CLIAPIでしか行えないようです。

aws lambda add-permission \
--function-name FUNC_NAME \
--statement-id Example permissions \
--action lambda:InvokeFunction \
--principal 111111111111 \
--output json 

上記のコマンドを実行すると以下のjsonが返ってきます。

{
    "Statement": "{\"Action\":[\"lambda:InvokeFunction\"],
                   \"Resource\":\"arn:aws:lambda:YOUR_REGION:000000000000:function:FUNC_NAME\",
                   \"Effect\":\"Allow\",
                   \"Principal\":{\"AWS\":\"111111111111\"},
                   \"Sid\":\"Example permissions\"}"
}

CLIなので、見ためは違いますが、やっていることとしては先ほどのバケットポリシーと対して変わりません。アカウント1のrootユーザーに対してアカウント0のLambdaへのlambda:InvokeFunctionの権限を与えています。
次に、アカウント1のコンソールから、Lambda(アクセスする方)の実行ロールにアカウント0のLambdaへアクセスする権限を与えるポリシーをアタッチします。

{
    "Version": "2012-10-17",
    "Statement": [ 
        {
            "Action": [
                "lambda:InvokeFunction"
            ],
            "Resource": "arn:aws:lambda:YOUR_REGION:000000000000:function:FUNC_NAME",
            "Effect": "Allow"
        }
    ]
}

これで、アカウント1のLambdaからアカウント0のLambdaにアクセスすることができるようになります。

おまけ: kmsのキーポリシーをIAMポリシーで管理する

実はクロスアカウントの権限管理以外にも、似たような権限の与え方をするパターンがあります。
それが、KMSのキーポリシーです。
AWSでは若干複雑なことに、KMSで作ったCMKに対する権限をKMSのキーポリシーで管理するということになっています。
ただ、特にLambdaに関しては実行ロールに付与されているIAMポリシーでLambdaの権限を管理できた方がわかりやすいため、IAMポリシーで管理できた方が便利だと思います。
そういった場合に、キーポリシーをIAMポリシーで管理できるようにするキーポリシーの書き方が以下です。

{
  "Sid": "Enable IAM User Permissions",
  "Effect": "Allow",
  "Principal": {"AWS": "arn:aws:iam::111111111111:root"},
  "Action": "kms:*",
  "Resource": "*"
}

これで、IAMポリシーでKMSのCMKの権限を管理できるようになります。 Lambdaの実行ロールには以下の権限を付与します。

{
    "Version": "2012-10-17",
    "Statement": [
         {
            "Action": [
                "kms:*"
            ],
            "Resource": "arn:aws:kms:YOUR_REGION:111111111111:key/xxxxxxxxxx",
            "Effect": "Allow"
        }
    ]
}

【Python】responder でHello, World ! 0.0.0.0をhostに指定する方法

responder でHello World! 0.0.0.0をhostに指定する方法

人間のためのイケてるPython WebFramework「responder」、そして作者のKenneth Reitzについて - フリーランチ食べたい

こちらの記事で話題になっているresponderを早速試して見ました。
Hello World ! するまでに2つひっかかったポイントがあったので、共有します。
ちなみにresponderどころかDjangoもFlaskも使ったことのない初心者なので、おかしな内容になっているかもしれません。ご容赦ください。
※2018年12月2日時点での情報です。

環境 * AWS EC2 Ubuntu Server 18.04 LTS (HVM), SSD Volume Type * Python 3.6.5

ModuleNotFoundError: No module named 'starlette.lifespan'

とりあえずこちらのクイックスタート通りにHello World ! を書いてみました。   Quick Start! — responder 1.1.2 documentation

import responder
api = responder.API()
@api.route("/")
def hello_world(req, resp):
    resp.text = "hello, world!"
api.run()

実行したところModuleNotFoundError: No module named 'starlette.lifespan’というエラーが出てしまいました。
これはgithubのissueにもあげられていました。
ModuleNotFoundError: No module named ‘starlette.lifespan’ · Issue #255 · kennethreitz/responder · GitHub.

problem solved by using starlette 0.8 version

とりあえず今のところはstarletteのバージョン0.8を使えば解決するようです。
pip install starlette==0.8

hostに0.0.0.0を指定したい

ubuntu@ip-172-31-43-88:~/dev/test$ python hello.py
INFO: Started server process [17400]
INFO: Waiting for application startup.
INFO: Uvicorn running on http://127.0.0.1:5042 (Press CTRL+C to quit)

これはEC2を開発環境にしていると毎回困ることなのですが、localhost127.0.0.1にアクセスするのが困難です。
0.0.0.0を開くようにしないとローカルのブラウザから確認できません。

Responderのコードを読むと、どうやら環境変数として”PORT”が設定されている場合は0.0.0.0:PORTが開かれ、それでない場合は127.0.0.1:5042になるようです。

if "PORT" in os.environ:
    if address is None:
        address = "0.0.0.0"
        port = int(os.environ["PORT"])

if address is None:
    address = "127.0.0.1"
if port is None:
    port = 5042

ということで適用にわかりやすいPORTを設定して、

ubuntu@ip-172-31-43-88:~/dev/test$ export "PORT"=10001

EC2のセキュリティグループも変更します。 f:id:rutei:20181202020216p:plain

そしてブラウザからアクセスすると、 f:id:rutei:20181202020213p:plain

Hello World ! が無事表示されました 。

ubuntu@ip-172-31-43-88:~/dev/test$ python hello.py
INFO: Started server process [17405]
INFO: Waiting for application startup.
INFO: Uvicorn running on http://0.0.0.0:10001 (Press CTRL+C to quit)
INFO: ('m.y.i.p', 53094) - "GET / HTTP/1.1" 200

追記: api.run()の引数にaddressとportを与えても同様のことができるようです。

api.run(address='0.0.0.0',port=10001)

【Python】requestsでボタンを擬似クリック

requestsでボタンを擬似クリック

Beautiful Soupでhrefが取得できないボタン等のクリック動作をrequestsで再現する方法を調べたので、まとめます。

hrefが取得できないボタン

例えばこのページの「CSVダウンロード」ボタン部分などはBeatiful Soupでもhrefが取得しにくいためにrequestsでのスクレイピングが困難です。
CSVダウンロード f:id:rutei:20181130205349p:plain

csvダウンロード」ボタンのソースコードはこのようになっています。

<input class="btn btn-primary" type="submit" value="CSVダウンロード">

おそらくこのボタンを押すとなんらかのscriptが動いてダウンロードが始まるものと思います。
requestsでこのボタンをクリックするという動作を再現することはできないので、seleniumを使うといった選択肢をとることが多いかもしれませんが、場合によってはrequestsだけでボタンをクリックしたのと同等の通信を再現することができます。
以下がその方法です。

crhomeデベロッパーツールでnetworkを見てみる

f:id:rutei:20181130205413p:plain

デベロッパーツールを表示した状態でボタンをクリックすると、networkタブでどのような通信が行われているか確認することができます。
今回のダウンロードもバッチリURLを確認することができました。
URLはhttps://www.itdashboard.go.jp/Csv/downloadで、ここにPOSTでdataを送れば良さそうです。
cookieなどはrequestsのsession()を使えば処理してもらえます。
POSTで送るdataもデベレッパーツールから確認できます。
f:id:rutei:20181130205424p:plain

最終的なコードはこのようになりました。

import requests

# ページにアクセス
session = requests.Session()
res = session.get('https://www.itdashboard.go.jp/DataFeeds/csv')
res.raise_for_status() # エラーならここで例外を発生させる

# dataに項目をセット
data = {
    "_method": "POST",
    "year": "",
    "modelname": "BasicInformationAll"
}

# クリック時の通信
res = session.post('https://www.itdashboard.go.jp/Csv/download', data=data)
res.raise_for_status() # エラーならここで例外を発生させる

# レスポンスで返ってきたコンテンツをcsvとして保存
contentType = res.headers['Content-Type']
contentDisposition = res.headers['Content-Disposition']
ATTRIBUTE = 'filename='
fileName = contentDisposition[contentDisposition.find(ATTRIBUTE) + len(ATTRIBUTE):]

with open(fileName, 'wb') as saveFile:
        saveFile.write(res.content)

ファイルを保存する部分はこちらの記事を参考にしました。
Pythonのrequestsを利用してファイルダウンロードする方法 - Qiita

【GitHub】commit履歴にアイコンや名前が正しく表示されなくなった時の対処法

GitHubのアイコンが表示されない

最近、GitHubのcommit履歴で、自分のアイコンや名前が正しく表示されなくなりました。

以下のコミットは全て私のものなのですが、アイコンが表示されている時と表示されていない時があります。

commit履歴のアイコンが正しく表示されず、デフォルトの表示になっている。マウスオーバーしても、ユーザーネームが表示されない。
アイコンがデフォルト表示になっている

調べて見るとどうやらgit configを正しく設定していないことが原因だとわかりました。

私はMac OSAWSのEC2でgitを操作していたのですが、Mac OSではgit configを設定できていたのでアイコンや名前が表示されていて、EC2ではgit configを設定していないためにアイコンや名前が表示されなかったようです。

git configの設定を行うと、正常にアイコンが表示されるようになりました。 ギットコンフィグを正しく設定したら、正常にアイコンが表示されるようになった。

 

git configコマンドによる設定

私が設定したgit configのコマンドは以下の通りです。

$ git config --global user.name "Tanaka Tarou" #GitHubに登録したユーザーネーム
$ git config --global user.email Taro@example.com #GitHubに登録したメールアドレス

ちなみに現在どう設定されているかは

$ git config user.name
$ git config user.email

で確認することができます。 上記のコマンドを打っても何も返ってこなければgit configの設定がされていないことになります。

git configは奥が深いらしく設定ファイルの置き場所や設定項目は他にも色々あるようです。 詳しくは以下のサイトが参考になります。

参考にしたサイト:Gitの設定をgit configで確認・変更 | note.nkmk.me

 

MacBook Pro のキーボードの隙間にホコリが入る問題を解決した Maxkuキーボードカバー

今年の4月に MacBook Pro 13 inch (2017年モデル)を購入して仕事で使用しています。

機能的にはとても満足しているのですが一つだけかなり気がかりな点がありました。

キーボードの隙間に細かいホコリが入り込んでしまうことです。

こういった小さい埃の入り込みによってキーボードが押せなくなったり、あるいは二度と入力することができなくなったりする不具合も発生するようです。ただでさえMacbook Proはキーボードの不具合が公式に認められているので、神経質になってしまいます。

 

MacBook および MacBook Pro キーボード修理プログラム - Apple サポート

 

 

公式がサポートしてくれるのはまあ良いことなのですが毎日仕事で使用しているので数日使えなくなるような状況になるのも困ります。

そこでキーボードの隙間へのホコリの入り込みそのものの防止するような解決策はないか探しました。

その結果こんなものが見つかったので購入してみました。

 

 

 

 

これはシリコン製のカバーで、キーボードの上に乗っけて使います。

特に何かで固定するわけではないのですが、薄くキーボードの凹凸に沿って忠実に作られているので意図的に剥がそうとしない限りは外れてしまうことは滅多にないと思います。

これを常に貼り付けて PC を使用すればほこりが入ることもなくなるし、またうっかり飲み物などをこぼしてしまった時にもキーボードが浸水する恐れはありません。

気になるタイピングの感触の変化ですが、最初の数回は若干の違和感を感じることはあったものの慣れてしまえばほとんど日常の仕事には影響はない程度でした。またカバーの厚みも数ミリ程度なのでつけたまま PC を閉じることができます。

 

アマゾンのレビューによると数ヶ月間使用するとシリコンの形状が変化しキーボードから浮き上がってしまうこともあるようです。私は今のところ2ヶ月弱使用していますが確かに一部のキーが浮き上がってしまっている箇所もあります。

とはいえ、1000円弱の値段で購入することができたので私はそこまで 気にしていません。むしろ製品自体は気に入っているので買い替えに備えてもう一つ同じ商品を購入しました。

これである程度キーボードへのホコリ侵入問題は解決できたかなと思います。

 

 

f:id:rutei:20180629012135j:plain

色にこだわりはなく青のグラデーションカラーを選んだのは単に最も安かったからという理由だけなのですが、実際に使ってみると意外にも評判が良く褒められることが多いので気に入りました。

2016モデル向けの商品ですが、2017モデルでも問題なく使用できます。