Python
Friday-IO

脱初心者したい人の為のPythonテクニック

Friday I/O ですー。
株式会社ワムウでは、毎週金曜日は 興味がある事柄に取り組み、その成果を何らかの形でアウトプットする日 としております。
みんな金曜日は好きな事やったらいいよ!

はじめに

Pythonにもそろそろなれてきたなー って人はどんどん新しい事を学びましょう。
Pythonの世界にはまだまだ知らない事がいっぱいあると思いますよ。(僕も知らない事だらけ)

※ Python3.6系をベースにお話します。

内包表記

リスト内包表記は知ってると思いますが、実はリスト以外でも書けるんですよ!!

# 辞書内包表記
print({v: v * 10 for v in range(0, 10)})

# set内包表記
l = [1, 1, 1, 2, 2, 2, 3, 3, 3]
print({v * 10 for v in l})

いえーい!タプル内包表記書いちゃうぜー! とか言って以下の様に書いても動かないけどね!!!

print((v * 10 for v in range(0, 10)))

この場合、 generator object が返ってきます。
という事は以下の様にすればおk。

print(tuple(v * 10 for v in range(0, 10)))

コンテキストマネージャ と Withステートメント

ファイルオープンのサンプルとかで with 使ってるのみた事ありませんか?
アレですよアレ。

with open(filepath) as f:
    content = f.read(-1)

withステートメントから抜けるとファイルは自動的に close されますが、あれはコンテキストマネージャという仕組みを利用しております。

class ContextManager(object):
    def __enter__(self):
        print('はいったー!!!')
        return 'おにぎり'

    def __exit__(self, exc_type, exc_value, traceback):
        print('でたー!!!')

with ContextManager() as value:  #  value == 'おにぎり'
    print(value)

with ステートメント内に入ると __enter__ が呼ばれ、抜けると __exit__ が呼ばれます。
とってもシンプル。

ディスクリプタ

Pythonでは、 あるクラスに属している場合にのみ特殊な挙動をするクラス を作る事が出来るんですよ。

class Person(object):
    def __init__(self, last_name, first_name):
        self.last_name = last_name
        self.first_name = first_name
        self.parent = None

    # これがキモ!
    def __get__(self, parent, klass):
        self.parent = parent
        return self

    @property
    def fullname(self):
        if self.parent:
            return self.parent.last_name + self.first_name
        return self.last_name + self.first_name

普通にインスタンス化して利用するとふつーのクラス。

gakky = Person('新垣', '結衣')
print(gakky.fullname)  # 新垣結衣

ただし、あるクラスに属すると挙動が変わります!

class Man(Person):
    # みんなの嫁!
    yome = Person('新垣', '結衣')

# この時はまだみんなの嫁。
print(Man.yome.fullname)  # 新垣結衣

# 俺ー!
ore = Man('山田', '太郎')

# ケコーン!!!!
print(ore.yome.fullname)  # 山田結衣!!!!!

クラスやインスタンスを経由した参照の場合、 __get__ が実行される仕組みです。
属しているクラスの値をモリモリ参照するクラスを作るときにはとっても便利。

デスクリプタ HowTo ガイド

キャッシュ機能付き Property デコレータ

Djangoのユーティリティとして実装されてたりするんですが、 @property デコレータのキャッシュ機能付き的なやつ。
1回呼び出すと結果をキャッシュするので、内部で重たい処理をしてたりする場合、PCに優しい人になれるよ。

class cached_property(object):
    def __init__(self, func):
        self.__doc__ = getattr(func, '__doc__')
        self.func = func

    def __get__(self, parent, klass):
        if parent is None:
            return self
        value = parent.__dict__[self.func.__name__] = self.func(parent)
        return value


class Hoge(object):
    @cached_property
    def value(self):
        import time
        time.sleep(10)
        return 'ちょー重たい処理だったぜ'


hoge = Hoge()
print(hoge.value)
print(hoge.value)

これが正しいディスクリプタの使い方です!!!
単体のライブラリもありまっせ。

メタプログラミング

Pythonの黒魔術 でございます。
クラスの定義を動的にイジイジ出来る仕組みなんですが、以下の様に実装すると大嘘つき野郎になれます。

class MetaClass(type):
    def __new__(klass, name, bases, attrs):
        """
        arguments:
            klass -- MetaClass 自身
            bases -- Hogeの名前
            bases -- Hogeの親クラス達
            attrs -- Hogeが持つクラス変数とか関数とか

        return:
            実際に利用するクラス
        """
        # new_class には Hoge が入ります
        new_class = super(MetaClass, klass).__new__(klass, name, bases, attrs)

        # int を返すよ!
        return int

# list を継承するよ!!
class Hoge(list, metaclass=MetaClass):
    pass

# ガン無視!!!
print(Hoge)  # <class 'int'>

こんなネタ実装はさておき、Metaclassは込み入ったライブラリなら大体使ってる技術なので、あれこれ見ると勉強になると思います。
色々無視した挙動になるのでご利用は計画的になのですが、実装がすっきりするので是非チャレンジしてみてください。

class MetaClass(type):
    def __new__(klass, name, bases, attrs):
        new_class = super(MetaClass, klass).__new__(klass, name, bases, attrs)

        silent = attrs.get('silent', False)
        if silent:
            new_class.say = lambda s: print('。。。')

        return new_class

class Person(object):
    def say(self):
        print('こんにちわこんにちわこんにちわヒャッハー!!!')

class Ore(Person, metaclass=MetaClass):
    silent = False

class Omae(Person, metaclass=MetaClass):
    silent = True

Ore().say()  # こんにちわこんにちわこんにちわヒャッハー!!!
Omae().say()  # 。。。

小ネタとか

空の値を削除する

配列から空の要素を削除したい時は以下の様に書くと出来るよ。

def ignore_empty(l):
    return list(filter(None, l))

print(ignore_empty([1, None, 0, 2, '', 3]))

https://docs.python.jp/3/library/functions.html#filter

flatten

多次元の配列を1次元にしたい時ってたまにあるよね!
2次元の配列の場合ならさっくり書けるのです。

from itertools import chain

def flatten(l):
    return list(chain.from_iterable(l))

print(flatten([
    [1,2,3],
    [4,5,6],
    [7,8,9],
]))

もっと多次元とか、いろいろ混ざってる場合はちゃんと実装しないとダメみたい。

Pythonでリストをflattenする方法まとめ

複数の値にマッチする条件式を簡単に書く

ファイル名が hoge.txt か piyo.md にマッチしてる なんて条件式を書くと以下の様になると思います。

if filename in ['hoge.txt', 'piyo.md']:
    pass

じゃあこれに hige.textiiyo.md も追加したいってなって、ああそうえばあれもこれもーとかなる非常に長い配列を書かなくちゃいけなくなる。

if filename in ['hoge.txt', 'hige.text', 'piyo.md', 'iiyo.md', ...]:
    pass

そんな時便利なのが以下の関数。
Unixの glob の要領で柔軟に書けます。

from fnmatch import fnmatch

class glob_list(list):
    def __contains__(self, value):
        for pattern in self:
            if fnmatch(value, pattern):
                return True
        return False

l = glob_list(['??ge.txt', '?iyo.md'])

assert 'hoge.txt' in l
assert 'hige.txt' in l
assert 'piyo.md' in l
assert 'iiyo.md' in l

おしまい

Pythonは本当に便利ですねー。
少しでも皆さまのスキル向上に貢献出来れば幸いでございますー。