山pの楽しいお勉強生活

勉強の成果を垂れ流していきます

Pythonでjsonを読み込み、出力する際にdateやdatetime型を使用する

出力の時の話はよく記載がありましたが、読み込みの際に変換する方法はあまりなかったのでメモ。

概要

jsonでは日付型というのは定義されていません。(そもそもどう表現する?) そのため、pythonjsonを読み込みの際に日付が含まれていても文字列になりますし、出力の際にdate型が含まれているとエラーになります。

対応方法としては読み込むためのloadsではobject_hook、出力のためのdumpsはdefaultというオプションがあるので、こちらを設定することで対応が可能です。

対応前のコード

import json
data = '{"date" : "2019/02/08"}'
d = json.loads(data)
type(d['date']) # str
import json
from datetime import datetime
data = {'date' : datetime.now()}
json.dumps(data)
# TypeError: Object of type 'datetime' is not JSON serializable

対応後

詳しくは下記のコードを見てください。 出力時は型を調べて文字列に変換するだけなので何の問題もないですが、読み込み時は何らかの条件で日付を判別しdate型に変換しています。 (jsonには明示的に日付を示す構文はないためしょうがない。) ここではキーの末尾が「date」の場合に、date型に変換するようにしています。

import re
import json
from datetime import datetime, date

DATE_FORMAT = '%Y/%m/%d'
DATE_KEY = re.compile(r'date$')

def _json_parser(dct):
    for k, v in dct.items():
        if re.search(DATE_KEY, k):
            dct[k] = datetime.strptime(v, DATE_FORMAT).date()
    return dct

json_str = '{"hoge_date" : "2019/01/02", "l" : [{"huga_date" : "2018/12/31"}]}'
dct = json.loads(json_str, object_hook=_json_parser)
print(dct)
print(type(dct['hoge_date'])) # <class 'datetime.date'>
print(type(dct['l'][0]['huga_date'])) # <class 'datetime.date'>

def json_serial(obj):
    if isinstance(obj, date):
        return obj.strftime(DATE_FORMAT)
    return obj
json.dumps(dct, default=json_serial)

参考

S3cmdのProxy設定について

s3cmdを使用して動いていたバッチ処理で、proxy設定を追加しようとしたら色々大変だったお話。

S3cmdとは

S3cmd : Command Line S3 Client and Backup for Linux and Mac

公式ページより。

Windows用にはS3Expressというのがあるらしい。

S3Express : Command Line S3 Client and S3 Backup for Windows

まとめ

  • 環境変数(http_proxy)ではなく、設定ファイルに記載する
    • proxy_host, proxy_port
    • 設定ファイルのデフォルトは「~/.s3cfg」
    • コマンド実行時に「-c」で設定ファイルを渡すことも可能
  • ↑のproxy設定はPython2.7以上が必要
  • Python2のバージョン上げたくない場合はPython3を使用することも可能
    • S3cmdのV2以降はPython3を使用することも可能。
  • python3を使用する場合にはs3cmdを直接実行するのではなく、「python3 s3cmd」的な感じで実行する必要がある。
    • 「s3cmd」はPythonスクリプトで先頭に「#!/usr/bin/env python2」とpython2の指定があるため。

経緯とか(読む意味なし)

  • 会社でproxyを設定しろよという連絡がきた
  • (半)自動化されていたとあるシステムが動かない
  • S3cmdの処理で落ちている
    • Problem: error: [Errno 111] Connection refused
  • Proxy設定してないのでは?
    • export http_proxy=xxxxx
    • export https_proxy=yyyy
  • 変わらずエラー
    • 調べた → 設定ファイルに書く必要がある
  • 設定ファイルに書いた
  • python2.7以上で動かせと怒られる
    • 2.6.6か何かで動いていた
  • python2のバージョンは上げたくない。というかpython3にしたい
  • s3cmdは2のみ → s3cmdバージョン2で対応しているらしい → s3cmdのバージョンアップ
  • python3をデフォルトにしてs3cmd起動
  • python2.7以上で動かせと怒られる
    • (゚Д゚)ハァ?
  • シバンにpython2が指定してある
  • python3に引数でs3cmd渡して起動
  • OK

参考URL

