投稿者「python-tksssk」のアーカイブ

propertyの作成

Pythonのクラスでは属性に直接読み書きができる。

class MyClass(object):
    def __init__(self):
        self.myattr = 1

my = MyClass()
print(my.myattr)      #  1

@propertyデコレータで、読み取り専用のプロパティが作成できる。

class MyClass(object):
    def __init__(self):
        self.myattr = 1
    @property
    def en_myattr(self):
        return ['zero', 'one', 
                'two', 'three'][self.myattr]

my = MyClass()
my.en_myattr    # 'one'
my.myattr=5
my.en_myattr   #'five'

@property名.setterで書き込み可能なプロパティが作成できる。

class MyClass(object):
    def __init__(self):
        self.myattr = 1
    @property
    def en_myattr(self):
        return ['zero', 'one',
                'two', 'three'][self.myattr]

    @en_myattr.setter
    def en_myattr(self, val):
        self.myattr = ['zero', 'one',
                        'two', 'three'].index(val)

my = MyClass()
print(my.en_myattr)     # one

my.en_myattr = 'two'
print(my.en_myattr)     # two
print(my.myattr)        # 2

ファイルのコピーやディレクトリツリーの作成・削除 (高レベルなファイル操作)

ファイルのコピーは、コピー元ファイル、コピー先ファイルをopenして、コピー元を読み込みながら、コピー先に書き込めばできるが、もっとシンプルに元と先のファイルパスだけ指定してコピーしたい場合は、shutilモジュールのcopyfileを使うことでできる。

shutilモジュールには、高レベルなファイル操作が用意されている。OSのシェルコマンドを使えば簡単な操作だが、プログラムで実装すると手間な処理を簡単に実装できる。(OSのシェルコマンドは使用しないので環境依存しない)

ディレクトリをツリーごと作成

>>> import os
>>> os.makedirs('/tmp/foo/bar/hoge')
# /tmp/foo/bar/hoge のディレクトリツリーを作成。
# 親ディレクトリがなければ作成)

ファイルのコピー(shutil.copy)

>>> import shutil
>>> shutil.copy("/tmp/src", "/foo/dst")

# /tmp/srcを /foo/dst にコピー

ディレクトリまるごとコピー(shutil.copytree)

>>> import shutil
>>> shutil.copytree("/tmp/srcdir", "/foo")    
# /tmp/srcdirの配下のファイルも含めて
# まるごと/fooにコピーする

ディレクトリまるごと削除(shutil.rmtree)

os.rmdirでは、ディレクトリ内にファイルが存在する場合に削除できないが、shutil.rmtreeは削除できる。

>>> import shutil
>>> shutil.rmtree("/tmp/foo")
# /tmp/foo ディレクトリツリー全体を削除

ファイルの移動(shutil.move)

>>> import shutil
>>> shutil.move("/tmp/src", "/tmp/dst")
# /tmp/srcを/tmp/dstに移動

参考:
16.1.5. ファイルとディレクトリ

11.10. shutil — 高レベルなファイル操作

環境変数の取得

os.environで環境変数の辞書を取得。

>>> import os
>>> print( os.environ['LANG'] )
ja_JP.UTF-8

辞書操作なので、itemsで全要素を操作可能。

>>> import os
>>> for k,v in os.environ.items():
>>>    print(k + "=" + v)
LANG=ja_JP.UTF-8
....

組み合わせや順列の列挙に便利なitertoolsの機能

確率や統計だったり、全パターン列挙などをする場合に便利な関数群を一部紹介。
これらの関数を使うことで複数のネストしたループを作らなくてすんで大変便利です。

組み合わせ combinations

例:5枚の数字カード[0..4]から3枚引いた時の取れるカードの組み合わせ。順番は関係なし

>>> import itertools
>>> list(itertools.combinations(range(5), 3))
[(0, 1, 2), (0, 1, 3), (0, 1, 4), (0, 2, 3), (0, 2, 4), 
  (0, 3, 4), (1, 2, 3), (1, 2, 4), (1, 3, 4), (2, 3, 4)]

順列 permutations

例:4枚の数字カード[0..3]の並び順のパターン。

>>> import itertools
>>> list(itertools.permutations(range(4)))
[(0, 1, 2, 3), (0, 1, 3, 2), (0, 2, 1, 3), (0, 2, 3, 1), 
....
(3, 1, 0, 2), (3, 1, 2, 0), (3, 2, 0, 1), (3, 2, 1, 0)]   #24パターン

例:4枚の数字カードから2枚取る場合の並び順のパターン。

>>> list(itertools.permutations(range(4), 2))
[(0, 1), (0, 2), ... (3, 2)]  #12パターン

