LoginSignup
16
21

More than 5 years have passed since last update.

ざっくりPythonのおさらい その1。基本文法など

Last updated at Posted at 2018-08-27

1年ぶりぐらいにPythonを業務で使うことになったので復習してます。

[ざっくりPythonのおさらい その1。基本文法など]
https://qiita.com/48hands/items/31b7ac2b49addbb8658c

[ざっくりPythonのおさらい その2。おもにデータ保存]
https://qiita.com/48hands/items/ab86b7463268e669d216

[ざっくりPythonのおさらい その3。Numpy, Pandas]
https://qiita.com/48hands/items/b1a71000533e129b27f2

復習に使った教材は、酒井潤さんがUdemyで提供しているこちらの講座です。
Pythonに入門するときに一度通しで勉強したのですが、入門書として私の中ではベストでした。

[講座名]
Python3入門 + 応用 + アメリカのシリコンバレー流コードスタイルを学び、実践的なアプリ開発の準備をする
https://www.udemy.com/python-beginner/

入門〜応用までかなり幅広く網羅しているので、市販で売ってる入門書籍よりも個人的にかなりおすすめです。
一方で他言語を学んでいない初心者には中半〜後半は難しいかもしれません。
説明は非常にわかりやすかったです。

Udemyの割引対象にもなっていた(1200円)ので本を何冊も買うより、こっちの方が実践的な内容を広く学べて割安です。

起動引数を扱う

sysを使った簡単な実装

import sys

print(sys.argv)

for arg in sys.argv:
    print(arg)
$ python index.py ppppp eeeee
['index.py', 'ppppp', 'eeeee']
index.py
ppppp
eeeee

optparserを使った実装

from optparse import OptionParser


def main_process():
    # usageで使い方の説明
    # usageをパーサーに渡すことによって、--helpで表示できる。
    usage = 'usage: %prog [options] arg1 arg2'

    # パーサーの生成
    parser = OptionParser(usage=usage)

    # -f,もしくは--fileで指定するように指定する
    # actionは渡されたものをストアすることを示している
    # typeにstringを指定して文字列を受け取るようにしている
    # destで代入先の変数名をfilenameとしている。
    parser.add_option('-f', '--file', action='store', type='string',
                      dest='filename', help='ファイル名を指定してください。')

    # -n,もしくは--numで指定するように指定する
    # actionは渡されたものをストアすることを示している
    # typeにintを指定して整数を受け取れるようにしている
    # destで代入先の変数名をnumとしている。
    parser.add_option('-n', '--num', action='store', type='int', dest='num')

    # -vを指定すると、verboseにTrueが代入されるようにしている。
    # デフォルト値にFalseを指定している。
    parser.add_option('-v', action='store_true', dest='verbose', default=False)

    # -vを指定すると、verboseにFalseが代入されるようにしている。
    # parser.add_option('-v', action='store_false', dest='verbose')

    # optionsとargsに分けてアンパッキング
    options, args = parser.parse_args()
    print(options)
    print(args)

    # 変数を取得
    print(options.filename)
    print(options.num)
    print(options.verbose)


if __name__ == '__main__':
    main_process()
$ python main_app.py --help

Usage: main_app.py [options] arg1 arg2

Options:
  -h, --help            show this help message and exit
  -f FILENAME, --file=FILENAME
                        ファイル名を指定してください。
  -n NUM, --num=NUM     
  -v     

$ python main_app.py -f test.txt  a b c -n 123456

{'filename': 'test.txt', 'num': 123456, 'verbose': False}
['a', 'b', 'c']
test.txt
123456
False

for文関連のおさらい

for-else文

# breakがない場合はelseが実行される
for fruit in ['apple', 'pen']:
    print(fruit)
else:
    print('I ate all')

# breakがある場合はelseは実行されない
for fruit in ['apple', 'pen']:
    print(fruit)
    break
else:
    print('I ate all')

range関数

# 0から9まで
for i in range(10):
    print(i)

# 2から9まで
for i in range(2,10):
    print(i)

# 2から始まって9まで3スキップ
for i in range(2, 10, 3):
    print(i)

# index番号を使わないときは変数名を`_`にして使わない意思を示す。
for _ in range(10):
    print('hello')

enumerate関数

# index付きで表示したいときに利用する
for i, fruit in enumerate(['apple', 'banaana', 'orange']):
    print(i, fruit)

zip関数

days = ['Mon', 'Tue', 'Wed']
fruits = ['apple', 'banana', 'orange']
drinks = ['coffee', 'tea', 'beer']

# zip関数を使わない場合
for i in range(len(days)):
    print(days[i], fruits[i], drinks[i])

# zip関数を使った場合
for day, fruit, drink in zip(days, fruits, drinks):
    print(day, fruit, drink)

辞書のfor文処理

