Python
Pygments
python2.7
Python2
prompt_toolkit

Pythonのインタプリタを簡単に拡張して使いやすくする

More than 1 year has passed since last update.


Pythonのインタプリタを少しの手間で簡単に拡張して使いやすくする

Pythonには簡単にモジュールの使い方などを確認できるインタプリタがありますが、これを少しの手間で簡単に拡張して使いやすくすることができます。今回はPythonのインタプリタでPygmentsを使ってコードをハイライトさせてみましょう。

注:この記事では Python2.7.5 を使っています。


必要なライブラリ

インタプリタを拡張するにあたって、必要なライブラリが二つあります。

これらをpipでインストールしてください。

$ pip install prompt_toolkit

$ pip install Pygments


どうやって拡張するか

Pythonのインタプリタを拡張するためには、Pythonに標準で搭載されている code.py というモジュールを使います。

このモジュールについては、

これらの記事、ドキュメントを参照してください。まぁ、簡単に言うとcode.pyはPythonでPythonのインタプリタを実装したものです。このcode.pyのInteractiveConsoleクラスを継承することによって、インタプリタを拡張していきます。

code.pyのインタプリタ実行部分のコードはこんな感じです

def interact(self, banner=None):

"""Closely emulate the interactive Python console.

The optional banner argument specify the banner to print
before the first interaction; by default it prints a banner
similar to the one printed by the real Python interpreter,
followed by the current class name in parentheses (so as not
to confuse this with the real interpreter -- since it's so
close!).

"""
try:
sys.ps1
except AttributeError:
sys.ps1 = ">>> "
try:
sys.ps2
except AttributeError:
sys.ps2 = "... "
cprt = 'Type "help", "copyright", "credits" or "license" for more information.'
if banner is None:
self.write("Python %s on %s\n%s\n(%s)\n" %
(sys.version, sys.platform, cprt,
self.__class__.__name__))
else:
self.write("%s\n" % str(banner))
more = 0
while 1:
try:
if more:
prompt = sys.ps2
else:
prompt = sys.ps1
try:
line = self.raw_input(prompt)
# Can be None if sys.stdin was redefined
encoding = getattr(sys.stdin, "encoding", None)
if encoding and not isinstance(line, unicode):
line = line.decode(encoding)
except EOFError:
self.write("\n")
break
else:
more = self.push(line)
except KeyboardInterrupt:
self.write("\nKeyboardInterrupt\n")
self.resetbuffer()
more = 0

今回は、line = self.raw_input(prompt)この部分を書き換えることによって、インタプリタを拡張していきます。


手順① InteractiveConsoleクラスを承継したクラスを作る

from code import InteractiveConsole

class EnhancedPythonConsole(InteractiveConsole):

# とりあえずコピー&ペースト
def interact(self, banner=None):
"""Closely emulate the interactive Python console.

The optional banner argument specify the banner to print
before the first interaction; by default it prints a banner
similar to the one printed by the real Python interpreter,
followed by the current class name in parentheses (so as not
to confuse this with the real interpreter -- since it's so
close!).

"""
try:
sys.ps1
except AttributeError:
sys.ps1 = ">>> "
try:
sys.ps2
except AttributeError:
sys.ps2 = "... "
cprt = 'Type "help", "copyright", "credits" or "license" for more information.'
if banner is None:
self.write("Python %s on %s\n%s\n(%s)\n" %
(sys.version, sys.platform, cprt,
self.__class__.__name__))
else:
self.write("%s\n" % str(banner))
more = 0
while 1:
try:
if more:
prompt = sys.ps2
else:
prompt = sys.ps1
try:
line = self.raw_input(prompt)
# Can be None if sys.stdin was redefined
encoding = getattr(sys.stdin, "encoding", None)
if encoding and not isinstance(line, unicode):
line = line.decode(encoding)
except EOFError:
self.write("\n")
break
else:
more = self.push(line)
except KeyboardInterrupt:
self.write("\nKeyboardInterrupt\n")
self.resetbuffer()
more = 0


手順② インストールしたライブラリをimport

from __future__ import unicode_literals # prompt_toolkitを使うにあたってこの一行が必要です

import sys

from code import InteractiveConsole
from pygments.lexers import PythonLexer
from pygments.styles.monokai import MonokaiStyle

from prompt_toolkit import prompt as input_console
from prompt_toolkit.styles import style_from_pygments
from prompt_toolkit.layout.lexers import PygmentsLexer

