2017/11/22

Python Tips: Python でインタフェースを使いたい

Python でインタフェースの機能を使う方法をご紹介します。

・・・といっても、 Python 3.6 の時点で Python には言語機能としてのインタフェースは存在しません。具体的にいうと、「継承先に特定のインタフェースの実装を強制できるような仕組み」が Python にはありません。

しかし、標準ライブラリの abc を使うと次の 2 つのことが実現できます。

  • a. 複数のクラスを抽象クラスでまとめる
  • b. 継承先クラスのインスタンス生成時にメソッドの存在チェックをかける

このことをもって、「 Python でも他の言語でいうところの『インタフェース』の機能をある程度は利用できる 」と考えてもよいのではないかな、と個人的には思います。

今回はこの Python におけるインタフェース的な機能を abc で実現する方法をご紹介します。上の a b を順に見ていきましょう。


a. 複数のクラスを抽象クラスでまとめる


最初に、複数のクラスを抽象クラスでまとめる方法を見てみます。

複数の異なるクラスを抽象クラスでまとめることで、それらのクラスのオブジェクトに対して isinstance() が True を返すようなチェック機構を作ることができます。

具体的なコードを見てみましょう。

# coding: utf-8

'''デジタルコンテンツインタフェースを作ってテストする
'''

from abc import ABC


class Movie:
    '''映画
    '''
    pass


class Book:
    '''書籍
    '''
    pass


class DigitalContentInterface(ABC):
    '''デジタルコンテンツのインタフェース
    '''
    pass


# 映画と書籍を DigitalContentInterface の仮想サブクラスとして登録する
DigitalContentInterface.register(Movie)
DigitalContentInterface.register(Book)


def is_digital_content(object):
    '''オブジェクトがデジタルコンテンツかどうかをチェック
    '''
    return isinstance(object, DigitalContentInterface)


# Movie のインスタンスは DigitalContentInterface のインスタンスと認識される
m1 = Movie()
print(is_digital_content(m1))  # => True

# Book のインスタンスも DigitalContentInterface のインスタンスと認識される
b1 = Book()
print(is_digital_content(b1))  # => True

# 登録されていないクラスのインスタンスでは当然 False が返る
d1 = {}
print(is_digital_content(d1))  # => False

ここでは abc.ABC というクラスを継承する形で DigitalContentInterface というインタフェース用のクラスを作成しました。

続いて、クラスメソッド register() を使って、 MovieBook という 2 つのクラスを DigitalContentInterface の仮想サブクラスとして登録しています。

その後に isinstance() でチェックをかけると、 MovieBook のインスタンスは DigitalContentInterface のインスタンスと認識されることが確認できます。

おもしろいですね。

register() メソッドが便利なのは、組み込みのクラスを含む定義済みのクラスも「独自に作ったインタフェース」の仮想サブクラスとして登録できるところです。

# リストは本来(当然) DigitalContentInterface のインスタンスではない
l1 = []
print(is_digital_content(l1))  # => False

# リストクラスを DigitalContentInterface の仮想サブクラスとして登録すると・・・
DigitalContentInterface.register(list)

# リストのインスタンスも DigitalContentInterface のインスタンスと認識されるようになる
print(is_digital_content(l1))  # => True

ちなみに、この、特定のクラスを仮想サブクラスとして登録する register() メソッドを使った方法とは別に、クラスメソッド __subclasshook__() を使った方法も用意されています。

公式のサンプルを少し縮めたサンプルを見てみましょう。

class MyIterable(ABC):

    @classmethod
    def __subclasshook__(cls, C):
        if cls is MyIterable:
            if any("__iter__" in B.__dict__ for B in C.__mro__):
                return True
        return NotImplemented

ここでは、 __subclasshook__() メソッドを実装することで「 __iter__() メソッドを実装しているクラスは MyIterable のサブクラスとみなす」というロジックが実現されています。 __subclasshook__() を使えば、このようにより柔軟なロジックで仮想サブクラスを設定することができます(例えば、インタフェースの定義時には具体的な仮想サブクラスがわからない状態でも仮想サブクラスを登録することができます)。

ちなみに、この abc.ABC はメタクラス abc.ABCMeta をよりシンプルに扱えるクラスとして Python 3.4 で導入されました。

a については以上です。続いて b の方を説明していきます。


b. 継承先クラスのインスタンス生成時にメソッドの存在チェックをかける


こちらは、よりインタフェースっぽい感じのふるまいが実現できる機能です。