d = {'x': 100, 'y': 200}
for k, v in d.items():
    print(k, v)

d.items()dict_items([('x', 100), ('y', 200)])を返している。
アンパッキングでkvに渡している。

if文関連のおさらい

Noneの判定

# これでもいいけどあまりよくない
if is_empty == None:
    print('None')

# Noneの判定はこういうのが自然
if is_empty is None:
    print('None')

# Noneでない判定はこういうのが自然
if is_empty is not None:
    print('not None')

関数定義関連

基本

def hello_hanako():
    print("Hello, Hanako!!")

# 実行
hello_hanako()

# 型を調べるとfunction型になっている
print(type(hello_hanako))

# function型なので、こういう使い方もできるっぽい
hanako_func = hello_hanako
hanako_func()
Hello, Hanako!!
<class 'function'>
Hello, Hanako!!

型の定義

def calc_num(a: int, b: int) -> int:
    return a + b

ans = calc_num(10, 20)
print(ans)

デフォルト引数にリストを与えるべきではない

def test_hoge(x , l=[]):
    l.append(x)
    return l

# 返却値として[100]を期待
r = test_hoge(100)
print(r)

# 返却値として[100]を期待しているが..
r = test_hoge(100)
print(r)

参照渡しなのでリストr100が追加されてしまう。

[100]
[100, 100]

位置引数のタプル化

# 位置引数*argsを定義
def say_something(person, *args):
    print('I am ' + person)
    for arg in args:
        print(arg)

say_something('Hanako', 'Taro', 'Shintaro')

# タプルを使って展開もできる
# ただし、あまり使わないらしい。
t = 'Taro', 'Shintaro'
say_something('Hanako', *t)
I am Hanako
Taro
Shintaro
I am Hanako
Taro
Shintaro

キーワード引数

def my_menu(**kwargs):
    print(kwargs)
    for k, v in kwargs.items():
        print(k, v)

# 書き方その1 よく見る書き方。
my_menu(food='unadon', drink='cola')

# 書き方その2 これもよく見る書き方
entry = {
    'food': 'udon',
    'drink': 'water',
    'dessert': 'ice'
}
my_menu(**entry)

引数、位置引数、キーワード引数が混在のパターン

# 引数、位置引数、キーワード引数が混在のパターン
# これもよく見る。
# 引数の定義順も大事。引数、,*args, **kwargsの順でないとNG
def my_menu(food, *args, **kwargs):
    print(food)
    print(args)
    print(kwargs)

my_menu('rice ball', 'water', 'cola', dessert1='pan-cake', dessert2='chocolate ice')
rice ball
('water', 'cola')
{'dessert1': 'pan-cake', 'dessert2': 'chocolate ice'}

inner function(関数内関数)

def outer(a, b):
    # outerの中でしか使わない場合はinner functionがいいこともある。
    def plus(c, d):
        return c + d

    print(plus(a, b))

outer(1, 2)

クロージャー

# クロージャ関数を定義
def square_area_func(length):
    # インナー関数を定義
    def calc(scale):
        return length * length * scale

    return calc # calc()を返すのではなくてインナー関数calcを返す

cal1 = square_area_func(6)
cal2 = square_area_func(7)

# 遅延評価する。ここで初めて計算が実行される
result1 = cal1(1.5) # 6 * 6 * 1.5
result2 = cal2(2.0) # 7 * 7 * 2.0
print(result1)
print(result2)
54.0
98.0

デコレータ

少しむずかしい方法

def add_num(a, b):
    """
    デコレーション対象のメソッド
    """
    return a + b


def print_info(func):
    """
    デコレータの定義
    :param func: デコレーション対象のメソッド
    :return:
    """

    # インナー関数を定義
    def wrapper(*args, **kwargs):
        print("Start..")  # デコレーション(前処理)
        result = func(*args, **kwargs)  # 計算処理
        print("End..")  # デコレーション(後処理)
        return result

    return wrapper  # 関数として返す


deco_f = print_info(add_num)
r = deco_f(10, 30)
print(r)
Start..
End..
40

上記をすこし簡単にした方法

デコレーション対象のメソッド(add_num)定義前に@デコレータメソッド(print_info)をつけてあげる。

デコレータメソッドの定義方法は変わらないが、使い方が簡単になる。

def print_info(func):
    """
    デコレータの定義
    :param func: デコレーション対象のメソッド
    :return:
    """

    # インナー関数を定義
    def wrapper(*args, **kwargs):
        print("Start..")  # デコレーション(前処理)
        result = func(*args, **kwargs)  # 計算処理
        print("End..")  # デコレーション(後処理)
        return result

    return wrapper  # 関数として返す


@print_info
def add_num(a, b):
    """
    デコレーション対象のメソッド
    """
    return a + b


# デコレーション対象のメソッドを呼び出すのみでデコレーションしてくれる
r = add_num(10, 30)
print(r)
Start..
End..
40