直積 product

デカルト積。for ループの入れ子をせずに全パターンを網羅するときに便利。

例:剣はw1, w2, w3の3種類。盾はs1,s2,s3,s4の4種類。鎧はa1, a2の2種類。この3つの装備の全パターンを網羅。

>>> import itertools
>>> swords=['w1', 'w2', 'w3']
>>> shields=['s1','s2','s3','s4']
>>> armors=['a1', 'a2']
>>> list(itertools.product(swords, shields, armors))
[('w1', 's1', 'a1'), ('w1', 's1', 'a2'), ('w1', 's2', 'a1'),
...
, ('w3', 's4', 'a2')]   #24パターン

例:サイコロを4回ふった時の出目の全パターン
repeatを使うと、集合に対する直積を指定していることになる。下記の例でいうと、
itertools.product(range(1,7), repeat=4)と、
itertools.product(range(1,7), range(1,7), range(1,7), range(1,7))
は同じ意味になる。

>>> list(itertools.product(range(1,7), repeat=4))
[(1, 1, 1, 1), (1, 1, 1, 2), (1, 1, 1, 3), (1, 1, 1, 4), (1, 1, 1, 5), 
...
  , (6, 6, 6, 5), (6, 6, 6, 6)]  #1296パターン

他にもたくさんあるので公式ドキュメントを参考に。
10.1. itertools — 効率的なループ実行のためのイテレータ生成関数

dateオブジェクトから時間が切り捨てられたdatetimeオブジェクトを取得する

dateオブジェクトは、datetimeオブジェクトと比べると時間の情報がないわけですが、datetimeオブジェクトでありながらhour,min,secなどの時刻属性が0で切り捨てられた値が欲しい時があります。

dateオブジェクトとdatetimeオブジェクトは似ていますが、違うオブジェクトなので大小比較できなかったりするので、そういう時にこまります。

timetupleから生成

datetimeオブジェクト生成時の年月日の引数を、dateオブジェクトのtimetupleから生成する方法です。

>>> import datetime

>>> date_object = datetime.date.today()   #datetime.date(2014, 11, 12)

>>> datetime.datetime(*date_object.timetuple()[:3])
>>> datetime.datetime(2014, 11, 12, 0, 0)   #時刻切り捨てのdatetime

date_object.timetuple()[:3]で、(year, month, day)のタプルが得られるので、それを引数に展開して生成しています。

時刻要素をreplace

別の方法として、dateオブジェクトからreplaceで時刻要素を0にする例です。

>>> import datetime

>>> date_object = datetime.date.today()   #datetime.date(2014, 11, 12)

>>> date_object.replace(hour=0, minute=0, second=0, microsecond=0)
>>> datetime.datetime(2014, 11, 12, 0, 0)   #時刻切り捨てのdatetime

zipファイルの読み書き

Zipファイルの作成

※複数のシーケンスをまとめて操作するzip関数についてはこちらを参照

PythonプログラムでZipファイルを作成するには、zipfile.ZipFileオブジェクトを作成する。

>>> import zipfile
>>> with zipfile.ZipFile('foo.zip', "w", zipfile.ZIP_DEFLATED) as zf:
>>>     zf.write("bar.txt", "bar2.txt") 

zipfileオブジェクトのwrite関数は、既存のファイルをzipファイルの中にアーカイブする関数。上の例では、ファイルシステム上に存在するbar.txtをzipファイルの中でのbar2.txtとしてzipファイルに含める。

zipファイルの中のフォルダに含める場合は、パス構造としてファイル名を記述するだけでよい。

>>>     zf.write("bar.txt", "hoge/bar3.txt") 

ファイルシステムに存在するファイルではなく、ファイルの内容そのものをpythonから書き込む場合には、writestrを使う。

>>> with zipfile.ZipFile('foo.zip', "w", zipfile.ZIP_DEFLATED) as zf:
>>>     zf.writestr("bar4.txt", "Hello World")

zipファイルの中にbar4.txtというファイルを作成し、ファイルの中身には”Hello World”というテキストを書き込む。

Zipファイルの読み込み

zipfile.ZipFileオブジェクトを読み込みモードで作成し、そこからZipファイル内のファイル名でopenする。あとは通常のファイル操作と同じ。

>>> with zipfile.ZipFile('foo.zip', 'r') as zf:
...     with zf.open('bar4.txt') as bar4:
...         print bar4.read()
...         
Hello World

ファイルを展開する場合には、extract,
extractallを使用する。

>>> with zipfile.ZipFile('foo.zip', 'r') as zf:
...     zf.extractall()

