tech_memo / python


tech_memo

jcコマンド

VSCode

正規表現を使った置換

  • 基本
    re.sub(正規表現, “置換する文字列”, 置換対象の文字列)
  • 例(改行を含む文字列の置換)
    import re
     
    text = """\
    070-1234-5678 Tokyo
    080-1234-5678 Chiba
    090-1234-5678 Saitama
    """
    text_mod = re.sub('^[0-9]{3}-[0-9]{4}-[0-9]{4}',"***-****-****",text, flags=re.MULTILINE) # flags=re.MULTILINEを追加
    print (text_mod)
    • 結果
      ***-****-**** Tokyo
      ***-****-**** Chiba
      ***-****-**** Saitama
  • 事前にコンパイルして性能向上させる場合
    import re
     
    saleVal = re.compile('[0-9]{2}%')
     
    text1 = "毎月5日は全品30%オフを実施中!!!"
    text2 = "毎月10日は全品50%オフを実施中!!!"
    text_mod1 = saleVal.sub("80%",text1)
    text_mod2 = saleVal.sub("90%",text2)
    print (text_mod1)
    print (text_mod2)
    • 結果
      毎月5日は全品80%オフを実施中!!!
      毎月10日は全品90%オフを実施中!!!

GUI

Tkinter

  • pythonの標準ライブラリ。
  • UIがダサいということであまり人気がなく、日本語ドキュメントはそこまで多くない

wxPython

  • 最も主流。
  • 以前はマニュアルでインストールする必要があったが、現在はpipでインストール可能になったため配布お楽。
  • 参考 : https://www.python-izm.com/gui/

kivy

  • レイアウトを設定ファイルで記述可能。Androidライクな感じ。
  • 最もトレンドだが、日本語ドキュメント少なし。
  • tech_memo/python/kivy

Pythonスクリプトを配布可能なexeファイルにする

PyInstaller?

bbfreeze

  • cx_Freezeよりsetup.pyの記述が少ない

cx_Freeze

  • setup.pyの記述要

ORマッパー (dataset)

  • 参考
  • インストール
    pip install dataset
  • 接続方法
    driver://user:pass@host/database
  • サンプル
    import dataset
    
    db_connect_str = 'sqlite:///C:/GitHub.Accounts.sqlite3'
    db = dataset.connect(db_connect_str)
    
    for account in db['Accounts']:
        print(account)
        print("id:{0},user:{1},pass:{2},mail:{3},create:{4}".format(account["Id"],account["Username"],account["Password"],account["MailAddress"],account["CreatedAt"]))

pip

pipの更新(pip installできない場合の対処)

  • pip installを実行すると下記が発生する
    Could not find a version that satisfies the requirement xxx (from versions: )
    No matching distribution found for xxx
  • pipのバージョン更新が必要
  1. $HOME/.pip/pip.conf の作成
    [global]
    trusted-host = pypi.python.org
                   pypi.org
                   files.pythonhosted.org
  2. pip更新
    curl https://bootstrap.pypa.io/get-pip.py | python3

ローカル環境にだけインストール

  • 下記設定で、$HOME/local配下にインストールされる
    export PYTHONUSERBASE=$HOME/local
    export PATH=$PATH:$HOME/local
    pip3 install --user python-language-server autopep8

プロキシ設定

pip install <PKG> --proxy=<PROXY_IP>:<PROXY_PORT>

OpenSSLのバージョンが古くてエラー

Linuxで仮想環境構築

複数バージョンのPythonを利用(pyenv)

  • 参考 : https://ccieojisan.net/post-1337/
  • pyenvのインストール
    $ git clone https://github.com/pyenv/pyenv.git ~/.pyenv
  • 環境変数設定
    $ echo 'export PYENV_ROOT="$HOME/.pyenv"' >> ~/.bash_profile
    $ echo 'export PATH="$PYENV_ROOT/bin:$PATH"' >> ~/.bash_profile
    $ echo 'eval "$(pyenv init -)"' >> ~/.bash_profile
    $ source ~/.bash_profile
  • 指定バージョンのpythonのインストール方法
    $ pyenv install 3.6.1
  • 利用するpythonのバージョン変更
    • 現在のシェルに対して変更
      $ pyenv shell 2.7.1
    • ディレクトリごとにpythonのバージョン変更
      $ pyenv local 2.7.1 # 設定したいディレクトリ直下で実行
    • デフォルト変更
      $ pyenv global 2.7.1