デコレータ自体の定義方法には慣れが必要なので、使い所など含めて要練習。

ラムダ

def capitalize_words(words, func):
    for word in words:
        print(func(word))


words = ['hanako', 'taro', 'kurara', 'haiji']
capitalize_words(words, lambda w: w.capitalize())
capitalize_words(words, lambda w: w.swapcase())

ジェネレータ

def foods():
    yield "Rice ball"
    yield "Udon"
    yield "Soba"


itr = foods()
print(itr)
print(next(itr))
print(next(itr))
print(next(itr))


def i_love_python(num=5):
    for _ in range(num):
        yield "I love python."


itr2 = i_love_python(3)
print(itr2)
print(next(itr2))
print(next(itr2))
print(next(itr2))
<generator object foods at 0x10598b1a8>
Rice ball
Udon
Soba
<generator object i_love_python at 0x10598b200>
I love python.
I love python.
I love python.

内包表記

内包表記を使うと簡潔になるが、複雑にしすぎるとわかりにくくなるので、適材適所で利用する。

リスト内包表記

# 基本
foods = ("ハンバーグ", "おにぎり", "ラーメン", "うどん")
foods_list = [food for food in foods]
print(foods_list)

# ifを挟む場合
numbers = (100, 10000, 33333, 5501)
even_number_list = [number for number in numbers if number % 2 == 0]
print(even_number_list)

# リスト同士の掛け算
num1 = [1, 2, 3, 4, 5]
num2 = [5, 6, 7, 8, 9]
mul_num = [i * j for i in num1 for j in num2]
print(mul_num)
['ハンバーグ', 'おにぎり', 'ラーメン', 'うどん']
[100, 10000]
[5, 6, 7, 8, 9, 10, 12, 14, 16, 18, 15, 18, 21, 24, 27, 20, 24, 28, 32, 36, 25, 30, 35, 40, 45]

辞書内包表記

week = ['mon', 'tue', 'wed']
drinks = ['coffee', 'milk', 'cola']

# 内包表記を使わない記述
d = {}
for day, drink in zip(week, drinks):
    d[day] = drink
print(d)

# 内包表記を使った記述
d = {day: drink for day, drink in zip(week, drinks)}
print(d)

集合内包表記

# 内包表記を使わない記述
s = set()
for i in range(10):
    s.add(i)

print(s)

# 内包表記を使った記述
s = {i for i in range(10)}
print(s)

ジェネレータ内包表記

# 内包表記を使わない記述
def generator():
    for _ in range(5):
        yield "Hello Andaconda!"

g = generator()
print(type(g))

# 内包表記を使った記述
g = ("Hello Miniconda" for _ in range(10))
print(type(g))

# 内包表記で生成したジェネレータからタプルを生成する
t = tuple("Hello Miniconda" for _ in range(10))
print(t)

# 内包表記で生成したジェネレータからリストを生成する
l = list("Hello Miniconda" for _ in range(10))
print(l)

例外処理

基本

try~except~finallyで記述する。

try:
    print(l[4])

# インデックスエラー例外
except IndexError as ex:
    print("Index Error!!", ex)

# Exceptionを捕捉する。
# これ以上のものは捕捉するべきでない。
except Exception as ex:
    print("まるっと例外を捕捉するのはあまりよくない", ex)

# 例外が発生しなかった場合に実行される
else:
    print("例外が発生しなかった場合に実行")

# 例外が発生しても必ず実行させたい処理
finally:
    print('finish!')

例外のクラス階層は以下参照

他言語と同様、例外を発生させるときはraise

raise IndexError('Index errorを発生させました')

独自例外定義

# 独自例外定義
class MyApplicationException1(Exception):
    pass

# 独自例外定義
class MyApplicationException2(Exception):
    pass


def check(words):
    for word in words:
        if word.isupper():
            # 例外を発生させる
            raise MyApplicationException1(word)
        if word.isdigit():
            # 例外を発生させる
            raise MyApplicationException2(word)


words = ['APPLE', 'orange', 'bananaa']

try:
    check(words)
except MyApplicationException as ex:
    print("あああああああ", ex)
except YourApplicationException as ex:
    print("いいいいいいい", ex)

クラス

基本

以下のselfはオブジェクトを指している。
personを示すことになる。

class Person(object):  # python2の名残でobjectを書いた方がいいらしい
    # クラス変数
    # すべてのオブジェクトで共有してもいいもののみに利用する。
    kind = "人間"

    def __init__(self, name):
        """
        これはコンストラクタ
        """
        print("init..")
        self.name = name

    def say_hello(self):
        """
        これはインスタンスメソッド
        """
        print('Hello, {}'.format(self.name))

    @staticmethod
    def run():
        """
        これはスタティックメソッド
        selfを使用しない。
        """
        print('run run run')

    def __del__(self):
        """
        デストラクタ
        オブジェクトが使われなくなったら呼ばれる
        """
        print('good bye')