ファイルのタイムスタンプ取得

ファイルのタイムスタンプの取得にはos.statを使ってstat構造体を取得し、st_mtime属性から得られる。

>>> os.stat('test.txt').st_mtime
1393290856.9768713

datetimeオブジェクトへの変換には、datetime.datetime.fromtimestampを使用する

>>> dt = datetime.datetime.fromtimestamp(os.stat('/tmp/tmpIpf8wd').st_mtime)
>>> dt
datetime.datetime(2014, 2, 25, 10, 14, 16, 976871)

>>> dt.strftime('%Y-%m-%d')  #datetime->文字列
'2014-02-25'

一時ファイル/一時ディレクトリの作成

一時ファイルの作成は、tempfileモジュールを使う。

tempfile.mkstempで一時ファイルが作成される。

この関数で作成した一時ファイルは作成したプログラムの責任で削除されなければならない。ファイルを閉じた時に自動削除されるようにするには。tempfile.TemporaryFileクラスを使用する。

>>> temp = tempfile.mkstemp()
>>> temp
(3, '/tmp/tmpIpf8wd')         # file_noとファイルパスのタプル

tempfile.mkdtempでは一時ディレクトリが作成できる。

loggingで例外情報を出力する

logging.errorで例外を出力しようとしたときに、例外オブジェクトを渡してもスタックトレースは出力されない。

Javaなんかだと、例外オブジェクトをerrorメソッドの引数として渡すが、pythonの場合はexceptionでログ出力する。

try:
  ...
except Exception, e:
    logging.exception('エラーが起きた')

logging.errorメソッドでもexc_info=Trueをつければ同じように出力される。それを簡略化したのがlogging.exceptionメソッド。

try:
  ...
except Exception, e:
    logging.error('エラーがおきた', exc_info=True)

tracebackモジュールのformat_exception(exc_type, exc_value, exc_traceback)を使って自前でスタックトレースを作成することもできるが、それは特に整形にい観があるとき以外は無駄な処理なのでやめるべき。

ライブラリの配布について

Pythonのパッケージ管理はいろいろと変遷していて、なにがベストなのかよく分かりません……。

パッケージ管理ツールのいろいろ

とりあえずパッケージ管理の仕組みについてキーワードを上げてみると、

  • distutils (python標準だが機能不足。コマンドはなかった)
  • setuptools (distutilsの置き換え。コマンドeasy_install)
  • distribute (setuptoolsの機能強化クローン。setuptoolにマージされたことでdeprecatedになった)
  • pip (easy_installの便利コマンド版)
  • distutils2 (setuptools/distutilの仕組みを取り入れた→packagingに変更)
  • packaging (python3.3の標準でsetuptools, easy_install相当だったがやっぱりpython3.3には入らなかった)
  • distlib (python3.4に入る予定?)

らしい。間違っていたら指摘してください。

「distutilsは機能不足なので、setuptoolを使う」
→「setuptoolsはメンテされてないのでpython3なんかを考慮するとdistributeを使うべき」
→「distributeはsetuptoolsに結局マージされたからやっぱりsetuptoolsを使うべき」
→「python標準で運用されるべきだからdistuitls2/packaging使うべき」
→「packagingは削除された」

みたいな流れになっているように思う。

distributeはもうsetuptoolsにマージされてdeprecatedになったので使うべきではなくて、distutils2/packagingは公式として期待されていたもののこれも削除されてしまい使わないほうが良さそうなので、python2.7環境で安定して使えそうなのはsetuptoolsかと思う。(将来的にはdistlibなんでしょうか)

配布用のsetup.pyを記述する

Pythonはライブラリ配布用にsetup.pyというスクリプトを記述する。これは定義ファイルではなくて、これ自体がpythonのスクリプトになっている。スクリプト記述用のツールライブラリとして先ほど上げたようなsetuptoolsのようなパッケージがあるので、定義ファイルのようにスクリプトを記述できる。

配布物としてはpythonソースコード以外のビルド成果物など詳細な記述ができるが、ここでは最も簡単なソースコード配布物について記述する。

特定のフォルダにsrcというソース用ディレクトリがあるとする。

 ./
 ├── setup.py
 ├── src
     ├── __init__.py
     ├── foo.py
     └── hoge
         ├── __init__.py
         └── bar.py

このfooモジュール,hogeパッケージのbarモジュールを配布を考える。

python標準のdistutilsでの記述

distutilsよりはsetuptoolsを使ったほうが多機能なのだが、まずは公式ライブラリとしてのdistutilsについて説明する。
setup.pyは以下のようになる。

from distutils.core import setup