仮想環境ごとにパッケージ管理 (virtuealenv)

  • virutalenvのインストール
    $ git clone https://github.com/pyenv/pyenv-virtualenv.git ~/.pyenv/plugins/pyenv-virtualenv
  • 環境変数設定
    $ echo 'eval "$(pyenv virtualenv-init -)"' >> ~/.bash_profile
    $ source ~/.bash_profile
  • 仮想環境作成
    $ pyenv virtualenv 3.6.1 test_3.6.1
  • 仮想環境有効化
    $ pyenv activate test_3.6.1
    • この仮想環境内でpip installしたものは、この仮想環境内だけで有効
  • 仮想環境無効化
    $ source deactivate

Windowsで仮想環境構築

複数バージョンのPythonを利用

  • 参考 : http://web.plus-idea.net/2017/02/python2-3-venv-virtualenv/
  • 手順 (上記参考手順とちょっと違う。実際にやった手順)
    • Python2をインストーラでインストール
    • Python3をインストーラでインストール
      • カスタムインストールで、「Add Python to environment variables」のチェックを外す(標準インストールでも設定可能?)
    • コマンドプロンプトでオプションで利用バージョンを切り分け可能
      $ py -3  # ver 3を利用
      $ py -2  # ver 2を利用

仮想環境構築(pipenv)

仮想環境構築(virtualenv)

Cygwinで仮想環境の構築


複数バージョンのPythonを利用(pyenv)

  • 参考 : http://www.hakopako.net/entry/2017/09/15/191140
  • pyenvのインストール
  • python2.xのインストール方法
    • 公式のインストーラから、インストールパスを指定して配置する。
      • Windows x86-64 MSI installer : https://www.python.org/downloads/release/python-2712/
      • インストール先を /path/to/.pyenv/versions/2.7.12 とし、実行。
      • 指定のバージョンが指定の場所に配置されているか確認。
      • おそらくシンボリックリンクは貼られていないので、手動で貼る。
        $ cd .pyenv/versions/2.7.12
        $ mkdir bin && cd bin
        $ ln -s ../python.exe python
  • python3.xのインストール方法
    • 3系では embeddable file が配布されており、解凍して指定の場所に置くだけでも可。

  • 解凍したフォルダを /path/to/.pyenv/versions/3.6.2 に移動。
  • おそらくシンボリックリンクは貼られていないので、手動で貼る。
    $ cd .pyenv/versions
    $ mv /path/to/python-3.6.2-embed-amd64 3.6.2
    $ mkdir 3.6.2/bin && cd 3.6.2/bin
    $ ln -s ../python.exe python

仮想環境の種類(pyenv, pyenv-virtualenv, virtualenv, virtualenvewrapper, pyvenv, venv)

pyenv, pyenv-virttualenv

  • pyenv
    • 複数バージョンのpythonを利用したい場合に導入。
    • コマンドで利用するバージョンを切り替えられる。作業ディレクトリごとに、利用バージョンを指定可能
    • Windowsには対応していない
  • pyenv-virtualenv
    • pyenvのプラグイン
    • virtualenvとは別物
    • 同じPythonバージョンで、別々のsite-package(各バージョンで管理されるモジュール群)を管理できる
    • プロジェクトごとに利用するモジュールを分けたい場合に利用

virtualenv, virtualenvwrapper

  • virtualenv
    • pyenvと機能的には同じ?
    • pyenvより古い(pyenvのほうが利用勝手がよい)
  • virtualenvwrapper
    • virtualenvのラッパーで、virtualenvの使い勝手を良くしたもの

pyvenv, venv

  • venv
    • Python3.3から標準で組み込まれている
    • pyenvや、virtualenvと役割は同じだが、比べると機能が不足している?
  • pyvenv
    • 非推奨になった。
    • 中身は、venvを呼び出しているだけ

Webスクレイピング

headlessモードを検知させない方法

  • https://intoli.com/blog/making-chrome-headless-undetectable/
    • mitmproxyを使ってレスポンスHTMLにJSをくっつける方法
    • chromeドライバのheadlessモードが、mitmproxyからの自己署名証明書を受けれないので、Chrome Remote Interface(Node.js)を利用する必要あり

mitmproxy (man-in-the-middle proxy)

Selenium

PhantomJS

PhantomJSとは?

  • https://qiita.com/weedslayer/items/5e55f3e5af1f5cebf41f
  • WebkitベースのHeadlessブラウザ。Webkitとは主にWebブラウザーで用いられているレンダリングエンジン
  • Webブラウザを立ち上げずにスクレイピングが可能
  • Javaスクリプトで描画された要素も取得可能