person = Person('Hanako')  # new演算子なるものはない。
person.say_hello()

# 明示的にデストラクタを呼ぶ場合
del person

オーバーライド/superによる呼び出し

class Car(object):
    def __init__(self, model=None):
        self.model = model

    def run(self):
        print('run')


class SuperCar(Car):
    def run(self):
        # オーバライド
        print('super run')


class UltraCar(Car):
    def run(self):
        # 親クラスのrunを呼び出し
        super().run()
        print('ultra run')


car = Car()
car.run()

super_car = SuperCar()
super_car.run()

ultra_car = UltraCar()
ultra_car.run()
run
super run
run
ultra run

プロパティを使った属性設定

プロパティを指定してゲッターとセッターを定義するのがいいらしい。プロパティをつけると、わかりやすくなるが少し手間。

class Person(object):
    def __init__(self, name, age):
        # プライベートな属性には慣習的に_を先頭につける
        # ただし、_ひとつだけだと外からもアクセスできるので、きちんと属性を保護したいのであれば、
        # __として2つアンダースコアをつけるようにする
        self._name = name
        self._age = age

    @property
    def name(self):
        """
        ゲッター
        """
        return self._name

    @property
    def age(self):
        """
        ゲッター
        """
        return self._age

    @name.setter
    def name(self, name):
        """
        セッター
        """
        self._name = name

    @age.setter
    def age(self, age):
        """
        セッター
        """
        self._age = age



person = Person("Hanako Yamada", 32)

print(person.name) # propertyがついているので、person.name()としなくていい。
print(person.age) # propertyがついているので、person.age()としなくていい。

person.name = 'Sakamichi Onoda' # セッターを使っている
person.age = 16 # セッターを使っている

print(person.name)
print(person.age)

抽象クラス

抽象クラスを使って、子クラスでメソッドの実装を忘れないようにさせたい場合に利用する。

Pythonでの抽象クラスの利用はあまり推奨されていないらしい。

# abcをインポートする
import abc

class Person(metaclass=abc.ABCMeta):
    """
    抽象クラス
    """
    def __init__(self, age=None):
        self.age = age

    @abc.abstractmethod
    def walk(self):
        """
        @abc.abstractmethodで子クラスに実装させるようにする
        """
        pass

class Baby(Person):
    def __init__(self):
        self.age = 1

    # このメソッドは実装されていないといけない
    def walk(self):
        print("はいはい")


class Adult(Person):
    def __init__(self):
        self.age = 20

    # このメソッドは実装されていないといけない
    def walk(self):
        print("るんるん")

多重継承

# 人間クラスの定義
class Human(object):
    def talk(self):
        print("ほげほげほげ")

    def walk(self):
        print("てくてく")

# 鳥クラスの定義
class Bird(object):
    def fly(self):
        print("とんでる")

    def walk(self):
        print("ぺたぺた")


# 鳥人間クラスの定義
# 多重継承で定義
class BirdHuman(Human, Bird):
    pass

bird_human = BirdHuman()
bird_human.fly()
bird_human.talk()

# 同じメソッドがある場合、先に継承したクラスのメソッドが呼ばれる
bird_human.walk() 

クラスメソッド

クラス変数を参照するメソッド
@classmethodをつける。

class Bird(object):
    # クラス変数
    kind = "鳥"

    def __init__(self):
        pass

    @classmethod
    def what_is_kind(cls):
        # clsは、Birdを指している。
        # Birdクラスから生成されたインスタンスオブジェクトではないことに注意
        print(cls.kind)

# クラスメソッドの実行
# インスタンス化しなくても使えるので、Bird()ではなく、Birdになっている
Bird.what_is_kind()

スタティックメソッド

クラス変数もインスタンス変数も参照せず、インスタンス生成せずに利用できるメソッド。
オブジェクトの状態とかに依存せず、util系のメソッドとかを定義するときとかに使う。@staticmethodをつける。

class Bird(object):

    def __init__(self):
        pass

    @staticmethod
    def has_leg():
        print("2本足です")


# スタティックメソッドを利用する
Bird.has_leg()

特殊メソッド

メソッド名の前後に__がつくメソッド
コンストラクタ、デストラクタをの__init____del__も特殊メソッド。

特殊メソッドはいろいろあるが、一番良く使うのが__str__メソッド。

class Word(object):
    def __init__(self, text):
        self.text = text

    def __str__(self):
        return "word word word"

    def __len__(self):
        return len(self.text)

    def __add__(self, other):
        return self.text + " is " + other.text

    def __eq__(self, other):
        return self.text == other.text


word = Word("Sunday Magazine Jump")
other_word = Word("週刊少年誌")
other_word2 = Word("Sunday Magazine Jump")