Pythonからs3fsを使用してS3を操作した際にハマった件について

はじめに

PythonsからS3をいじる際に、これまではaws cliを直接叩いていたのですが、s3fsを使用するとexistsやlsみたいなわかりやすい名称で使えるというのをどこかで知ったので使ってみた。

同名でS3をマウントするもの( https://github.com/s3fs-fuse/s3fs-fuse )もあるようですが、本記事でのs3fs( https://github.com/dask/s3fs )とは別物です。 で、キャッシュのせいかファイルを置いた直後にexistsするとFalseだったりと整合性がとれなくてハマった話です。

s3fsとは

リポジトリTOPには下記のように記載されています。

S3FS builds on boto3 to provide a convenient Python filesystem interface for S3.

Google翻訳すると↓

S3FSはboto3をベースにしており、S3用の便利なPythonファイルシステムインタフェースを提供します。

「はじめに」にも書きましたが、同名でS3をマウントするものもあるようですが、別物です。 むしろ、GitHubの☆を見てもマウントする奴が3000以上、本題の方が130位なので、どちらかというとこの記事に書くライブラリの方が無名。

使い方

インストールして(詳細

pip install s3fs

以下のように書けばOK

import s3fs
fs = s3fs.S3FileSystem(anon=True)
list = fs.ls('my-bucket')

ドキュメントがしっかりしているので、以下に気をつければ?簡単に使えて良いです。 が、気をつけるなんて現実的ではないので個人的にはあまりオススメしません。

ハマったことと対応方法

内容としては、ファイル配置後に上位のフォルダを見た際にFileNotFoundErrorになるという事。
で、キャッシュが悪さしているので、キャッシュを更新すれば正しく動作します。 (キャッシュ更新するにはlsのrefreshオプションをTrueにする)

具体的には下記のコード参照。 (実際に試したい場合には↓↓に全コード書いたので、そちらをオススメ。)

import s3fs

fs = s3fs.S3FileSystem(key=key, secret=secret)
# 特定のPATHの下にフォルダを挟んでファイルを配置
output_path = '{}/hoge/{}'.format(s3_path, 'test.txt')
fs.put('./test.txt', output_path)

# ファイルを配置したフォルダではなく上位のフォルダでlsするとFileNotFoundError
try:
    fs.ls(s3_path) # FileNotFoundError
except FileNotFoundError:
    print('ls : FileNotFoundError')
# existsでも同様に取得できない
if not fs.exists(s3_path):
    print('exists : False')
# 上位のフォルダから再帰的に削除しようとしてもFileNotFoundError
try:
    fs.rm(s3_path, recursive=True) # FileNotFoundError
except FileNotFoundError:
    print('rm : FileNotFoundError')

# lsにrefreshオプションがあったので付与すると取得できる
try:
    fs.ls(s3_path, refresh=True) # refresh=Trueをつける
    print('ls : refreshしたら例外なし')
except FileNotFoundError:
    pass

# 1度refreshするとexistsも問題なし
if fs.exists(s3_path):
    print('exists : refresh後はTrue')

# 同様にrefresh後はlsにrefreshつけなくても例外なし    
try:
    fs.ls(s3_path)
    print('ls : FileNotFoundErrorは発生しない')
except FileNotFoundError:
    pass

考察とか

全コード

import s3fs

key = 'xxxxxxxxxx'
secret = 'yyyyyyyyyyyyyyy'
s3_path = 'my-bucket/aaa/bbb'

# 前準備
try:
    fs.ls(s3_path, refresh=True)
    fs.rm(s3_path, recursive=True)
except FileNotFoundError:
    pass
try:
    fs.ls(s3_path)
except FileNotFoundError:
    pass
try:
    fs.ls('{}/{}'.format(s3_path, 'hoge'))
except FileNotFoundError:
    pass

# 特定のPATHの下にフォルダを挟んでファイルを配置
output_path = '{}/hoge/{}'.format(s3_path, 'test.txt')
fs.put('./test.txt', output_path)

# ファイルが置かれていることを確認
try:
    fs.ls(output_path)
    print('ls : FileNotFoundErrorは発生しない')
except FileNotFoundError:
    pass
if fs.exists(output_path):
    print('exists : True')

# ここから本番
# ファイルを配置したフォルダではなく上位のフォルダでlsするとFileNotFoundError
try:
    fs.ls(s3_path) # FileNotFoundError
except FileNotFoundError:
    print('ls : FileNotFoundError')
# existsでも同様に取得できない
if not fs.exists(s3_path):
    print('exists : False')
# 上位のフォルダから再帰的に削除しようとしてもFileNotFoundError
try:
    fs.rm(s3_path, recursive=True) # FileNotFoundError
except FileNotFoundError:
    print('rm : FileNotFoundError')

# lsにrefreshオプションがあったので付与すると取得できる
try:
    fs.ls(s3_path, refresh=True) # refresh=Trueをつける
    print('ls : refreshしたら例外なし')
except FileNotFoundError:
    pass

# 1度refreshするとexistsも問題なし
if fs.exists(s3_path):
    print('exists : refresh後はTrue')

# 同様にrefresh後はlsにrefreshつけなくても例外なし    
try:
    fs.ls(s3_path)
    print('ls : FileNotFoundErrorは発生しない')
except FileNotFoundError:
    pass

参考URL

PowerShellでJava実行する際にシステムプロパティを設定する際の注意点

概要

f:id:yamap_55:20181108195116p:plain f:id:yamap_55:20181108195251p:plain

  • 正しく実行する際には java "-Dsystem.language=ja" Sample のようにダブルクォーテーションで囲う必要がある。 f:id:yamap_55:20181108195415p:plain

  • PowerShellは「-」の後に「.」が来ると区切りと判定するらしい

経緯とか

  • 日本語版Stack Overflowで質問発見
  • ↓のJavaコマンドを投げても正しく動かないらしい。
    • java -cp libtensorflow-1.11.0.jar;. -Djava.library.path=.\jni HelloTensorFlow
  • エラーメッセージを見るとクラスが正しく認識でされてないっぽいので、コマンドのどこかにスペース入ってるんじゃね?っと思ったら違うらしい。
    • エラーメッセージ : エラー: メイン・クラス.library.path=.\jniが見つからなかったかロードできませんでした
  • やり取りしていたら、PowerShell上ではオプションが正しく設定されないという事が発覚。
  • 調べてみたらPowerShellの仕様っぽい。

echoの例

  • 正常系1
PS C:\work\20181108> echo a b
a
b
  • 正常系2
PS C:\work\20181108> echo a.b
a.b
  • 想定外1
PS C:\work\20181108> echo -a.b
-a
.b
  • 想定外2

本題ではないですが、「,」を入れてみたら分割されました。

PS C:\work\20181108\j> echo a,b
a
b

参考URL

S3で静的ファイルのホスティングしてIP制御

経緯

静的なHTMLをお客さんに見せる事になり、ローカルPCで見せてもいいけど、どうせならどこかにホスティングして置いといた方がいいよね。って事でS3に置いたらIP制御でハマったのでメモ。

※ハマったというか、エンドポイントではなくて直接ファイルにアクセスしてたのでうまくいかなかっただけ。。。

手順

  1. S3にバケット作る
  2. バケットのプロパティ → Static website hosting → 「このバケットを使用してウェブサイトをホストする」 → 保存
    • エンドポイントのURLをメモ
    • http://bucket-name.s3-website-ap-northeast-1.amazonaws.com
  3. 2で設定したインデックスドキュメント(デフォルトはindex.html)を置く
  4. エンドポイントにブラウザでアクセスして、index.htmlが表示されることを確認。
  5. バケット → アクセス権限 → バケットポリシー
    • 以下のように記載(うまくいかない場合は、ポリシージェネレーターで作成)

バケットポリシーエディタ

{
    "Version": "xxxxxxxxxxxxx",
    "Id": "xxxxxxxxxxxx",
    "Statement": [
        {
            "Sid": "xxxxxxxxxxxxx",
            "Effect": "Allow",
            "Principal": "*",
            "Action": "s3:*",
            "Resource": "arn:aws:s3:::bucket-name/*",
            "Condition": {
                "IpAddress": {
                    "aws:SourceIp": [
                        "198.51.100.0",
                        "192.0.2.0"
                    ]
                }
            }
        }
    ]
}
  • 各「xxxx」的な所と、Resourceの「バケット名」、aws:SourceIpの「IP」は適当な値に変更する。

確認

  • 自分のIPのみを設定して表示されることを確認。
  • 他のIP(スマホからなど)で表示されないことを確認。
  • ↑と同じく、直接ファイルのリンクにアクセスして、表示される、されない事を確認。

VSCodeで静的コード解析ツールPylintを使用する

はじめに

PythonではPEP8というコード規約が一般的に使用されているようです。(PEP 8 -- Style Guide for Python Code

この規約に準拠したツールがいくつかあり、これ!っというものは特にないという印象です。
で、代表的なものとして、同名のpep8があるのでややこしい。
ちなみにツールの方のpep8は名前を変更してpycodestyleとなっていますので注意。(pep8は1.7.1が最終更新のようです。)
詳細はQiitaにまとまっている記事がありましたのでこちらを参照してください。(pep8 が pycodestyle に変わった話

他にもflake8や今回紹介するPylintが競合としてあるようです。
で、今回はPylintを使用することになっていたので、こちらをVSCodeで使用する方法について調べたメモです。

手順

  • VSCodePythonを入れる際にはまず使用するであろう拡張機能ms-python.pythonを入れる
  • 設定を確認
    • "python.linting.enabled"true になっていること
    • "python.linting.pylintEnabled"true になっていること
    • "python.linting.lintOnSave" はお好みで
    • "python.linting.pylintPath" このPATHでコマンドが実行されるので、PATHが通ってない場合には修正が必要です。
  • Pylintの設定ファイルを作成
    • デフォルトだと警告が出てくれない事、厳しすぎる規約を変更したい事から設定ファイルを作成します。
    • 下記のコマンドでデフォルトの設定ファイルが作成されます。
    • pylint --generate-rcfile > .pylintrc
      • 【注意】私の環境だとUTF-16で出力されたのでUTF-8に変換の必要がありました。
    • 設定ファイルの場所は公式ドキュメントを参照。
  • 指摘を確認
    • 問題ビューに指摘が表示されます。(macだとshift + command + m)
    • 明らかにおかしいコードで表示されない場合には、設定ファイルを一旦renameして指摘されるか確認しましょう。
  • 設定ファイルを調整
  • システム全体で警告を無効化する場合は設定ファイルを修正。
    • あるコードのみ例外的に無視したい場合にはコメントとして↓のように無視したいコードを入力すれば無視してくれます。
      • # pylint: disable=C0330
      • あまり調べていないのですがファイル全体で無効化されるようなので注意。
    • 好みですが、私は以下を修正しました。
    • max-line-lengthを120
      • デフォルトの100だと厳しいと感じました。
    • good-namesにf,vを追加
      • スコープに関係なく、1から2文字の変数を許してくれません。
      • 実際のプロジェクトでは分析でよく使用される変数名を追加しています。

参考URL

オブジェクトの配列をPandasのDataFrameに変換する

背景とか

オブジェクト(エンティティとか、JavaBeansとか、DTOとか呼ばれる属性(フィールド)しか持たない奴)の配列をPandasのDataFrameに変更したメモです。
簡単にできそうだったけどできなかったので結局dictionaryに変換して突っ込みました。
PythonにもPandasにも詳しくないのですが、こういうケースはよくあると思ったのですが、あまりない??

コード

import pandas as pd

class Hoge:
    c1 = ''
    c2 = ''
    c3 = ''

    def __init__(self, *args):
        self.c1 = args[0]
        self.c2 = args[1]
        self.c3 = args[2]

hoge1 = Hoge('c1_1','c2_1','c3_1')
hoge2 = Hoge('c1_2','c2_2','c3_2')
hoge3 = Hoge('c1_3','c2_3','c3_3')

ar = [hoge1, hoge2, hoge3]

# 最初こんなのでできるかなと思ってました。
df1 = pd.DataFrame(ar)
print(df1) # 1列3行でHogeオブジェクトが設定される(当たり前)

# 最終的にこうなりました。
df2 = pd.DataFrame(list(map(lambda hoge: vars(hoge), ar)))
print(df2)