上記のコードではpygments.stylesからMonokaiStyleをimportしていますが、各々好きなスタイルをimportしてください。

Pygmentsのスタイル一覧はこちら


手順③ コードを書き換える

手順①でコピペしたコードのline = self.raw_input(prompt)この部分を、以下のように書き換えます。

line = input_console(

prompt, lexer=PygmentsLexer(PythonLexer),
style=style_from_pygments(MonokaiStyle)
)

このコードを解説していきます。


Lexer

lexer=PygmentsLexer(PythonLexer),

Lexerは字句解析のことで、PythonLexerを指定することで「このプロンプトではPythonの構文を解析する」ということを指定してます。わざわざPythonLexerを指定していることから、いろんな言語のLexerが存在します。


style

style=style_from_pygments(MonokaiStyle)

style は文字通りプロンプトのハイライトを、MonokaiStyleで指定してます。

PygmentsではMonokaiのほかいろんなstyleがあります。

Pygmentsのスタイル一覧はこちら


手順④ 継承したクラスを実行する

今まではクラスのコードを書いただけですので、このままではインタプリタは起動しません。

if __name__ == "__main__":

console = EnhancedPythonConsole()
console.interact()

これでインタプリタが起動します。

今までのコードを合わせるとこんな感じになります。


enhanced_python.py

#!/usr/bin/env python

# coding: utf-8

from __future__ import unicode_literals

import sys

from code import InteractiveConsole
from pygments.lexers import PythonLexer
from pygments.styles.monokai import MonokaiStyle

from prompt_toolkit import prompt as input_console
from prompt_toolkit.styles import style_from_pygments
from prompt_toolkit.layout.lexers import PygmentsLexer

class EnhancedPythonConsole(InteractiveConsole):

def interact(self, banner=None):
"""Closely emulate the interactive Python console.

The optional banner argument specify the banner to print
before the first interaction; by default it prints a banner
similar to the one printed by the real Python interpreter,
followed by the current class name in parentheses (so as not
to confuse this with the real interpreter -- since it's so
close!).

"""

try:
sys.ps1
except AttributeError:
sys.ps1 = ">>> "
try:
sys.ps2
except AttributeError:
sys.ps2 = "... "
cprt = 'Type "help", "copyright", "credits" or "license" for more information.'
if banner is None:
self.write("Python %s on %s\n%s\n(%s)\n" %
(sys.version, sys.platform, cprt,
self.__class__.__name__))
else:
self.write("%s\n" % str(banner))
more = 0
while 1:
try:
if more:
prompt = sys.ps2
else:
prompt = sys.ps1
try:

line = input_console(
prompt, lexer=PygmentsLexer(PythonLexer),
style=style_from_pygments(MonokaiStyle)
)

# Can be None if sys.stdin was redefined
encoding = getattr(sys.stdin, "encoding", None)

if encoding and not isinstance(line, unicode):
line = line.decode(encoding)

except EOFError:
self.write("\n")
break

else:
more = self.push(line)

except KeyboardInterrupt:

self.write("\nKeyboardInterrupt\n")
self.resetbuffer()
more = 0

if __name__ == "__main__":

console = EnhancedPythonConsole()
console.interact()



実際に起動してみる

このコードをenhanced_python.pyとして保存し、実行してみましょう。

$ python enhanced_python.py

そうすると…

SnapCrab_NoName_2017-9-22_16-22-21_No-00.png

このようにインタプリタが起動しました!コードを入力すると…

SnapCrab_NoName_2017-9-22_16-27-8_No-00.png

ちゃんとハイライトされています!


prompt_toolkitについて

インタプリタを拡張するときに使ったprompt_toolkitについてですが、このprompt_toolkitは、Pythonのraw_inputよりもパワフルなプロンプトを作れるライブラリで、今回のようにプロンプトに入力する文字をハイライトしたり、入力したプロンプトの履歴を保存したり、保存した履歴からプロンプトをサジェストしたり、色々な機能がありますので、もし興味がありましたらぜひドキュメントを一読してみてください。(全部英語だけど)

prompt_toolkitで拡張したインタプリタの例として、自分が作ったPythonインタプリタのコードがgistにあります。

https://gist.github.com/alice1017/1a87bd6792d027a5ef2b06708d22ca71

今回は自分の環境の都合でPython2.7.5を使いましたが、もちろんPython3でも使えます。

皆さんも自分で好きなようにPythonインタプリタを拡張して使いやすくしましょう!