# __str__メソッドが呼び出される
print(word)

# __len__メソッドが呼び出される
print(len(word))

# __add__メソッドが呼び出される
print(word + other_word)

# __eq__メソッドが呼び出される
print(word == other_word)
print(word == other_word2)

ファイル読み書き

ファイルの書き込み

withステートメントを使わない書き方

f = open('test.txt', 'w')
f.write('Test')
f.close()

withステートメントを使った書き方

ファイルの閉じ忘れがないように基本的には、withステートメントを使うようにする。

with open('test.txt', 'w') as f:
    f.write('Test\n')
  • w: ファイルの新規作成、存在する場合は上書き
  • w+: ファイルの書き込み+読み込み
  • a: ファイルが既に存在する場合は追記、存在しない場合はファイルの新規作成
  • a+: ファイルの書き込み+読み込み。ファイルが既に存在する場合は追記、存在しない場合はファイルの新規作成

ファイルの読み込み

read()でファイルの内容をすべて読み込む。
readline()でファイルの内容を一行ずつ読み込む。

with open('test.txt', 'r') as f:
    # f.reaad()ですべて読み込み
    print(f.read()) 
    while True:
        # f.readline()で一行ずつ読み込み
        line = f.readline() 
        print(line, end='')
        if not line:
            break

以下のようにしてチャンクサイズを指定して読み込むこともできる。

# チャンクをつかって2文字ずつ読み込みする
with open('test.txt', 'r') as f:
    while True:
        chunk = 2 # チャンクのサイズを2としている
        line = f.read(chunk)
        print(line)
        if not line:
            break

seek()でファイル内の位置を指定することもできる。

with open('test.txt', 'r') as f:
    print(f.tell()) # f.tell()で現在の位置を返す
    print(f.read(1))
    f.seek(5) # ファイルの位置を先頭から5に設定
    print(f.read(1))
    f.seek(14) # ファイルの位置を先頭から14に設定
    print(f.read(1))
    f.seek(0) # ファイルの位置を先頭に設定

テンプレート

以下のようなテンプレートファイルdesign/email_template.txtがあるとして、$name$contentsにプログラムから値を設定する。

design/email_template.txt
Hi $name.

$contents

Have a good day

このファイルを使って、

  1. stringライブラリをインポート
  2. string.Template()関数でテンプレートを読み込んでtに設定して
  3. t.substitute()でテンプレートに値を設定して文字列を返す

コードは以下。

import string

# テンプレートファイルの読み込み
with open('design/email_template.txt') as f:
    t = string.Template(f.read())

# テンプレートの変数に値を設定
contents = t.substitute(
  name='Hanako', 
  contents='How are you? I am fine!')

print(contents)

CSVライブラリ

import csvでインポートしてから利用する。

ファイルの書き込み

import csv

with open('test.csv', 'w') as csv_file:
    # フィールド名を配列で設定する。
    fieldnames = ['name', 'count']

    # csv.DictWriter()メソッドの引数として、ファイルオブジェクト、フィールド名を渡す
    writer = csv.DictWriter(
      csv_file, ieldnames=fieldnames)

    # ヘッダーの出力
    writer.writeheader()

    # ボディ部の書き込み
    # フィールド名と対応する値をディクショナリ形式で
    # writer.writerow()メソッドの引数として渡す
    writer.writerow({'name': 'A', 'count': 1})
    writer.writerow({'name': 'B', 'count': 2})
    writer.writerow({'name': 'C', 'count': 3})

ファイルの読み込み

with open('test.csv', 'r') as csv_file:
    # csv.DictReader()メソッドの引数として、ファイルオブジェクトを渡す
    reader = csv.DictReader(csv_file)

    # 一行ずつ処理する
    for row in reader:
        print(row['name'], row['count'])

ファイル操作

ライブラリos, glob, shutil, pathlibを使う。
ライブラリ名だけ覚えておいて、あとはググる。

os

import os

# ファイルが存在しているかをTrue,Falseで返す
print(os.path.exists('test.txt'))

# ファイルかどうかをTrue,Falseで返す
print(os.path.isfile('test.txt'))

# ディレクトリかどうかをTrue,Falseで返す
print(os.path.isdir('design'))

# ファイルのリネーム
os.rename('test.txt', 'renamed.txt')

# シンボリックリンクの作成
os.symlink('renamed.txt', 'symlink.txt')

# ディレクトリの作成
os.mkdir('test_dir')

# ディレクトリの削除
# ただし、ディレクトリの中身が空の場合のみ消せる
os.rmdir('test_dir') 

# ディレクトリの中身をリストで取得する
print(os.listdir('test_dir'))

pathlib

import pathlib

# 空ファイルを作成する
pathlib.Path('test_dir/test_dir2/empty.txt').touch()