setup(name='foo',
        version='1.0',
        packages = ["hoge"],
        py_modules = ["foo"],
        package_dir = {'': 'src'}
    )

記述内容は最小限にしてあるので詳細は公式ドキュメントを参照。
http://docs.python.jp/2/distutils/introduction.html

packagesではパッケージを指定することでそのパッケージ内のすべてのモジュールをインストール対象にする。パッケージのないモジュールや小さいモジュールであれば、py_modulesでモジュールそのものを指定する。

今回の例であればpackagesの指定はなくして、pymodules=["foo", "hoge.bar"]と記述してもよい。パッケージ内に大量にモジュールがある場合はpackagesで指定する。

packageを取得するときにsrcフォルダをルートとして探す必要があるので、package_dirで検索位置を指定してある。「特定のパッケージ: このディレクトリ」というように複数の指定ができる。

これでソースコード配布物を生成するには、

$ python setup.py sdist

とすることで、distフォルダにfoo-1.0.tar.gzが生成さる。配布物を受け取ってインストールする側は、

$ gunzip -c foo-1.0.tar.gz | tar xf -    # unpacks into directory foo-1.0
$ cd foo-1.0
$ python setup.py install

のように展開後に同じsetup.pyを使ってinstallコマンドを実行する。

インストールはpythonのlib/site-packagesに入るので、システムpythonを使っていると書き込み権限がない可能性がある。virtualenvで仮想環境を作るとよい。

配布の方法については、「配布物をどうにかして渡す」「PyPIに登録する」「自前のPyPIを作成してそこに登録する」などが考えられるが、pipを使ってインストールする前提で、「リポジトリから直接インストールする」という方法を後述する。

setuptoolsでの記述

setuptoolsを使うことで、拡張された記述や実行スクリプトを登録したりできる。今回のモジュールではそれほどdistutilsに比べた差はないが、詳しくはドキュメントを参照。http://pythonhosted.org//setuptools/

setuptoolsではtestを実行することもできるので、srcディレクトリと同じ階層にtestディレクトリとコードを用意した。

 ./
 ├── setup.py
 ├── src
 │   ├── __init__.py
 │   ├── foo.py
 │   └── hoge
 │       ├── __init__.py
 │       └── bar.py
 └── test
     └── foo_test.py

foo_test.pyはfooモジュールのテストをする。

import foo
import unittest

class FooTest(unittest.TestCase):
    def test_hello(self):
        pass  #test code.

def suite():
    suite = unittest.TestSuite()
    suite.addTests(unittest.makeSuite(FooTest))
    return suite

setup.pyはdistuitlsから少し拡張した指定ができるようになる。

from setuptools import setup, find_packages
import sys

sys.path.append('./test')

setup(name='foo',
        version='1.0',
        packages = find_packages("src"),
        py_modules = ["foo"],
        test_suite = "foo_test.suite",
        package_dir = {'': 'src'}
    )

packagesでパッケージを列挙するのが面倒な場合は、find_packagesを使って、フォルダを指定して勝手に検索さえることができる。srcフォルダをルートしたが直下を検索する場合は引数指定は不要。除外条件なども指定ができる。
https://setuptools.readthedocs.io/en/latest/setuptools.html#using-find-packages

配布物の生成は、distutilsと同じように、python setup.py sdistで生成できる。

test_suiteはテストSuiteの指定になる。テストコードはtestディレクトリになるので、sys.pathに追加してある。

テストを実行する場合は、

$ python setup.py test

で実行できる。

配布の方法

setup.pyを記述して配布物を生成し、PyPIにアップして公開・配布するのが正当な方法であると思うが、社内ライブラリなど簡便に配布する仕組みを考えた場合にgitなどのvcs管理下からインストールする楽なので、ここではその方法を紹介する。

gitに登録されたプロジェクトからpip経由でインストールする。(この場合setup.pyで生成された配布成果物は使用しない)

gitリポジトリ http://git.example.com/my-library.git に登録されている場合、pipコマンドでインストールできる。

$ pip install git+http://git.example.com/my-library.git#egg=my-library

gitリポジトリ直下にsetup.pyが存在する必要がある。tagやbranchを指定することで特定のバージョンを取得するような設定も可能。

$ pip install git+http://git.example.com/my-library.git@mybranch#egg=my-library

詳細はドキュメント参照。http://www.pip-installer.org/en/latest/logic.html#vcs-support (ただ、subdiretoryの指定はよく分からなかった)

pythonでの環境構築で記載したようにrequirements.txtにも記載できる。
「社内ライブラリを作成」→「gitに登録」→「ライブラリを利用するモジュールで依存関係に追加」という利用の仕方が可能になる。