利用

  • 参考 : http://uitspitss.hatenablog.com/entry/2016/04/15/193446
  • ダウンロード : http://phantomjs.org/download.html
  • 利用時にUser-AgentをChromeなどに設定しないと、スクレイピング対象のHTMLの構成が変わるので設定したほうがよい
    user_agent = 'Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/29.0.1547.66 Safari/537.36'
    # PhantomJS本体のパス
    pjs_path = './phantomjs'
    dcap = {
        "phantomjs.page.settings.userAgent" : user_agent,
        'marionette' : True  # WebDriverにデフォルトのGhostDriverではなくFirefox後継のmarionetteを利用する(あまりわかってない)
    }
    driver = webdriver.PhantomJS(executable_path=pjs_path, desired_capabilities=dcap)

シェル等の外部プログラム実行

curses

スクリーン描画など

クラス

メンバ変数定義時の注意

  • __init__メソッドでメンバ変数をself.XXXの形式で初期化しないと、Javaでいうstatic変数扱いになるっぽい
    def __init__(self):
        self.data = "A" # selfがないとstaticになる

リストの要素ごとに足し算等

グラフ描画

  • matplotlibを使用

インストール

  • python 2.4の場合
    • yumでインストール可能
      [root@testserver07 ~]# yum install python-matplotlib.x86_64 --disablerepo=base
      
      =========================================================================================================
       Package                       Arch               Version                     Repository            Size
      =========================================================================================================
      Installing:
       python-matplotlib             x86_64             0.91.2-1.el5.rf             rpmforge             7.1 M
      Installing for dependencies:
       python-dateutil               noarch             1.2-1.el5.rf                rpmforge             164 k
       python-tz                     noarch             2006p-1.el5.rf              rpmforge             667 k
      
      Transaction Summary
      =========================================================================================================
    • pylabを使用する場合、numpyモジュールも必要
      testuser@testserver07% sudo yum install -y python-numpy.x86_64 --disablerepo=base
      
      =========================================================================================================
       Package                   Arch                Version                       Repository             Size
      =========================================================================================================
      Installing:
       python-numpy              x86_64              1.0.1-1.el5.rf                rpmforge              2.5 M
      Installing for dependencies:
       blas                      x86_64              3.0-37.el5                    core-2                318 k
       lapack                    x86_64              3.0-37.el5                    core-2                3.5 M
      
      Transaction Summary
      =========================================================================================================

画像をファイルに落とすサンプル

  • python 2.4
    #!/usr/bin/python
    
    import matplotlib
    
    matplotlib.use('Agg')
    
    import pylab
    
    pylab.title("Title")
    pylab.figure(figsize=(10, 1.7))
    pylab.plot( [ 1, 2, 3, 4,2,12] )
    pylab.ylim(-1, 13)
    pylab.grid(True)
    pylab.savefig('/tmp/aaa.png')
    pylab.close()

棒グラフ

Webアプリ作成

Flask

WSGI

時刻系

  • python 2.6

エポックタイム取得

  • 小数点第2位までミリ秒がとれる模様
    #!/usr/bin/python
    
    import time
    
    print time.time()
    
    # 結果 1447294620.45

Timestamp <--> EpochTime?

#!/usr/bin/python

import time
from datetime import datetime

def datetime_to_epoch(d):
    return int(time.mktime(d.timetuple()))


def epoch_to_datetime(epoch):
    return datetime(*time.localtime(epoch)[:6])


now = datetime.now()
epoch = datetime_to_epoch(now)
epoch_to_datetime(epoch)

文字列 --> 日時

#!/usr/bin/python

import time
from datetime import datetime

tstr = "1980-01-01 00:00:01"
tdatetime = datetime.strptime(tstr, '%Y-%m-%d %H:%M:%S')
  • python 2.4の場合、datetime.strptimeがないので、time.strptimeを、datetimeコンストラクタの引数に入力することで実現可能(*は可変長引数の意)
    #!/usr/bin/python
    
    import time
    from datetime import datetime
    
    tstr = "1980-01-01 00:00:01"
    tdatetime = datetime(*time.strptime(tstr, '%Y-%m-%d %H:%M:%S')[0:6])

日時⇒文字列

import datetime

now = datetime.datetime.now()
now.strftime("%Y/%m/%d %H:%M:%S")

日付計算(月単位)

PythonでProtocolBuffers?