glob

import glob

# test_dirディレクトリ内のファイルをリストで取得する
print(glob.glob('test_dir/*'))

# test_dirディレクトリ内の,txtファイルだけリストで取得する
print(glob.glob('test_dir/*.txt'))

shutil

import shutil

# ファイルのコピー
shutil.copy('test_dir/test_dir2/empty.txt',
            'test_dir/test_dir2/empty2.txt')

print(glob.glob('test_dir/test_dir2/*'))


# os.rmdirだとディレクトリが空の場合でないと消せないので、
# 空でない場合に消したいときにshutil.rmtree()を利用する
shutil.rmtree('test_dir')

ファイルの圧縮、解答

tar.gzファイル

import tarfileでインポートしてから利用する。

import tarfile

# tarファイルの圧縮
with tarfile.open('test.tar.gz', 'w:gz') as tr:
    # 圧縮したいファイル、ディレクリを追加する
    tr.add('test_dir')

# tarファイルの解凍
with tarfile.open('test.tar.gz', 'r:gz') as tr:
    tr.extractall(path='test_tar')

    # 展開せずに中身を見ることもできる
    with tr.extractfile('test_dir/test.txt') as f:
        print(f.read())

zipファイル

import zipfileでインポートしてから利用する。

import zipfile
import glob

# zipファイルの圧縮
with zipfile.ZipFile('test.zip', 'w') as z:
    # ひとつずつ追加する方法
    z.write('test_dir')
    z.write('test_dir/test.txt')

    # まとめて追加する方法
    for f in glob.glob('test_dir/**', recursive=True):
        print(f)
        z.write(f)

# zipファイルの解凍
with zipfile.ZipFile('test.zip', 'r') as z:
    # test_zipというディレクトリに展開する
    z.extractall(path='test_zip')

    # tarと同様、展開せずにファイルの内容を取得することもできる
    with z.open('test_dir/test.txt') as f:
        print(f.read())

tempfile

import tempfileでインポートしてから利用する。
tempfileは一時的なファイルを意味している。

import tempfile

# 一時的なファイルとして作成したい場合に利用する
# 処理が終わったら自動的に削除される
# IO Bufferの用途で利用する
with tempfile.TemporaryFile(mode='w+') as t:
    t.write('hello')
    t.seek(0)
    print(t.read())

# テンポラリファイルを作って残す場合
# NamedTemporaryFileを使い、引数のdeleteをFalseに設定する
with tempfile.NamedTemporaryFile(delete=False) as t:
    print(t.name)
    with open(t.name, 'w+') as f:
        f.write('test\n')
        f.seek(0)
        print(f.read())

# テンポラリディレクトリをつくる場合
# 処理が終わったら自動的に消去される
with tempfile.TemporaryDirectory() as td:
    print(td)

# テンポラリディレクトリを処理が終わっても残す場合
temp_dir = tempfile.mkdtemp()
print(temp_dir)

外部コマンドの実行

subprocess.run()を利用する。
os.system()は推奨されていないので利用しないようにする。

import subprocess

# コマンドの実行
subprocess.run(['ls', '-la'])

# 戻り値(結果コード)を取得して判定などしたい場合
rtn = subprocess.run(['ls', '-la'])
print(rtn)

タイムスタンプ、 日付

import datetimeでライブラリをインポートして利用する。

タイムスタンプを扱う場合

import datetime

# 現在の時刻を取得する
now = datetime.datetime.now()
print(now) # datetime.datetime(2018, 8, 30, 1, 10, 57, 15584)

# ISO形式の文字列として出力する
print(now.isoformat()) # 2018-08-30T01:10:57.015584

# 形式を指定して出力する
print(now.strftime('%Y%m%d:%H:%M:%S')) # 20180830:0110:57

日付までを扱う場合

import datetime

today = datetime.date.today()

# ISOフォーマットで表示
print(today)
print(today.isoformat())

# 任意のフォーマットで表示
print(today.strftime('%d/%m/%y'))

出力結果は以下。

2018-08-31
2018-08-31
31/08/18

計算

import datetime

now = datetime.datetime.now()
print(now) # 2018-08-30 01:18:09.813074

# 差分
d = datetime.timedelta(weeks=1)
# d = datetime.timedelta(days=1)
# d = datetime.timedelta(hours=24)

# 演算
# この場合は1週間前
print(now - d) # 2018-08-23 01:18:09.813074

コンフィグ

configparser

import configparserでインポートして利用する。

書き込み

import configparser

# configオブジェクトの生成
config = configparser.ConfigParser()

config['DEFAULT'] = {
   'debug':  True
}

config['web'] = {
    'host': '127.0.0.1',
    'port':  80
}

config['db'] = {
    'host': '127.0.0.1',
    'port': 5432
}