こちらも先にサンプルコードを見てみましょう。

# coding: utf-8

'''デジタルコンテンツインタフェースでメソッドの実装の強制機能をテストする
'''

from abc import ABC
from abc import abstractmethod


class DigitalContentInterface(ABC):
    '''デジタルコンテンツのインタフェース
    '''

    def __init__(self, title):
        self.title = title

    @abstractmethod
    def format_title(self):
        pass


class Movie(DigitalContentInterface):

    def format_title(self):
        return 'Movie: {}'.format(self.title)


class Book(DigitalContentInterface):
    pass


# Movie のインスタンスは問題なく生成できる
m1 = Movie('From Dusk Till Dawn')
print(m1.format_title())
# => Movie: From Dusk Till Dawn
print(isinstance(m1, DigitalContentInterface))
# => True

# Book のインスタンスは
# Book が format_title() メソッドを定義していないので作成できない
b1 = Book('The Martian')
# TypeError: Can't instantiate abstract class Book with abstract methods format_title

以下、コード内の処理の流れを説明します。

まず最初に、 a と同じような形で abc.ABC クラスを継承した DigitalContentInterface というクラスを定義しています。この中で abc.abstractmethod というデコレータ用の関数を使って format_title() というメソッドをアブストラクトメソッドとして登録しています。

つづいて、 a の register() を使った形ではなく、通常の継承のシンタックスを使って DigitalContentInterface を継承したクラス MovieBook を定義しています。

実際にインスタンスを生成しようとすると、 Movie の方は問題なく生成できますが、 Book の方はアブストラクトメソッド format_title() をきちんと実装していないということで例外が上がって生成できません。

・・・というように、「インスタンス生成時にメソッドの存在チェックをかける」ことができていることが確認できます。

こちらもおもしろいですね。うまく活用できると便利そうです。

ただし、 b の方は使う際に押さえておくべき注意点がいくつかあります。

  • register() を使った方法で登録した仮想サブクラスに対してはチェックが走らない
  • @abstractmethod で登録されたメソッド自身が `pass` 以外の実装を持つことができて、サブクラスから super() で呼び出すことができる

ここで取り上げたのはインスタンスメソッドを使ったパターンだけでしたが、クラスメソッド、スタティックメソッド、プロパティに対しても同様のチェックを行うこともできます。その場合はデコレータの記述順などにも厳密なルールがあるため、興味のある方は利用の前に公式のドキュメントなどでしっかり確かめてから使うようにするとよいかと思います。


以上です。

おもしろいですねー。


参考

abc” Abstract Base Classes — Python documentation
Python でも ABC (Abstract Base Class) を使えば抽象クラスが作れる | CUBE SUGAR STORAGE

2017/11/17

Python Tips: 改行をうまく扱いたい

Python での改行の扱い方についてまとめてみました。わりとピンポイントなテーマになりますが、興味のある方はご参考にしてみていただければと思います。

  • Python における改行コード
  • 改行なしで出力する
  • ファイルの中身を行単位で取得する
  • 文字列の末尾の改行コードを取り除く
  • 文字列を改行で分割する
  • 改行コードをそのまま出力する
  • ソースコード内で改行する

Python における改行コード


まずは、基本中の基本ですが、今回のお話の前提となる「 Python における改行コード」について見ておきましょう。

Python における改行コードは \n です。

print('五月雨を\nあつめて早し\n最上川')
# =>
# 五月雨を
# あつめて早し
# 最上川
#

print("蛤の\nふたみにわかれ\n行秋ぞ")
# =>
# 蛤の
# ふたみにわかれ
# 行秋ぞ
#

文字列中に \n を入れるとそれが改行コードとして解釈されます。

Python には文字列リテラルの表記が 2 種類ーー通常の引用符と二重引用符がーーありますが、どちらも改行コードに関するふるまいは同じで、改行コードは出力時に改行に変換されます。

改行なしで出力する


組み込みの print() 関数は、デフォルトでは末尾に改行を自動的に追加するふるまいになっています。

print('Hello')
print('world')
# =>
# Hello
# world

この改行を防ぐには 2 の方法があります。

  • a. print() の引数 end を指定する
  • b. print() の代わりに sys.stdout.write() を使う

a. print() の引数 end を指定する

print() 関数の宣言部は次のようになっていて、このうちの end を指定すると、末尾に自動で追加される改行コードを変更することができます。

print(*objects, sep=' ', end='\n', file=sys.stdout, flush=False)

改行なしの出力にしたい場合は単純に空文字を渡せば OK です。

