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


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

## はじめに

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

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


## 内包表記

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

```python
# 辞書内包表記
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})
```

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

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

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

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


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

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

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

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

```python
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では、 `あるクラスに属している場合にのみ特殊な挙動をするクラス` を作る事が出来るんですよ。

```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
```

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

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

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

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

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

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

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

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

[デスクリプタ HowTo ガイド](https://docs.python.jp/3/howto/descriptor.html)


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

[Djangoのユーティリティ](https://github.com/django/django/blob/master/django/utils/functional.py#L15)として実装されてたりするんですが、 `@property` デコレータのキャッシュ機能付き的なやつ。
1回呼び出すと結果をキャッシュするので、内部で重たい処理をしてたりする場合、PCに優しい人になれるよ。

```python
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)
```

これが正しいディスクリプタの使い方です！！！
[単体のライブラリ](https://github.com/pydanny/cached-property)もありまっせ。


## メタプログラミング

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

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

```python
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()  # 。。。
```

## 小ネタとか

### 空の値を削除する

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

```python
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次元の配列の場合ならさっくり書けるのです。

```python
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する方法まとめ](http://d.hatena.ne.jp/xef/20121027/p2)

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

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

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

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

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

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

```python
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は本当に便利ですねー。
少しでも皆さまのスキル向上に貢献出来れば幸いでございますー。