# ファイル書き込み
with open('config.ini', 'w') as file:
    # writeメソッドで書き込む
    config.write(file)

出力されるconfig.iniは以下の形式になっている。

config.ini
[DEFAULT]
debug = True

[web]
host = 127.0.0.1
port = 80

[db]
host = 127.0.0.1
port = 5432

読み込み

import configparser

# configオブジェクトの生成
config = configparser.ConfigParser()

# readメソッドでconfig.iniを読み込む
config.read('config.ini')

# 内容の確認
print(config['web'])
print(config['web']['host'])

yaml

書き込み

import yaml

with open('config.yml', 'w') as file:
    # dumpメソッドで書き込み
    yaml.dump({
        'web': {
            'host': '127.0.0.1',
            'port': 80
        },
        'db': {
            'host': '127.0.0.1',
            'port': 3306
        }
    }, file, default_flow_style=False)

出力結果は以下。

config.yml
db:
  host: 127.0.0.1
  port: 3306
web:
  host: 127.0.0.1
  port: 80

なお、default_flow_style=Falseがない場合には、以下のような形式となってしまう。

config.yml
db: {host: 127.0.0.1, port: 3306}
web: {host: 127.0.0.1, port: 80}

読み込み

import yaml

with open('config.yml', 'r') as file:
    data = yaml.load(file)
    print(data['web']['host'])

ロギング

基本

import logging

# ロギングの出力レベルをINFOに設定する。
# デフォルトはWARNING以上になっている。
logging.basicConfig(level=logging.INFO)

# ファイルに出力する場合は、filenameを指定する。
# logging.basicConfig(filename='test.log', level=logging.INFO)

logging.critical('criticalです')
logging.error('errorです')
logging.warning('warningです')
logging.info('infoです')
logging.debug('debugです')

コンソール表示結果

CRITICAL:root:criticalです
ERROR:root:errorです
WARNING:root:warningです
INFO:root:infoです

フォーマッタ設定

import logging

# フォーマットを指定する。
# ここでは、asctime,levelname,messageを属性として指定する。
# 他の属性情報などは、pythonのドキュメントを参照する。
# https://docs.python.jp/3/library/logging.html#logrecord-attributes
formatter = '%(asctime)s:%(levelname)s:%(message)s'


# basicConfigにフォーマッタを渡す。
logging.basicConfig(level=logging.INFO, format=formatter)

logging.info('infoです。')
2018-08-31 12:48:16,326:INFO:infoです。

ロガー設定

main_app.py
import logging

import component_app

# 基本的にメインのアプリでbasicConfigでloggingを設定する
logging.basicConfig(level=logging.INFO)

logging.info('infoです。')

component_app.do_something()
component_app.py
import logging

# ロガーの設定
# サブ側(component_app.py)ではloggingを直接使わずにloggerを使うようにする。
# getLoggerには__name__を慣習的に渡す。
logger = logging.getLogger(__name__)

# レベルをDEBUGに設定
logger.setLevel(logging.DEBUG)

def do_something():
    # main_app.pyではレベルをINFOに設定しているが、
    # component_app.pyではloggerの設定レベルをDEBUGに設定しているため出力される。
    logger.info('info component_app')
    logger.debug('debug component_app')

    # main_appで定義したloggingの設定レベルに依存するので、
    # なるべくloggerを使うようにする。
    # logging.info(info by logging)

出力結果は以下。

INFO:root:infoです。
INFO:component_app:info component_app
DEBUG:component_app:debug component_app

ハンドラー設定

ハンドラを使うと、出力先の設定を変更できる。
以下はloggerの出力先をファイルに設定した例。

component_app.py
import logging

logger = logging.getLogger(__name__)
logger.setLevel(logging.DEBUG)

# ハンドラの設定
h = logging.FileHandler('component_app.log')
logger.addHandler(h)


def do_something():
    logger.info('info component_app by logger')
    logger.debug('debug component_app by logger')

    # これはloggerを使っていないためcomponent_app.logに出力されない
    logging.info('info component_app by logging')
component_app.log
info component_app by logger
debug component_app by logger

ハンドラはデフォルトのStreamHandlerやFileHandler以外にも多くあるので、詳細はPythonドキュメントを参照する。
https://docs.python.jp/3/library/logging.handlers.html

フィルタ設定

ログメッセージのフィルタリング設定例。

import logging

logging.basicConfig(level=logging.INFO)


# フィルタリングクラスを定義する。
class NoPasswordFilter(logging.Filter):
    """
    パスワードをチェックするフィルタクラス
    logging.Filterクラスを継承してカスタマイズする
    """

    def filter(self, record):
        """
        メッセージをフィルタする
        """
        # レコードからログメッセージを取得する
        log_message = record.getMessage()

        # ログメッセージにpasswordという語句が含まれていたらフィルタリングする
        if 'password' not in log_message:
            return True
        else:
            return False