IDLのコンパイル

  • 構成
    44maru@silver02% tree         
    .
    |-- test1
    |   `-- Test1.proto
    `-- test2
        `-- Test2.proto
  • コンパイル用シェル
    #!/bin/bash
    
    # need to protoc version 2.6
    
    OUT_PATH=./output
    PROTO_PATH="."
    PROTO_FILES="${PROTO_PATH}/test1/Test1.proto ${PROTO_PATH}/test2/Test2.proto"
    
    mkdir -p $OUT_PATH
    protoc -I${PROTO_PATH}/test1 -I${PROTO_PATH}/test2 --python_out=${OUT_PATH} ${PROTO_FILES}
  • output
    44maru@silver02% ls ./output
    Test1_pb2.py  Test1_pb2.pyc  Test2_pb2.py  Test2_pb2.pyc

ソース例

  • TESTDBのProtobuff格納テーブルからPBTestReq?を1件取得して表示させる
    #!/usr/bin/python
    
    import Test1_pb2
    import cx_Oracle 
    
    db_user = "TESTUSER"
    db_pass = db_user
    db_sid = "TESTDB"
    
    connection_url = "%s/%s@%s" % (db_user, db_pass, db_sid)
    connection = cx_Oracle.connect(connection_url)
    cursor = connection.cursor()
    try:
        pbReq = Test1_pb2.PBTestReq()
        sql = "select pb_lob_data from pb_tab where rownum = 1"
        for r in cursor.execute(sql):
            pbReq.ParseFromString(r[0].read())
            print pbReq
    finally:
        cursor.close()
        connection.close()
  • PB_INSTANCE.ParseFromString?(PB_SERIALIZE_DATA)が、JavaでいうparseFrom()にあたるが、Javaと違って戻り値はなく、生成したProtoBuff?のオブジェクトでParseすることによって、値がsetされる。

Excel 編集

  • http://www.python-excel.org/
  • sample
    from xlrd import open_workbook
    import sys
    
    TABLE_SHEET_NAME = "List of sheets"
    TABLE_TYPE_COL_INDEX = 0
    TABLE_NAME_COL_INDEX = 1
    TABLE_DESCRIPTION_COL_INDEX = 2
    
    def open_table_sheet(excel_sheet_path):
       workbook = open_workbook(excel_sheet_path)
       sheet = workbook.sheet_by_name(TABLE_SHEET_NAME)
       return sheet
    
    def write_table_name_to(sheet):
       for row in range(sheet.nrows):
           table_type = sheet.cell(row, TABLE_TYPE_COL_INDEX).value
           if table_type == "Table":
               table_name = sheet.cell(row, TABLE_NAME_COL_INDEX).value
               # if table_name == '44maru':
               # FIXME! using xlwt
               sheet.write(row, TABLE_DESCRIPTION_COL_INDEX, '44maru-machi-desu')
    
    def main():
       excel_sheet_path = sys.argv[1]
       sheet = open_table_sheet(excel_sheet_path)
       write_table_name_to(sheet)
    
    main()

XlsxWriter?

  • 先述のxlwtだと、xlsフォーマットで、行数が65535までしか対応していない。(それ以上書き込もうとするとエラーになる)
  • XlsxWriter?であれば、xlsxフォーマットを利用でき、かつAPIも使用しやすい

vim plug-in

おすすめ

jedi-vim

  • コード補完プラグイン
  • python-2.6以上(RHEL6)
    • jedi-vimプラグインの他に、jedi libraryが別途必要
      [root@testserver07 ~]# yum install -y python-pip.noarch
      [root@testserver07 ~]# export http_proxy=http://192.168.22.118:3128/
      [root@testserver07 ~]# export https_proxy=http://192.168.22.118:3128/
      [root@testserver07 ~]# pip install jedi

pyflakes-vim

  • コード(文法)解析プラグイン
  • vim pluginのほかに以下もpipでインストールする
    [root@testserver07 ~]# export https_proxy=http://192.168.22.118:3128/
    [root@testserver07 ~]# pip install pyflakes
    Downloading/unpacking pyflakes
      Downloading pyflakes-0.7.3.tar.gz
      Running setup.py egg_info for package pyflakes
    Installing collected packages: pyflakes
      Running setup.py install for pyflakes
        Installing pyflakes script to /usr/bin
    Successfully installed pyflakes
    Cleaning up...

IDE

optparse

#!/usr/bin/python
  
""" 
super 44maru parser script
"""

import optparse

class CLIParser:
    def __init__(self):
        self.parser = optparse.OptionParser()
        self.parser.set_description(__doc__)
        self.parser.set_usage('44maru.py [options] *.txt')
        self._setup_options()

    def parse_cli(self):
        (options, args) = self._parse_args()
        self._check_required_options_exist(options)
        return (options, args)

    def _parse_args(self):
        return self.parser.parse_args()

    def _setup_options(self):
        self.parser.add_option('-n', '--44maru', help='required option. 44maru', dest='44maru')
        self.parser.add_option('-c', '--christmas', help='required option. christmas', dest='christmas')
        self.parser.add_option('-t', '--test', help='test option yeah', dest='test')

    def _check_required_options_exist(self, opts):
        mandatories = ['44maru', 'christmas']
        for m in mandatories:
            if not opts.__dict__[m]:
                self.parser.error(m + " is required option")
                exit(-1)

if __name__ == '__main__' :
    parser = CLIParser()
    (options, args) = parser.parse_cli()
    print options