print('Hello', end='')
print('world', end='')
# => Helloworld

b. print() の代わりに sys.stdout.write() を使う

標準ライブラリ syssys.stdout.write()print() と同じように標準出力に書き出すための関数ですが、こちらは末尾に改行を自動追加するようなことはしないので、文字列を渡せばそれがそのまま出力されます。

import sys

sys.stdout.write('Hello')
sys.stdout.write('world')
# => Helloworld

このトピックについては過去にも記事を書いているので、興味のある方はご覧になってみてください。


ファイルの中身を行単位で取得する


テキストファイルの中身を改行までをひとつの区切りとして取得する方法です。方法がいくつかあるのでひとつずつご紹介します。

  • a. ファイルオブジェクトを for ループで回す
  • b. ファイルオブジェクトの readline() メソッドを使う
  • c. ファイルオブジェクトの readlines() メソッドを使う

a. ファイルオブジェクトを for ループで回す

ファイルオブジェクトはイテレータプロトコルを備えているので、ループで回すことができます。その場合の各要素はファイル内の各行となります。

with open(FILE_IN) as f:
    for line in f:
        print(line, end='')
# => ファイルの各行が出力される

各行の末尾には改行コード \n が付いたままになっているので、他のファイルや標準出力に出力する際には末尾の改行が重複しないように気をつける必要があります。

b. ファイルオブジェクトの readline() メソッドを使う

ファイルオブジェクトには readline() というメソッドが備わっており、これを使えば for ループの場合と同様に行単位でファイルの中身を取得することができます。

with open(FILE_IN) as f:
    while True:
        line = f.readline()
        if not line:
            break
        print(line, end='')
# => ファイルの各行が出力される

こちらの場合は readline() の戻り値が空文字列になればファイルの末尾に到達したことになります。ファイル途中の空行の場合は空文字ではなく改行コード \n が 1 つだけ入った文字列が返るので、区別することができます。

a と b を比べると、 pythonic なのはどちらかということ a の方なので、 b で書きたい強い理由が特に無いかぎりは a のパターンで書くのがよいかと思います。

c. ファイルオブジェクトの readlines() メソッドを使う

ファイルオブジェクトの readlines() メソッドは、その名前が示すとおり、ファイルの中身を複数行まとめて取得できるメソッドです。

with open(FILE_IN) as f:
    print(f.readlines())
# => ファイルの全行が行単位で格納されたリスト

ファイルのサイズが巨大な場合にどうなるかは調べていないのでわかりませんが、私が確認したかぎりでは、ファイルの全行を行単位で格納したリストが生成されます。 a 、 b の方法と同じく、こちらの場合も末尾の改行は含まれたままとなっているので注意が必要です。

ちなみに、次の例のように、ファイルオブジェクトの readlines() メソッドに引数を渡すと取得行数を指定することもできます。

with open(FILE_IN) as f:
    while True:
        lines = f.readlines(5):
        if not lines:
            break
        print(lines)
# => ファイルの中身を 5 行ずつ取得したリスト
# ファイルの行数が 5 行よりも少ない場合は、例外などは上がらずファイルの行がすべて格納される

文字列の末尾の改行コードを取り除く


続いて、文字列の末尾の改行コードを取り除く方法です。上の「テキストファイルの中身を行単位で取得する」ときなどに、各行を前処理したい場合などに利用することがあるかと思います。

方法としては、おそらく rstrip() メソッドを使う方法が最もシンプルで、なおかつ間違いがありません。

with open(FILE_IN) as f:
    for line in f:
        trimmed = line.rstrip('\n')
        print(trimmed)
# => ファイルの各行が表示される

ちなみに、 for ループで各行を取得した場合には、行の先頭に改行文字列があることは無いので、ここの rstrip()strip() に置き換えても同じ結果を得ることができます。ただ、 strip() よりも rstrip() を使った方がコードの意図をより明確に示すことができるので、このようなケースでは私は rstrip() を好んで使っています。

文字列を改行で分割する


間に改行が挟まった文字列を改行のところで分解する方法についてです。

こちらは、文字列型の splitlines() メソッドを使うのがよいでしょう。

haiku = '夏草や\n兵どもが\n夢の跡'
lines = haiku.splitlines()
print(lines)
# => ['夏草や', '兵どもが', '夢の跡']

名前そのままの挙動でわかりやすいですね。