logger = logging.getLogger(__name__)

# フィルタを追加する
no_password_filter = NoPasswordFilter()
logger.addFilter(no_password_filter)

# passwordが含まれていないのでフィルタリングされない。
logger.info('from main')

# passwordが含まれているのでフィルタリングされる。
logger.info('from main password = "this is secret password"')

コンフィグ設定

ロギングの設定をファイルに定義する。
import logging.configでインポートして利用する。

import logging.config

# logging.ini参照
logging.config.fileConfig('logging.ini')

# rootロガーを利用している
logger = logging.getLogger(__name__)

logger.debug('debug message')
logger.info('info message')
logger.warning('warn message')
logger.error('error message')
logger.critical('critical message')


# simpleExampleロガーを利用する
logger2 = logging.getLogger('simpleExample')

logger2.debug('debug message')
logger2.info('info message')
logger2.warning('warn message')
logger2.error('error message')
logger2.critical('critical message')

logging.iniの設定例は以下。

logging.ini
[loggers]
keys=root,simpleExample

[handlers]
keys=streamHandler

[formatters]
keys=formatter


[logger_root]
level=WARNING
handlers=streamHandler

[logger_simpleExample]
level=DEBUG
handlers=streamHandler
qualname=simpleExample
propagate=0

[handler_streamHandler]
class=StreamHandler
level=DEBUG
formatter=formatter
args=(sys.stderr,)

[formatter_formatter]
format=%(asctime)s %(name)-12s %(levelname)-8s %(message)s

ポイントは、上3つの[loggers],[handlers],[formatters]で定義しているキー情報。

  • [loggers]セクションにはrootキーとsimpleExampleキーを使えるように定義している
    • rootは[logger_root]セクションで設定している
    • simpleExampleは[logger_simpleExample]セクションで設定している
  • [handlers]セクションにはhandlerにstreamHandlerキーを使えるように定義している
    • [handler_streamHandler]セクションで設定している
  • [formatters]にはformatterにformatterキーを使えるように定義している
    • [formatter_formatter]セクションで設定している。

ロギングの書き方

キー・バリュー形式で出力する

# 基本的な書き方
logger.error('Hoge hoge hoge hoge')

# キーバリューの形式で出力
# ログ解析のアプリやソフトで扱いやすい場合がある
logger.error({'action': 'create', 'status': '99', 'message': 'Hoge failed!'})

ログ出力ポイントの例

以下のようなメソッドが合った場合、どこにログを出力するべきか。

import csv

data = {'hanako': 22, 'taro': 25}

def save(, filepath):
    columns = ['name', 'age']
    with open(filepath, 'w+') as file:
        writer = csv.DictWriter(file, fieldnames=columns)
        writer.writeheader()

        for name, age in data.items():
            writer.writerow({'name': name, 'age': age})


save('hoge.csv')

以下のことが確認できるようにログ出力ポイントを設ける。
システム障害時のトラブルシューティングのしやすさを考慮することを念頭に置くことが大切。

  1. saveメソッドが呼ばれたことを確認できるようにする
  2. 書き込みが完了したことを確認できるようにする。
  3. メソッドの引数に何が与えられたか確認できるようにする。
data = {'hanako': 22, 'taro': 25}

def save(filepath):
    logger.info({
        'action': 'save',
        'filepath': filepath,
        'status': 'run'})

    columns = ['name', 'age']
    with open(filepath, 'w+') as file:
        writer = csv.DictWriter(file, fieldnames=columns)
        writer.writeheader()

        for name, age in data.items():
            writer.writerow({'name': name, 'age': age})

    logger.info({
        'action': 'save',
        'filepath': filepath,
        'status': 'done'})

save('hoge.csv')

virtualenv

virtualenvを使うことによって、pythonのバージョンを使い分けることができる。

virtualenvのインストール

pip install virtualenv

virtualenvの利用設定

pyCharmの場合

PreferencesProject InterpreterAddLocalから設定する。
New environmentを選択して、以下設定する。

  • Location: 新規でつくるvirtualenvの名前
  • Base Interpreter: 作成もとのPython

pycharm1_venv1.png

pycharm_venv2.png

再度、PreferencesProject Interpreterで新規で作成したvirtualenvを設定してあげれば完了。

コマンドラインの場合

現在のバージョンの確認

$ python -V
Python 3.6.3 :: Anaconda custom (64-bit)

my_python36という名前で仮想環境を作成する

$ virtualenv my_python36
Using base prefix '/Users/nagakuray/anaconda3'
New python executable in /private/tmp/my_python36/bin/python
Installing setuptools, pip, wheel...done.

アクティベートして利用できるようにする

$ source my_python36/bin/activate
(my_python36) $

無効化したい場合はディアクティベートする。

(my_python36) $ deactivate
16
21
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
16
21