知っておくとPythonを早く会得できる5つのポイントを考察!『パーフェクトPython』著者がPythonの魅力を語る!

株式会社ディー・エヌ・エーのシステム本部CTO室の露木誠です。
PythonやDjangoについて執筆した『パーフェクトPython』や『Django×Python』などの著書が技術系出版社から数冊出版されています。DjangoのAUTHORSファイルにも実は名前が掲載されています。
本記事では、Pythonを始めたいと思っている方向けに、Pythonの魅力をお伝えできればと思います。
知っておきたいPythonの言語仕様や特徴的な考え方をご紹介しますので、参考にしてください。
目次
1.自己紹介とPython、Djangoに関わる活動について
ディー・エヌ・エーのCTO室に所属、元々は異業種からIT業界に参入
現在は、株式会社ディー・エヌ・エーのシステム本部CTO室で、エンジニア組織の課題解決を主な活動として、日々奮闘しています。
私はもともとファッションジュエリーの輸入販売業という全くの異業種にいました。転職を決めたのがちょうど20世紀末です。2000年問題なども影響してエンジニア不足が叫ばれていた時代に、エンジニアに転向しています。
IT業界に入ってからは、Python、Java、PHPなどのプログラミング言語を用いてB向けC向けを問わずさまざまな開発に従事しました。製造業の社内システムなども開発していた時期もあります。
ソフトウェアエンジニア、チーフアーキテクト、テックリードなども務めたほか、プログラミング講習の講師や自社サービス用工場の立ち上げなど、幅広い領域に関わってきました。
2006年頃からはPythonやDjangoの普及を目指して、Djangoの日本コミュニティを立ち上げ、勉強会を主催したりしていました(現在は私ではなく2代目が規模を拡大しながら続けています)。Maker Faire Tokyoへの出展を目指しながらIoTを楽しむ「鎌倉Maker Lab.」でスタッフとしてお手伝いをしているもその一つです。 また、書籍も執筆しており、複数冊出版されています(共著・単著あり)。『パーフェクトPython』に関しては2020年6月1日に最新版の『パーフェクトPython 改訂2版』が出版される予定です。
【過去の著作一覧】
・ムック
『最新Pythonエクスプローラ ~Django,TurboGears,Twisted,IronPython 完全攻略』
・共著 『開発のプロが教える標準Django完全解説』 『パーフェクトPython』 『15時間でわかるPython集中講座』
・単著 『Django×Python』
2. Pythonとの出会い、Zope世代
私とPythonの出会いについて少しお話しします。エンジニアに転向してからしばらくはJavaを主に使用していたのですが、2002年頃に何かスクリプト言語を使えるようになっておこうと考えました。PerlやPHP、Ruby、Pythonの当時4大LL言語と呼ばれていたプログラミング言語が選択肢でした。
PerlとPHPはすでに相当使われていたので、まだまだ使っている人が少ないRubyとPythonのほうが面白そうだ、ということで絞込んで比較をしていました。
その中で最終的にPythonを選んだ決定打になったのがZopeです。これはWeb Application ServerでもありWeb Application Frameworkでもあり、そして統合開発環境でもある、という不思議なソフトウェアです。20世紀の当時から、オブジェクトデータベースという考え方のデータベースを標準搭載していて、なおかつ普通に使われていました。
このZopeのすごさに惹かれてPythonを選ぶことにしたのです。具体的にはZope上で動くSquishdotというSlashdotのクローンやCOREBlogというブログツールをいじくり回すところから入りました。
その後Ruby on Railsが流行り始めたわけですが、同時期にオープンソースになったDjangoに出会い、普及活動を通して完全にハマりました。Pythonエンジニアのことをよく「Zope世代」とか「Django世代」とか言いますが、私はZope世代です。
現在Pythonは機械学習での利用が増えていますから、流行り言葉で言えば「AI世代」ということになるのでしょう。
でも実は、機械学習やAIのライブラリで基礎となっている数値計算ライブラリのNumPyがリリースされたのは1995年です。ここには、Pythonが世界的にはかなり面白い形で使われてきたという背景があります。
プログラマーでなくとも扱いやすいという特徴があり、世界中のさまざまな業種の人たちが使うための多彩なライブラリが生まれ、そこから発展してきたのです。
3.魅力を考察
Pythonの魅力―「こうなるだろう」を実現する
Pythonの魅力は、デコレーターや名前空間、間違いが起こりやすいということでほかの言語が忌避した演算子のオーバーロードやメタプログラミング、C言語のAPIなどにあらわれています。
後の項目でも詳しくご説明しますが、これらは一貫した思想のもとに設計されています。それはプログラムを見る人が「こうなるだろう」と素直に読めることを目指している、ということです。
Pythonの一番の特徴であるインデント(字下げ)にしてもそうです。ほかのプログラミング言語は{…}の範囲を同じカタマリとして見ますが、Pythonの場合はインデントして「見た目としてズレている」部分がカタマリ(以下ブロック)だという考え方を採用しています。プログラミングを書いていれば見やすいようにインデントするし、ブロックで分けるだろう、だったら{}は不要だ、という考えなのです。
ほかにも演算子のオーバーロードができることで、自分で定義したクラスについても「これとこれを+記号で連結をしたらこうなる」ときちんと定義できます。演算子のオーバーロードを許していない言語ではプリミティブ型と一部のビルトイン型でのみ+記号が使えて、自分が定義したクラスのインスタンスにはaddメソッドを定義して使う必要があります。
こうなるだろう、と思ったとおりにできるのがPythonの非常に面白い部分なのです。
Djangoの魅力―固定観念に囚われない機能
Djangoの魅力にも是非、触れたいと思います。
DjangoはPythonの言語仕様活かすようにできており、Webアプリケーションの開発をしていると知らない間にPythonの特徴的な機能を利用しています。
DjangoはちょうどRuby on Railsが流行り始めた頃にオープンソースで公開されたのですが、その影響もあって「Railsを真似たもの」をいう見方をされることがあります。ですが、実際は全く思想が異なります。
Rails以後のWebアプリケーションフレームワークとしてかなり面白い機能があります。例えばApplicationという単位で関心ごとを分割する思想やテンプレートの継承などです。固定観念に囚われていない点が魅力です。
ほかにも例えば、MVC model2というMVC(Model View Controller)をWebに適用させたものがあるのですが、Djangoの場合はこれをMTV(Model Template View)という言い方に変えました。MVCはもともとグラフィカルユーザーインターフェース(GUI)用の考え方だからということで、再定義をしたのです。
テンプレートの継承についてはほかのWebアプリケーションフレームワークでも取り入れられてきたので今は普通の概念になっていますが、Djangoが持ち込んだ概念でほかのWebアプリケーションフレームワークが追いついていないものは残っていると思います。
4. おすすめの勉強法
Python自体が初めての方は、Dive Into Python3というチュートリアルがおすすめです。
Djangoに初めて触れるなら、Django Girlsのチュートリアルにトライしてみるのがいいと思います。
プログラミングそのものの勉強を始める人にもPythonはおすすめです。オブジェクト指向プログラミングや関数プログラミング、クラスやモジュールといった名前空間、クラス生成自体をカスタマイズできるメタプログラミング、そして本記事でご紹介する機能など、簡単で読みやすい構文と共存する柔軟な機能や考え方を学べるからです。
Pythonのリリースから間もなく30年になろうとしていますが、Python自体の開発ルールや、Pythonを発展させる上で気をつけられていることなど、開発コミュニティが持っている意思を組みとることも面白いでしょう。
5.これだけは知っておきたい!Pythonの5つの面白さ
本章では、知っておくと面白くなるPythonの以下の5つのポイントについてご説明します。
1. デコレーターと高階関数
まず最初に、デコレーターと高階関数から説明します。
Pythonは関数が書ける言語です。関数をオブジェクトとして扱えるので、関数を関数に渡す、関数が関数を返すといった高階関数の仕組みを書けるようになっています。
例えば複数の機能が同じ前提条件を要求する場合、前提条件を確認する共通機能と、それぞれの機能から前提条件を確認する共通機能を呼び出すプログラムの記述が必要です。 高階関数を書けるので、前提条件を確認する機能をそれぞれの機能から呼び出す書き方ではなく、前提条件を確認して「何かの機能を持った関数」を呼び出す書き方ができます。前提条件を確認する関数で各機能の関数をそれぞれラップすれば良いのです。 ですが、高階関数で書けるだけでは見た目が冗長です。
このような場合、高階関数はデコレーターという可視性の高い機能を使うことで、関数やメソッドをマークするような感覚で解決できます。
例えばスクレイピングをするオブジェクトがログイン済みかを確認し、ログイン済みであればスクレイピング作業に入り、終わった後に何かをする…というシーンがあるとしましょう。どのページでもスクレイピング以外の部分は同じことをしなければなりません。
以下にscrape_page_a、scrape_page_b、scrape_page_cという関数がありますが、これらはそれぞれ同じ意味を持ちます。
デコレーターと高階関数の理解のために、以下に例を掲載してご紹介します。
import os # osというモジュールを利用するために名前空間にosという名前でインポートしています
class Scraper: # Pythonのクラス宣言です
def __init__(self): # コンストラクタです。メソッド定義は第一引数にインスタンスが渡されます
self.is_logined = False # インスタンス(self)の変数をFalse(偽)で初期化しています
self.token = None # Noneは何も無いことを表します
def login(self, userid, password):
print('call Scraper login')
... # ピリオド3つは省略(ellipsis)を表します
self.is_logined = True
return True
def get(self, url, params=None): # key=... はキーワード引数。デフォルト値を設定できます
...
return
def post(self, url, parms=None):
...
return
def update_status(self):
print('call Scraper update_status')
...
# スクレイピングのコードに毎回書く
def scrape_page_a(scraper): # 関数定義
if not scraper.is_logined:
if not scraper.login(
os.environ.get('userid'),
os.environ.get('password'),
):
raise LoginException('login failed') # 例外を投げています
... # page_aのスクレーピング処理
print('call scrape_page_a')
scraper.update_status()
# チェックメソッドを毎回呼ぶ
def do_login(scraper):
if not scraper.is_logined:
if not scraper.login(
os.environ.get('userid'),
os.environ.get('password'),
):
raise LoginException('login failed')
def scrape_page_b(scraper):
do_login(scraper)
... # page_bのスクレーピング処理
print('call scrape_page_b')
scraper.update_status()
# デコレーターを使う
def need_login(fnc): # 関数を引数で受け取って関数内関数でラップした関数を返す高階関数
def _do_login(scraper):
if not scraper.is_logined:
if not scraper.login(
os.environ.get('userid'),
os.environ.get('password'),
):
raise LoginException('login failed')
result = fnc(scraper)
scraper.update_status()
return result
return _do_login
@need_login
def scrape_page_c(scraper):
... # page_cのスクレーピング処理
print('call scrape_page_c')
if __name__ == '__main__':
print('スクレイピングのコードに毎回書いたタイプ')
scraper = Scraper()
scrape_page_a(scraper)
print('チェックメソッドを毎回呼ぶタイプ')
scraper = Scraper()
scrape_page_b(scraper)
print('デコレーターを使うタイプ')
scraper = Scraper()
scrape_page_c(scraper)
scrape_page_aはコードを毎回愚直に書いた場合です。ログインの確認をして、ログインをしていなければ別の何かをする。ログインをしようとして失敗したら失敗のフローに移ります。
scrape_page_bは上記を関数に切り出して読み込み、そのページならではの処理をしてその後に後処理を書くといったことをしています。
scrape_page_cは関心事の関数だけを切り出し、その関数をログインの処理と後処理に対して渡し、渡された関数をその間で呼ぶ、といったことをしています。need_loginという関数が高階関数です。高階関数を利用するにはそのページならではの関数を定義してneed_loginの引数として呼び出して返ってくる関数を変数に格納して利用時にはその変数を呼び出します。通常の呼び出し方より見た目がわかりやすくなるように、Pythonはデコレーターという@を使うシンタックスを導入しました。
ここでは肝になるscrape_page_cという関数を@need_loginというデコレーターでデコレーションするイメージで書けるようになっています。
この機能のメリットは、関心事と共通して持っている性質を分けて書けること、そしてぱっと見で「ログインが必要であること」と「どういう処理になっているか」が分かることです。冗長な部分はほとんどありません。
デコレーターという発明は便利なので、Python以外の言語からも実装を希望する声をよく聞きます。 私もほかの言語で書いているときに、Pythonではデコレーターで書くような処理をわかりやすく書くにはどう書けば良いのかを識者に聞いたりしたりしていますが、その度にデコレーターの良さを実感します。
2. 名前空間
名前空間はPythonの禅(The Zen of Python)にも登場するほど大事なものです。Pythonの禅はPythonインタプリタ上でimport thisと入力すると表示されます。Pythonならではのジョークだとも言われていますが、個人的には非常に大事なことが書かれていると思っています。『パーフェクトPython』はPythonの禅を真面目に解説している数少ない書籍です(笑)。
名前空間は、最後の一文に出てきます。
多くのプログラミング言語はincludeという仕組みを採用しています。分割されたファイルに記載された変数や関数などは、includeをいくつか経由していても使えてしまうのです。
例えばa、b、cというファイルがあったときにbがaの何かをincludeしており、cはbをincludeしているといった場合、aで定義されている変数や関数はcでも使えてしまうということです。逆に言えば、bでaをincludeするのをやめた瞬間cは使えなくなってしまいます。同じ名前の変数を上書きしてしまう、ということも発生するかもしれません。
この問題を回避するために、クラスやモジュールといったもので閉じ込めようとしている言語もありますが、includeである限りはその名前空間自体がインクルードされ、最終的に1つのファイルに展開されてしまう点は変わりません。
ではPythonはどうかというと、グローバル変数はファイルというモジュール単位で閉じこもっています。
さらに明示的なインポートという形でどのファイルからどの名前を持ってくるのか、あるいはファイル自体を持ってくるのかといったようにどこから何を持ってきたのかが明示的になります。あるファイルを使っていても自動的に全て使えるわけではないので、名前空間は区切られているというわけです。非常にバグが起きにくいですし、どこでどの変数名や関数名が定義されているのかがひと目で分かる優れた機能です。
(modulea.py)
VAR1 = True
VAR2 = True
(moduleb.py)
from modulea import VAR1, VAR2 # modulea.pyからVAR1とVAR2をmoduleb.pyの名前空間にimport
(modulec.py)
from moduleb import VAR1 # moduleb.pyにimportされたVAR1をmodulec.pyの名前空間にimport
if __name__ == '__main__':
print(VAR1)
print(VAR2) # NameError: name 'VAR2' is not define ← modulec.pyの名前空間にはimportされていない
3. 演算子のオーバーロードと多重継承
誤りが起きやすいということで搭載していないプログラミング言語が多いのですが、Pythonはあえて採用しています。上手く使えれば非常に綺麗に書けるものです。
以下は算術演算子のオーバーロードをした例です。
class One:
def __add__(self, other): # + された時に呼び出されます
if not isinstance(other, self.__class__):
raise TypeError('Only One and One can be added')
return 2
if __name__ == '__main__':
one1 = One()
one2 = One()
print(one1 + one2) # 2
print(one1 + 1) # TypeError
addを定義しておくと、one1に足し算記号が発生した際にone2がaddのotherの部分に入ります。そこで予期している型かをチェックします。本来ならoneというものが値を持っていて、確認をした上で何か処理をして値を変える…ということができます。今回はoneが絶対に1ということで、oneとoneを足すと2が入る。one+one+oneになった瞬間にバグるので残念な例です(笑)。 そういう足し算を普通にできるという意味では非常に便利です。通常ならaddのようなメソッドを用意するところから始めなければなりません。なぜもともとある値型は+でいいのに、自分で定義した数値は+できないのか、といった一貫性のない状態を排除できるのです。
演算子だけではなく、シーケンスのスライスも同様の仕組みで記述可能です。DjangoのO/R Mapperにおいては、Pythonのリストを扱うようなイメージでデータベースの集合を操作できます。
多重継承については、いろいろなものを継承してしまうとどれを抱えているのかが分かりにくくなりますし、実際解決できないこともあります。しかし、多段の継承になってしまう、冗長になりロジックが重複してしまう、コンポジットで誤魔化すといったことになるのは、目的を見失っている感があります。継承を使っていろいろな機能をシンプルに入れることができる点自体は、非常に便利だということをお伝えしておきたいです。
4. ディスクリプター
これも少し変わった機能です。通常はgetter/setterをクラスの変数やプロパティごとに定義する必要があるのですが、ディスクリプターを使うとある変数へのset/get/deleteに対して機能を持たせられます。
ディスクリプターについての例を以下にご紹介します。
def absolute_integer(value):
if value is None:
return 0
if not isinstance(value, int):
raise TypeError
if value < 0:
return value * -1
return value
class AbsoluteInteger:
def __set__(self, instance, value):
instance.value = absolute_integer(value)
def __get__(self, instance, owner):
return instance.value
class SomeClass:
data1 = AbsoluteInteger() # インスタンスの属性ではなくてクラスの属性です
data2 = AbsoluteInteger()
if __name__ == '__main__':
var1 = SomeClass()
var2 = SomeClass()
var1.data1 = 3
var2.data1 = -2
print(var1.data1) # 3
print(var2.data1) # 2
setが属性に対して行われたとき、ディスクリプター側でsetというメソッドが起動されます。なのでvar1.data1=3としたときには=3でsetが呼ばれ、その中で渡されたものに1行目から始まる関数が適用されています。 何もなければ0をvar1のdata1に移し、0以下なら+にして格納する。こういったことを属性ごとではなく、ディスクリプターで設定できます。
DjangoのO/R Mapperのモデルは仕組みを深堀りして見ていくと非常に面白いと思います。
5. 速くする仕組み
Pythonは書きやすく柔軟な動作をする代わり、あまり動作が速くありません。遅すぎるということはありませんが、とても速いというわけでもないのです。
その対応として、高速に動作させたい箇所はC言語で記述できるAPIが標準で規定されています。並列処理を行うとなると今度はメモリを破壊するという問題が発生するのですが、そこで登場するのがGILという仕組みです。GILはPythonのVM上でプログラムが同時に動かないよう制限しているものです。
これは逆に言えば、CPUを一つしか使えないということでもあります。プロセッサが単一でタイムシェアリングをしている時代は良かったのですが、ムーアの法則が終わりを告げ、マルチコアやハイパースレッディングといったCPUを並列で動作させる時代ではCPUを使い切ることができません。
そこで今はPythonにmultiprocessingというモジュールが入っており、プロセス自体を分けることで並列動作させられるようになっています。 弱点を作ってしまったら、そこを補うものを用意する。そういった周辺の仕組みも面白さのひとつです。
私が自作したブログを生成するコードをご紹介します。
昔はブログ記事が増えてくるとファイルの読み込みに時間がかかっていたのですが、今はSSDのおかげで読み込みが非常に速くなりましたので、構造化テキストをHTMLに変換する部分を並列化したものです。
【ブログを生成するコード】
def glob_rst_documents(base_path):
pool = Pool(config.settings.multiprocess) # 指定数でワーカープールを作成しています
story_list = pool.map(
unmarshal_story, # 並列処理させたい関数
list(glob('{0}/**/*.rst'.format(base_path), recursive=True)) # 並列処理させる対象
)
pool.close()
pool.join() # 全ての並列処理が終わるのを待ちます
story_list.sort()
return story_list
複数のプロセスを使って、全ての文章を処理し終えたら次に進むというコードが記述してあります。全体を知りたい場合にはリポジトリを参照してください。
6. 最後に―Python学習の勧めとPythonのパラドックス
Pythonはかなり幅広い領域を扱えます。3DCGの世界ではツールの自動化スクリプトでデファクトとなっていますし、最近流行している機械学習はもちろん、数値計算、GUIプログラミングも簡単です。
Pythonista3というアプリを使えば、iPhoneやiPad上でPythonプログラムを動作させることもできます。IoTの分野に登場するArduinoなどでもPythonは動作します。
これだけの領域を扱える言語はそうそうありません。もちろんなんでもPythonでできるというわけではありませんが、新しい領域にチャレンジしたい、何かを体得したいと思ったときにまずはPythonで試しに書けるようになっておくというのは悪くない選択肢です。
ただ、Pythonだけを使えるというのはPython自体の面白さが少し薄れてしまうかもしれません。静的にコンパイルされるような言語も一つ習得することを前提にしつつ学んでみるのがいいかもしれません。ニュアンスとしては何かを試す時にPythonを使えるようになっておくと良い、という感じでしょうか。
Pythonで最後までプロダクトを作ることが悪いのではなく、PythonだけができるプログラマーはPythonっぽくないという感覚があります。
ハッカーと画家で有名なポール・グレアム氏の「Pythonのパラドックス」という有名な文章がありますが、そこでは企業はPythonを使える人を探したいのではなくても、Pythonを学ぼうとする人を探すべきだとしています。Pythonを使える人はほかの言語も大体使えるからです。Pythonにはこういうパラドックス的な考え方が、長い間流通しています。
最後に、「Pythonのパラドックス」書かれている一文を紹介して終わりにします。
「メジャーな言語をほとんど全て知っていて、彼自身のプロジェクトにはPythonを使っている友人がいます。彼がPythonを選んだのはソースコードの見た目が理由だそうです」