ちなみに、 splitlines()keepends という引数を受け取ることができます。これは「改行コードを残すかどうか」を指定するためのフラグで、デフォルト値は False になっています。 True にすると、戻り値のリストの各要素の末尾に改行コードが残ったままになります。

haiku = '夏草や\n兵どもが\n夢の跡'
lines = haiku.splitlines(keepends=True)
print(lines)
# => ['夏草や\n', '兵どもが\n', '夢の跡']

同様の処理は、文字列型の split() メソッドで行うこともできますが、区切り文字が改行コードなら、 splitlines() を使う方が意図をより明確に示せるのでよいかなと思います。

改行コードをそのまま出力する


次は、改行コードをそのまま出力する方法についてです。

改行コードを変換せずにそのまま出力するには、 repr() 関数をかませるか、フォーマット文字列シンタックスの変換フラグ !rrepr() )を使うとよいかと思います。どちらも得られる結果は同じです。

haiku = '石山の\n石より白し\n秋の風'
print(repr(haiku))
# => '石山の\n石より白し\n秋の風'

haiku = '石山の\n石より白し\n秋の風'
print('{!r}'.format(haiku))
# => '石山の\n石より白し\n秋の風'

ソースコード内で改行する


以上のお話はすべて「文字列の中の改行」についてのお話でしたが、これは文字列の中ではなく、 Python のソースコードの中で改行するにはどうすればよいか、というお話です。

Python は、 C 言語のように文の末尾に ; を付ける必要がない代わりに、通常は行末がそのままひとつの文の終わりを意味することになっています。では、メソッドチェーンなどで一文が長くなるときにはどうやって改行したらよいのでしょうか。メジャーなやり方は、大きく分けて 2 つあります。

  • a. 改行を \ でエスケープする
  • b. かっこの中で改行する

a. 改行を \ でエスケープする

コード中の改行を文の終わりにしたくない場合は、 \ (バックスラッシュ)を使えば OK です。改行の直前に \ を置きましょう。

Address.select() \
    .where(Address.geo_lng.is_null(False)) \
    .where(Address.geo_lat.is_null(False))

b. かっこの中で改行する

Python では通常文末が文の終わりを示すことになりますが、かっこの中だけはわりと自由に改行することができます。ここで「かっこ」というのは、 [](){} のことを指しています。

例えば、リスト内包は次のように改行を入れて書くことができます。

[x * 5
    for x in range(10)
    if x % 2 == 0]

また、関数やメソッドの () 内の引数は途中で自由に改行することができます。

Address.select(
    Address.title,
    Address.geo_lng,
    Address.geo_lat)

この方法を使えば、例えば、メソッドチェーンが長くなる場合に、式全体を () でくくることで、各行の行末に毎回 \ を入れる手間を省くことができます。

(Address.select()
    .where(Address.geo_lng.is_null(False))
    .where(Address.geo_lat.is_null(False)))

・・・

というわけで、以上です。

このあたりは基本的なところではありますが、他の言語を長く触っていて久しぶりに Python に戻ってきたときなんかによく忘れていたりするので、パッと確認できるようにしておくと便利かと思います。

Python の改行の扱い方をまとめて押さえておきたいときにぜひ参考にしてみてください。

2017/11/13

Python が学べる英語のオンラインコースいろいろ

プログラミング言語「 Python 」を学べる英語のオンラインコースについてかんたんにまとめてみました。

システム開発・プログラミングというのはそれなりに歴史のある分野なので、日本語にも Python 関連の良書や良いサービスがたくさんあります(最近は特に増えてきたようです)。

ですので、プログラミングをわざわざ「英語で学ばないといけない」ということはありませんが、もしあなたが英語にある程度馴れがあり「プログラミングを学びたい」と考えているのであれば、日本語のサービスに加えて英語のサービスも選択肢に入れることを強くおすすめします。

英語でプログラミングを学ぶことには次のようなメリットがあります。

  • 日本語のものだけから選ぶよりも選択肢が広いので、より自分の学習スタイルに合ったものが見つかる
  • 各概念を英語の名称で学べるので、後からそれらを検索するときにスムーズ
  • 英語の説明の方が日本語よりもシンプルでわかりやすいことがある(必ずしもそうとはかぎりません)

これらに加えて、「 MIT やハーバード大学のような大学の講義を自宅で受けられること」もメリットのひとつだと思います。 MIT やハーバードの教授はどんな風に教えているのか、学生さんはどんな感じで学んでいるのか、を感じることができます。

ということで、私がよく聞くものや目にするものを中心に、英語で Python を学べるコースをいくつか集めてました。「英語で Python を学んでみたい」という方にご参考にしてみていただければと思います。


Google's Python Class



Google 社が提供する「 Google for Education 」というコンテンツ集の中の Python チュートリアルです。パッと読んだところでは、基本のところからとても丁寧に解説してあります。


Coursera Python コース群



定番の Coursera には Python で検索すると 100 以上のコースが見つかります。

ただし、数はたくさんありますが、次の 3 種類の講座があって、このすべてが「 Python を学ぶためのもの」というわけではないのでご注意ください。

  • a. Python を使って「プログラミング」を学ぶコース
  • b. 「 Python 」を学ぶコース
  • c. Python で「◯◯」をする方法を学ぶコース

c の◯◯には「統計分析」や「データサイエンス」、「機械学習」、「ウェブ制作」などのキーワードが入ります。

Python は幅広い分野で使われている言語なので、 c の講座が大量にあります。プログラミングや Python そのものについて学びたい場合は、 c のものは目的に合わないので、タイトルや説明文をよく読んで a か b のものを受講する(= c を選ばない)ようにするとよいでしょう。

動画、テキスト、クイズ、エクササイズなど、いろんなものを組み合わせてコースが組み立てられています。

コースによって無償のものもあれば有料のものもあります。


edX Python コース群



こちらも定番の edX の Python 関連コースです。内容は Coursera と似たような感じです。どのコースも非常にクオリティが高いです。

余談ですが、私は昔 edX の「 MITx6.00: Introduction to Computer Science and Programming Using Python 」を受講したことがあります。


そのときは、軽い気持ちで受け始めたらハマってしまい、課題をすべて提出し、満点に近いスコアにしてから修了しました(ちなみに私は学生時代の専攻がシステム関係だったので、コース内で学ぶことの大半は受講前から知っていました。それでも、ここで学べたことはたくさんありました。)。

いま確認すると、公称では「 1 週間に 15 時間程度の努力 × 9 週間」が必要なコースとのことなので、真剣に取り組むと決して楽なコースではありません。ただ、 CS に興味があり「将来プログラミング関係の仕事に就きたい」と考えている学生さんなんかは、とてもおすすめです。英語に苦手意識があまりなく、意欲と時間があれば(春休みや夏休みなどに)チャレンジしてみるとおもしろいと思います。

私の個人的な感覚では、「とりあえずプログラマーを 10 年間やっています」という人よりも「 MITx6.00 をすべて理解して修了しました」という初心者の方がエンジニアとして信用できる可能性が高いと思っています。それだけ濃密なコースです。


codecademy Python コース



codecademy の Python コースです。このうちの何割ぐらいの人が修了したのかはわかりませんが、 250 万人以上の人が受講を開始したそうです。


Udacity Python コース



Udacity の Python の基礎コースです。詳しくはわかりません。


Microsoft Virtual Academy Python コース



Microsoft も最近はこのようなコースを提供しているそうです。こちらも詳しいところはわかりません。


Lynda.com Python コース群



オンラインコースの老舗 Lynda.com の Python コース群です。 Lynda.com 全体の規模で考えると、 Python 関連のコースはあまり豊富ではありません。有料です。

個人的な印象としては Lynda.com は実用を重視している感じなので、学生さんよりは社会人を対象としている印象があります。


Code School の Python シリーズ



Code School の Python シリーズです。 Python の基礎と Pytbon のウェブフレームワーク Django のコースが用意されています。有料ですが、一部のコースが無料で提供されています。


Real Python



ウェブ開発を行うための Python 入門サイトです。有料です。次の 3 つのコースが提供されています。

  • Course 1: Introduction to Python
  • Course 2: Web Development with Python
  • Course 3: Advanced Web Development with Django



SoloLearn の Python 3 チュートリアル



SoloLearn の Python チュートリアル。教材・ツールがリッチでおもしろそうですが、詳しくはわかりません。


以上です。

他にも無数にあるかと思いますが、現時点でのメジャーどころで間違いがないのはこのあたりになる、の、かな、と思います。

受講・修了した経験のある方は、おすすめのものがあればぜひ教えてください。

...


英語圏のものも玉石混淆・ピンキリなので、「英語のものは日本語のものより絶対にいい!」とは思いませんが、こと CS 、プログラミングに関しては英語のリソースは日本語の何倍・何十倍もあるので(個人的な感覚値です)、選択肢は多ければ多いほどいいもの、自分に合うものに出会える可能性は上がるかなと思います。

よろしければ参考にしてみてください。