2017/12/31

2017 年 GitHub でスターの多かった Python リポジトリ

2017 年もいよいよ終わり、間もなく 2018 年ですね。

今年 1 年の振り返りのために、 2017 年にリリースされた人気の GitHub リポジトリについてまとめてみました。具体的には、「 GitHub に認識されているリポジトリの言語が Python で」「スター数が 2500 以上のもの」をリストアップしてみました。

結果、引っかかったリポジトリの数は合計 32 個です。個人的には、マシンラーニング(機械学習)関連のリポジトリが非常に多いのが印象的でした。みなさんはこのリストからどのような印象を抱かれるでしょうか。

以下私なりにかんたんな説明と description 翻訳を付けているので、興味のある方はご覧になってみてください。

1. system-design-primer


大規模システムの設計方法について紹介したドキュメントです。

リポジトリ名system-design-primer
説明Learn how to design large-scale systems. Prep for the system design interview. Includes Anki flashcards.
説明(翻訳)大規模システムの設計方法を学びましょう。システム設計面接の準備をしましょう。暗記用のフラッシュカード付きです。
URLhttps://github.com/donnemartin/system-design-primer
ホームページ
スター21763


2. cpython


CPython そのもののリポジトリです。 2017 年に GitHub に移設したようです。

リポジトリ名cpython
説明The Python programming language
説明(翻訳)Python プログラミング言語
URLhttps://github.com/python/cpython
ホームページhttps://www.python.org/
スター15030


3. face_recognition


画像内の人の顔を認識するためのライブラリです。

リアルタイムの顔認識もサポートしているようです。

リポジトリ名face_recognition
説明The world's simplest facial recognition api for Python and the command line
説明(翻訳)Python とコマンドラインで使う、世界で最もシンプルな顔認識 API
URLhttps://github.com/ageitgey/face_recognition
ホームページ
スター8444


4. pix2code


UI 画像からそれを生成するコードを自動生成するライブラリです。

README からリンクされているデモ動画がわかりやすいです。

pix2code - YouTube

現時点では出力フォーマットとして、 iOS アプリ、 Android アプリ、 HTML の 3 つがサポートされているようです。

まだ実用レベルにはなっていなさそうなので、このような技術が数年以内にコーダーの仕事をガラリと変える可能性は低そうですが、今の技術の進歩のスピードを見ると 10 年以内ぐらいには影響がありそうな気もします。

リポジトリ名pix2code
説明pix2code: Generating Code from a Graphical User Interface Screenshot
説明(翻訳)pix2code: グラフィカルユーザーインタフェーススクリーンショットからのコード生成
URLhttps://github.com/tonybeltramelli/pix2code
ホームページ
スター8024


5. python-fire


Python のユーティリティクラスをコマンドラインからすぐに呼び出せるようにできるライブラリです。

メソッド名と引数をコマンドラインで渡せばそのままクラスのメソッドが実行できる形のようです。

「 Google の公式なプロダクトではありません」と但し書きがついていますが、リポジトリは Google グループに所属しています。

リポジトリ名python-fire
説明Python Fire is a library for automatically generating command line interfaces (CLIs) from absolutely any Python object.
説明(翻訳)Python Fire はありとあらゆる Python オブジェクトに対してコマンドラインインタフェース( CLI )を自動生成するライブラリです。
URLhttps://github.com/google/python-fire
ホームページ
スター7655


6. ML-From-Scratch


説明のとおり、マシンラーニングのモデルとアルゴリズムを Python で実装したコード集です。

リポジトリ名ML-From-Scratch
説明Python implementations of Machine Learning models and algorithms from scratch. Aims to cover everything from Data Mining techniques to Deep Learning.
説明(翻訳)マシンラーニングのモデルとアルゴリズムの、ゼロベースでの Python 実装。データマイニング技術からディープラーニングまでのあらゆることをカバーすることを目指しています。
URLhttps://github.com/eriklindernoren/ML-From-Scratch
ホームページ
スター7598


7. pipenv


bundler や composer 、 npm といった他の言語のパッケージマネージャーと同等の機能を提供するツールです。

Python の virtualenv と pip を組み合わせて、プロジェクト固有の環境をシンプルに構築・管理できる機能を提供しています。

リポジトリ名pipenv
説明Python Development Workflow for Humans.
説明(翻訳)人間のための Python 開発ワークフロー。
URLhttps://github.com/pypa/pipenv
ホームページhttps://docs.pipenv.org/
スター6936


8. sonnet


複雑なニューラルネットワークを構築するための、 TensorFlow ベースのライブラリです。

Google が 2014 年に買収した DeepMind 社が提供しているようです。

リポジトリ名sonnet
説明TensorFlow-based neural network library
説明(翻訳)TensorFlow ベースのニューラルネットワークライブラリ
URLhttps://github.com/deepmind/sonnet
ホームページ
スター5698


9. gixy


Nginx の設定ファイルを静的に解析するためのツールです。

設定ミスなどを自動的に検出することを目的に作られているようです。

リポジトリ名gixy
説明Nginx configuration static analyzer
説明(翻訳)Nginx コンフィギュレーションのスタティックアナライザー
URLhttps://github.com/yandex/gixy
ホームページ
スター5001


10. trump2cash


アメリカのドナルド・トランプ大統領のツイートを見て、株取引を行うためのライブラリだそうです(笑)。

リポジトリ名trump2cash
説明A stock trading bot powered by Trump tweets
説明(翻訳)Trump のツイートで動く株取引のボット
URLhttps://github.com/maxbbraun/trump2cash
ホームページhttp://trump2cash.biz
スター4111


11. pytorch-tutorial


ディープラーニング用のライブラリ PyTorch のチュートリアルです。

リポジトリ名pytorch-tutorial
説明PyTorch Tutorial for Deep Learning Researchers
説明(翻訳)ディープラーニングの研究者のための PyTorch チュートリアル
URLhttps://github.com/yunjey/pytorch-tutorial
ホームページ
スター4079


12. wtfpython


一見直感的でないふるまいをする、トリッキーな Python スニペットのコレクションです。

リポジトリ名wtfpython
説明A collection of interesting, subtle, and tricky Python snippets.
説明(翻訳)Python の、おもしろかったりトリッキーだったりするスニペットのコレクション
URLhttps://github.com/satwikkansal/wtfpython
ホームページhttps://bit.ly/wtfpython
スター4046


13. inter


UI 用フォントです。

プログラムの部分で Python が使われているため GitHub 上では言語が Python ということになっていますが Python に限ったものではありません。

リポジトリ名inter
説明The Inter UI font family
説明(翻訳)inter UI フォントファミリー
URLhttps://github.com/rsms/inter
ホームページhttp://rsms.me/inter/
スター4045


14. wxpy


(中国語で書かれていて読めないのでスキップ)

リポジトリ名wxpy
説明微信机器人 / 可能是最优雅的微信个人号 API ✨✨
説明(翻訳)-
URLhttps://github.com/youfou/wxpy
ホームページhttp://wxpy.readthedocs.io
スター3850


15. TensorFlow-World


TensorFlow のチュートリアルです。

リポジトリ名TensorFlow-World
説明Simple and ready-to-use tutorials for TensorFlow
説明(翻訳)TensorFlow のシンプルで即使えるチュートリアル
URLhttps://github.com/astorfi/TensorFlow-World
ホームページ
スター3676


16. pysc2


Google が 2014 年に買収した DeepMind 社による、ゲーム「 StarCraft II 」の AI 学習環境です。

次のページがリリース記事のようです。

DeepMind and Blizzard open StarCraft II as an AI research environment | DeepMind

リポジトリ名pysc2
説明StarCraft II Learning Environment
説明(翻訳)StarCraft II の学習環境
URLhttps://github.com/deepmind/pysc2
ホームページ
スター3635


17. SerpentAI


ゲームをプレイするプログラムを作るためのフレームワークだそうです。

リポジトリ名SerpentAI
説明Game Agent Framework. Helping you create AIs / Bots to play any game you own! BETA
説明(翻訳)ゲームのエージェントのフレームワーク。あなたのどんなゲームでもプレイする AI 、ボッツを作成するのを助けます。 β 版です。
URLhttps://github.com/SerpentAI/SerpentAI
ホームページhttp://serpent.ai
スター3353


18. deepo


ディープラーニング用の Docker イメージです。

リポジトリ名deepo
説明A series of Docker images (and their generator) that allows you to quickly set up your deep learning research environment.
説明(翻訳)Docker イメージ(とそのジェネレーター)のシリーズです。これを使うと、ディープラーニングの研究環境を素早くセットアップすることができます。
URLhttps://github.com/ufoym/deepo
ホームページhttps://hub.docker.com/r/ufoym/deepo
スター3350


19. visdom


ウェブブラウザーベースのデータ可視化ツールです。

リポジトリ名visdom
説明A flexible tool for creating, organizing, and sharing visualizations of live, rich data. Supports Torch and Numpy.
説明(翻訳)ライブデータ、リッチなデータのビジュアルを作成・整理・共有するための柔軟なツールです。 Torch と Numpy をサポートしています。
URLhttps://github.com/facebookresearch/visdom
ホームページ
スター3310


20. seq2seq


機械翻訳に使用できる TensorFlow のエンコーダー / デコーダーのフレームワークです。

リポジトリ名seq2seq
説明A general-purpose encoder-decoder framework for Tensorflow
説明(翻訳)TensorFlow の汎用エンコーダー / デコーダーフレームワーク
URLhttps://github.com/google/seq2seq
ホームページhttps://google.github.io/seq2seq/
スター3217


21. sandsifter


x86 プロセッサーに対してファジング(ファズテスト)を行うためのツールのようです。

リポジトリ名sandsifter
説明The x86 processor fuzzer
説明(翻訳)x86 プロセッサーのファジングツール
URLhttps://github.com/xoreaxeaxeax/sandsifter
ホームページ
スター3201


22. WebHubBot


世界最大のアダルト動画サイトをクローリングするためのツールです。

動画のタイトル、動画の長さ、 mp4 リンク、 URL などを取得するそうです。

リポジトリ名WebHubBot
説明Python + Scrapy + MongoDB . 5 million data per day !!!💥 The world's largest website.
説明(翻訳)Python + Scrapy + MongoDB 。 1 日に 500 万データを取得。世界最大のウェブサイト。
URLhttps://github.com/xiyouMc/WebHubBot
ホームページ
スター3129


23. pygorithm


重要な基本的アルゴリズムを網羅した Python ライブラリです。

ソートや探索、経路探索、動的計画法、貪欲アルゴリズムといった、 CS の学生さんにお馴染みのさまざまなアルゴリズムのサンプルコードが入っています。

リポジトリ名pygorithm
説明A Python module for learning all major algorithms
説明(翻訳)すべてのメジャーなアルゴリズムを学ぶための Python モジュール
URLhttps://github.com/OmkarPathak/pygorithm
ホームページ
スター3124


24. awx


Ansible のウェブインタフェースを提供するためのライブラリのようです。

リポジトリ名awx
説明AWX Project
説明(翻訳)AWX プロジェクト
URLhttps://github.com/ansible/awx
ホームページ
スター3063


25. shadowbroker


クラッキンググループ Shadow Brokers が「 Lost In Translation 」という名前で公開した Windows などの脆弱性を突くコード(「エクスプロイト」)のようです。

リポジトリ名shadowbroker
説明The Shadow Brokers "Lost In Translation" leak
説明(翻訳)Shadow Brokers の「 Lost in Translation
」リーク
URLhttps://github.com/misterch0c/shadowbroker
ホームページ
スター3001


26. howmanypeoplearearound


同じ WiFi ネットワークを利用する人の数を数えるツールのようです。

リポジトリ名の howmanypeoplearearound は「 how many people are around 」の意味のようです。

リポジトリ名howmanypeoplearearound
説明Count the number of people around you by monitoring wifi signals
説明(翻訳)WiFi の信号をモニタリングしてあなたの周りにいる人の数を数えます。
URLhttps://github.com/schollz/howmanypeoplearearound
ホームページ
スター2981


27. baselines


強化学習アルゴリズムのベースを実装したコード集です。

研究者コミュニティがこれをベースに研究を進められることを目的に作られたようです。

リポジトリ名baselines
説明OpenAI Baselines: high-quality implementations of reinforcement learning algorithms
説明(翻訳)OpenAI Baselines: 強化学習アルゴリズムの高品質な実装
URLhttps://github.com/openai/baselines
ホームページ
スター2972


28. apistar


ドキュメント付きの API サイトを作成するためのフレームワークです。

リポジトリ名apistar
説明A smart Web API framework, designed for Python 3. 🌟
説明(翻訳)Python 向けに設計されたスマートなウェブ API フレームワーク
URLhttps://github.com/encode/apistar
ホームページhttps://discuss.apistar.org/
スター2938


29. pytorch-CycleGAN-and-pix2pix


画像から別の画像を自動生成する( image-to-image translation の)ためのアルゴリズムの Python 実装です。

CycleGAN と Pix2pix という、その筋では有名な画像変換のアルゴリズムを実装しており、 README では画像内の馬をしまうまに変えたり、線画を猫に変えたりするサンプル(オリジナル研究者が出したもの?)が紹介されています。

リポジトリ名pytorch-CycleGAN-and-pix2pix
説明Image-to-image translation in PyTorch (e.g. horse2zebra, edges2cats, and more)
説明(翻訳)PyTorch での画像変換(例: horse2zebra / edges2cats その他)
URLhttps://github.com/junyanz/pytorch-CycleGAN-and-pix2pix
ホームページ
スター2791


30. fashion-mnist


MNIST 風の、衣服の画像のデータベースです。

MNIST に馴染みの無い方に説明すると、 MNIST database というのは「 Modified National Institute of Standards and Technology database 」の略で、手書きの数字画像のデータベースのことです。マシンラーニングの研究や練習の定番として使われています。

リポジトリ名fashion-mnist
説明A MNIST-like fashion product database. Benchmark :point_right:
説明(翻訳)MNIST 風のファッションプロダクトデータベース。
URLhttps://github.com/zalandoresearch/fashion-mnist
ホームページhttp://fashion-mnist.s3-website.eu-central-1.amazonaws.com/
スター2774


31. Pokemon-Terminal


名前のとおり、ポケモンのターミナルのテーマ集です。

README に書いてあるのを読むと 719 体のポケモンのテーマがあるそうです。世界のポケモンですね。

任天堂(株式会社ポケモン)に許可を取っていないのではないかとも思いますが、おしゃれでかわいいです。

リポジトリ名Pokemon-Terminal
説明Pokemon terminal themes.
説明(翻訳)ポケモンのターミナルテーマ。
URLhttps://github.com/LazoCoder/Pokemon-Terminal
ホームページ
スター2657


32. ParlAI


Python で実装された、会話の AI 研究のフレームワークです。

ちなみに ParlAI は「パーレイ」と読むそうです。

リポジトリ名ParlAI
説明A framework for training and evaluating AI models on a variety of openly available dialog datasets.
説明(翻訳)さまざまな会話データセットに対する API モデルのトレーニングと評価のためのフレームワーク。
URLhttps://github.com/facebookresearch/ParlAI
ホームページparl.ai
スター2550


以上です。興味のある方は各リポジトリの README などをご覧になってみてください。

尚、スター数は 2017 年 12 月 31 日時点で確認した数字です。

違うブログですが、次の投稿では言語を制限せず「 2017 年リリースの GitHub リポジトリのうちスター数が 1 万を越えているもの」をリストアップしてみました。こちらも興味のある方はよろしければ。

2017 年リリースのスター 1 万越え GitHub リポジトリ | gotohayato

2017/12/21

Python Tips: Mac の .DS_Store を除外して zip ファイルを作成したい

Python で、 MacOS で自動作成される .DS_Store ファイルを除外した形で zip ファイルを作成する方法をご紹介します。

まずは .DS_Store とは何ぞやというそもそものところを確認した上で具体的な方法を見ていきましょう。

.DS_Store とは


MacOS を使っている方にはおなじみですが、 .DS_Store ファイルとは、フォルダのメタ情報を格納したファイルです。 MacOS に特有のもので、 GUI 関連の情報を格納しているようです。具体的には、アイコンの位置、背景画像などの情報が含まれるようです。

.DS_Store は Desktop Services Store の略で DS Store だそうです。

英語の Wikipedia の説明を一部抜粋します。

In the Apple macOS operating system, .DS_Store is a file that stores custom attributes of its containing folder, such as the position of icons or the choice of a background image. The name is an abbreviation of Desktop Services Store, reflecting its purpose. It is created and maintained by the Finder application in every folder, and has functions similar to the file desktop.ini in Microsoft Windows.

意訳:

Apple の OS macOS において、 .DS_Store はフォルダのカスタム属性を格納するファイルです。アイコンの位置や背景画像などの情報を格納しています。名前は Desktop Services Store の省略形です。 Finder アプリが各フォルダに対して作成・管理しており、 Microsoft Windows の desktop.ini に似た機能を持っています。



使用するもの


この .DS_Store ファイルを除外した形でディレクトリの zip ファイルを作成する方法ですが、いくつかのアプローチが考えられます。

今回は標準ライブラリのみを使った方法のうち、 zip ファイルを扱うためのライブラリ zipfile と Python 3.4 で導入された pathlib.Path を使う方法をご紹介します。

  • zipfile
  • pathlib.Path


コード


早速ですが、実際のコードを見てみましょう。

import zipfile
from pathlib import Path
from typing import Iterable, List


settings = {
    'verbose': False,
}


def compress_dir(zip_out: Path, directory: Path, skipped_names: Iterable[str]):
    '''指定されたディレクトリを指定された名前で zip ファイルに圧縮する
    '''
    if zip_out.exists():
        message = 'Specified zip file already exists: "{}".'.format(zip_out)
        raise CompressionError(message)

    with zipfile.ZipFile(str(zip_out.absolute()), 'w') as z:
        if settings['verbose']:
            print('Zip file has been initialized: "{}".'.format(z.filename))
        for f in iter_all_files(directory):
            if f.name in skipped_names:
                if settings['verbose']:
                    print('Skipped: "{}".'.format(f))
                continue

            z.write(f)
            if settings['verbose']:
                print('Added: "{}".'.format(f))


def iter_all_files(directory: Path) -> List[Path]:
    '''指定されたディレクトリの下にあるすべてのファイル( Path )を下層のものも含めて返すイテレータを返す
    '''
    if not directory.is_dir():
        message = 'Specified directory is not found: "{}".'.format(directory)
        raise CompressionError(message)

    return directory.glob('**/*.*')


class CompressionError(Exception):
    '''zip 圧縮プロセスで発生するエラーを表すカスタム例外
    '''
    pass

この関数 compress_dir() は、指定されたディレクトリ diretory を zip ファイル zip_out として圧縮する関数です。第 3 引数 skipped_names で指定された名前のファイルはスキップします。

これを次のように使用すると、 .DS_Store ファイルを除外した形でディレクトリ「圧縮対象ディレクトリ」を zip 圧縮することができます。

zip_file = Path('サンプル.zip')
directory = Path('圧縮対象ディレクトリ')
skipped_names = ('.DS_Store', )

try:
    compress_dir(zip_file, directory, skipped_names)
except CompressionError as e:
    # TODO 文脈によってより適切な例外処理に差し替える
    raise e

以下にポイントをいくつか取り上げてみます。

zipfile.ZipFile は Python 3.2 以降で context manager として使えるようになっているので、 with ... as ... 構文で使用しています。

カスタム例外の CompressionError は、次の 2 つのケースで上がるようになっています。

  • zip ファイルがすでに存在するとき
  • 指定されたディレクトリが存在しないとき


完全なスクリプトのサンプル


この compress_dir() を使ってスクリプトを作った場合のサンプルを GitHub Gist に置いたので、興味のある方はご覧になってみてください。 argparse を使って、コマンドラインの引数、オプション引数を受け付けるようにしています。



以上、 Python で Mac の .DS_Store を除外して zip ファイルを作成する方法についてでした。

この記事に興味のある方は次の記事も参考になるかもしれません。

Python Tips:zip ファイルをインターネットからダウンロードして利用したい - Life with Python

zip ファイルの zip ではなくて Python の組み込み関数の zip() の方に興味のある方は次の記事などが参考になるかもしれません。

Python Tips:複数のリストに対してループを回したい - Life with Python
Python Tips:長い方のリストに合わせて zip したい - Life with Python

2017/12/13

Python Tips:正規表現で複数行を扱いたい

Python で正規表現を利用するときに複数行マッチを行う方法についてご紹介します。

Python で正規表現といえば、標準ライブラリの re です。

import re


上の記事では re の下にある関数を使った正規表現の利用方法をご紹介しましたが、今回は re.compile() で取得できる正規表現オブジェクトを使った方法を用いてご説明していきます。

まず、おさらいの意味も込めて、通常の(デフォルトの)単一行にマッチさせる方法から見ていきましょう。

単一行マッチ


単一行のマッチの場合は、 re.compile() の第 1 引数に正規表現パターンを渡してそのまま search() メソッドなどを実行すれば OK です。

次のコードは、マークダウンテキストの中から見出しを抽出してリストを生成するスクリプトのサンプルです。

# coding: utf-8

import re
from io import StringIO


MARKDOWN_TEXT = StringIO('''
# Python Tips:正規表現で複数行を扱いたい

Python で正規表現を利用するときに複数行マッチを行う方法についてご紹介します。

Python で正規表現といえば、標準ライブラリの re です。

import re
[ライブラリ:re](/2014/10/re-regular-expression.html) 上の記事では re 直下にある関数を使った方法をご紹介しましたが、今回は re.compile() で利用できる「正規表現オブジェクト」を使った方法でご説明していきます。 まず、通常の(デフォルトの)単一行にマッチさせる方法から見ていきましょう。 ## 単一行マッチ ## 複数行マッチ ''') def main(): # 見出しを抽出する headings = extract_markdown_headings(MARKDOWN_TEXT) for level, text in headings: _print_list_item(level, text) def extract_markdown_headings(file): '''Markdown の見出しを抽出する ''' headings = [] pattern = re.compile(r'^(#+) (.+)$') for line in file: match = pattern.search(line) # (見出しレベル, 見出しのテキスト) の形で返す if match: headings.append((len(match.group(1)), match.group(2))) return headings def _print_list_item(level, text): '''リストアイテムを出力する ''' print('{}- {}'.format(' ' * (level - 1) * 2, text)) if __name__ == '__main__': main()

実用に近いコードにするために少し長めになっていますが、ポイントは関数 extract_markdown_headings() の中身だけです。

def extract_markdown_headings(file):
    '''Markdown の見出しを抽出する
    '''
    headings = []
    pattern = re.compile(r'^(#+) (.+)$')
    for line in file:
        match = pattern.search(line)
        # (見出しレベル, 見出しのテキスト) の形で返す
        if match:
            headings.append((len(match.group(1)), match.group(2)))

    return headings

re.compile() で「正規表現オブジェクト」を生成して、 search() メソッドで各行に対して対象のパターンの有無をチェックしています。

出力は次のとおりとなります。

- Python Tips:正規表現で複数行を扱いたい
  - 単一行マッチ
  - 複数行マッチ

search() の戻り値としては、対象のパターンが見つかった場合は正規表現の「マッチオブジェクト」というものが、見つからなかった場合は None が返されます。

続いて、複数行マッチの方を見ていきましょう。

複数行マッチ


複数行マッチの場合は、 re.compile() の第 1 引数に対象パターンを指定する点は単一行マッチと同じですが、第 2 引数にオプションフラグを指定する形になります。

例えば、マークダウンテキストの中から順序無しリストのグループを全件抽出したい場合は次のようにオプション re.MULTILINEre.DOTALL を指定します。

import re

pattern = re.compile(r'- .+?\n$', re.MULTILINE | re.DOTALL)
lists = []
for match in pattern.finditer(text):
    lists.append(match.group(0))

finditer() はパターンにマッチした部分を格納するマッチオブジェクトを返すイテレータオブジェクトです。戻り値には None などは含まれないので、ループ内で if 文で場合分けしたりする必要はありません。

上のコード片を含む、もう少し長いサンプルは次のとおりです。

# coding: utf-8

import re
from io import StringIO


MARKDOWN_TEXT = StringIO('''
# Python Tips:正規表現で複数行を扱いたい

Python で正規表現を利用するときに複数行マッチを行う方法についてご紹介します。

- これは
    - いわゆる
    - リスト
    - です

Python で正規表現といえば、標準ライブラリの re です。

import re
- これも - ある種の - リストです ''') def main(): # 順序無しリストを抽出する lists = extract_unordered_lists(MARKDOWN_TEXT) print(lists) def extract_unordered_lists(file): pattern = re.compile(r'- .+?\n$', re.MULTILINE | re.DOTALL) lists = [] for match in pattern.finditer(file.read()): lists.append(match.group(0)) return lists if __name__ == '__main__': main()

当然ですが、単一行マッチの場合のように文字列を事前に行単位で分割してしまうと複数行のパターンが抽出できないので、対象の文字列全体を finditer() に直接渡す必要があります。

このコードを実行すると、次のような出力が返されるはずです。

['- これは\n    - いわゆる\n    - リスト\n    - です\n', '- これも\n    - ある種の\n    - リストです\n']

マークダウンテキストの中に含まれる 2 つの順序無しリストのグループが無事抽出できることが確認できます。

ここで利用したフラグ re.MULTILINEre.DOTALL の意味合いはそれぞれ次のとおりです。

  • re.MULTILINE: 指定されると、 ^ が「文字列全体の先頭」と「各行の行頭」を、 $ が「文字列全体の末尾」と「各行の末尾」を意味するようになる。指定されなかった場合のデフォルトのふるまいは、 ^ は「文字列全体の先頭」、 $ は「文字列全体の末尾」を意味する。
  • re.DOTALL: 指定されると、 . が改行を含むあらゆる文字を意味するようになる。指定されなかった場合のデフォルトのふるまいでは、 . は「改行以外のあらゆる文字」を意味する。

いずれも短く書けるバージョンが用意されており、 re.MULTILINEre.M と、 re.DOTALLre.S と書くこともできます(個人的には、 S は馴れていないとパッと読んだときに意味がわからないので、省略しない re.DOTALL の方で書く方が好みです)。

ちなみに、 re.MULTILINEre.DOTALL の他にも、 re.compile() で指定できるオプションフラグには次のようなものがあります。

  • re.ASCII (re.A)
  • re.DEBUG
  • re.IGNORECASE (re.I)
  • re.LOCALE (re.L)
  • re.VERBOSE (re.X)

名前からイメージが付くものも多いですが、実際に使う際には公式のドキュメントを参照してみてください。

Regular expression operations — Python 3 documentation

Python の正規表現で複数行マッチする方法についてでした。必要なときにパッと使えるようにしておくと便利です。

2017/12/01

Python のタプルとリストの違い、タプルの使いどころ

今回は基本に立ち返って、 Python の組み込み型である「タプル」と「リスト」の違い、それと「タプルの使いどころ」について説明してみたいと思います。

「 Python タプル リスト 違い 」といったキーワードで Google 検索すると、多くのページで「リストとタプルは記法が異なります」「タプルはイミュータブルです」という説明だけがなされていて、それだけだとなぜタプルが用意されているのか、タプルはどんなときに便利なのかがわからないのではないかと思い、今回このテーマで書いてみようと思いました。

前提として、 Python におけるリストが何なのかというのは読者の方はご存知だという前提でお話ししていきます。

まずはおさらいに、そもそもタプルとは何ぞやというところからまずは見ていきましょう。

タプルとは


Python における「タプル」とは、複数のデータを一直線に並べた「コレクション」タイプのデータ型です。

tuple1 = ('東京都', '神奈川県', '大阪府')
type(tuple1) == tuple  # => True

tuple1[0]  # => '東京都'
tuple1[1:]  # => ('神奈川県', '大阪府')

見た目上はリストのかっこ []() に変わっただけです。定義の仕方や要素へのアクセス方法もリストとよく似ています。

ただし、タプルはリストとは異なり、いったんオブジェクトを生成した後に変更ができないオブジェクトとなっています。「変更ができない」というのは、厳密には「オブジェクト id を変えずに、要素を追加・変更・削除することができない」という意味です。この性質のことを「イミュータブル」( immutable )と言ったりします。ちなみに、イミュータブルの逆(変更ができるという意味の用語)は「ミュータブル」( mutable )です。

タプルとリストの違い


では、タプルはリストと何が異なるのでしょうか。

上で述べたばかりですが、「リストは変更ができるが、タプルは変更ができない」、これが両者の違いです。上の用語を使うなら、「リストはミュータブルであり、タプルはイミュータブルである」といいます。


「イミュータブル」が意味することとしないこと


タプルを利用する場合には、「タプルはイミュータブルである」ということばの意味を正しく理解しておく必要があります。

「イミュータブル」というのはあくまでも「オブジェクト id を変えずに要素を追加・変更・削除をすることができないこと」のみを表します。逆に言うと、次の 2 つはイミュータブルなオブジェクトでも特に問題なくできるので、ここを混同しないように注意してください。

  • a. タプルを参照している変数に再代入すること
  • b. タプルの中の要素がミュータブルな場合にその要素そのものを変更すること

ことばでの説明だけだと意味がわかりづらいので、以下、サンプルを使って説明してみます。

a. タプルを参照している変数に再代入すること

次の例を見てください。

t1 = ('ハート', '7')
t1 = ('ダイヤ', '10')

タプルは変更不可ですが、このコードは問題なく動作します。変数がタプルを参照していても、そこに別のオブジェクトを再代入することは可能です。

これは、ミュータブルかイミュータブルかというポイントというよりもむしろ「 Python には定数(再代入ができないもの)がない」ことと関係していると言えるでしょう。

紛らわしいのは、 += 演算を利用した場合の挙動です。次のコードを見ると一見タプルが変更可能なように見えると思うのですが、いかがでしょうか。

t1 = ('みかん', 'りんご')
t1 += ('バナナ', 'パイナップル')
print(t1)
# => ('みかん', 'りんご', 'バナナ', 'パイナップル')

このコードは問題なく動作します。実はここにはトリックがあって、 += の行では既存のタプルが変更されているのではなく、新しいタプルが生成されていて、オブジェクト id が変わっています。左辺の t1 に再代入されているのは新しく作られたタプルオブジェクトです。

同じ += の演算でも、リストの場合は挙動がまったく異なるので注意が必要です。リストの場合は、 += 演算を行っても元のオブジェクト id が維持されます。

# タプルは += の結果新しいタプルが生成される
t2 = t1 = ('みかん', 'りんご')
print(id(t1))    # => 4464365064

t1 += ('バナナ', 'パイナップル')
print(id(t1))    # => 4461411048
print(t1 is t2)  # => False

# 一方、リストは += で元のリストが維持される
l2 = l1 = ['みかん', 'りんご']
print(id(l1))    # => 4464250312

l1 += ['バナナ', 'パイナップル']
print(id(l1))    # => 4464250312
print(l1 is l2)  # => True

続いて b の方を見ていきましょう。

b. タプルの中の要素がミュータブルな場合にその要素そのものを変更すること

次のサンプルを見てください。

# 第 1 要素に dict を持つタプルを定義する
# (要素数が 1 のタプルを定義するときは () 内の末尾に , を入れる必要あり)
t1 = ({'mark': 'ハート', 'number': '7'}, )

# dict を変更する
t1[0]['mark'] = 'スペード'

print(t1)
# => ({'mark': 'スペード', 'number': '7'}, )

この例では、 dict を要素に持つタプル t1 を定義した後に、その dict を変更しています。 dict 型はミュータブルなので、このコードは特に問題なく動作します。

「タプルが持つ参照」を変更することと「タプルが参照するオブジェクトの中身」を変更することは別であって、タプルが禁止しているのは前者のみです。参照されたオブジェクトが dict 等のミュータブルなオブジェクトであれば当然そのオブジェクトの中身は更新することができます。このあたりを混同しないように注意してください。


タプルの使いどころ


では、なぜ Python にはリストとタプルというよく似た 2 つのコレクション型が用意されているのでしょうか。リストは他のプログラミング言語で「配列」と呼ばれるものに近く、多くのプログラマーにとってより直観的です。リストがあれば十分で、タプルは要らないのではないかとも思えます。

このあたりの疑問点はタプルの具体的な使いどころを見るとすっきり理解できるので、タプルを使うべき場面を具体的に見てましょう。

タプルの使いどころ 1: 変更を許可しない変数を定義する

変更を許可したくない変数を定義したい場合にタプルは便利です。

API_KEYS = ('xxx', 'yyy', 'zzz')

例えば、上のコードのように、外部サービスの API キーを格納したタプルを用意すると、再代入さえしなければ、中身が変わっていないことが保証されているという前提の下でその変数を使いまわすことができます。

関数型プログラミングに見られるような「思わぬ変更が生まれる可能性を最小化してバグの発生源を減らす」という考え方でプログラムを組みたい場合なんかにもタプルは便利です。

タプルの使いどころ 2: dict のキーに使う

dict のキーには hashable なオブジェクトのみが利用できます。

リストは hashable ではないので、 dict のキーに使うことはできません。

例えば、 Python で地図アプリのようなものを考えるとします。この場合、処理の途中で、緯度経度の情報をキーに、その建物の名前を値に入れた辞書を作りたくことがあるかもしれませんが、リストは hashable ではないので緯度経度の情報を格納するためには使えません。しかし、タプルは hashable なので、 dict のキーとして使用することができます。

# リストとタプルが hashable かどうかを確認する
from collections import Hashable

print(isinstance([], Hashable))  # => False
print(isinstance((), Hashable))  # => True

# 緯度経度の情報をキーに、建物名を値に入れた dict を用意する
locations = {
    (35.676, 139.744): '国会議事堂',
    (34.669, 135.431): 'ホグワーツ城',
    (35.039, 135.729): '鹿苑寺金閣',
}

dict 的なデータ型のキーに文字列しか受け付けないような言語の場合は、このような数字のペアに特別な意味がある場合でもそれをキーとすることはできませんが、 Python は組み込み型のタプルを使うことで比較的かんたんにこのようなロジックを実現することができます。

タプルの使いどころ 3: パフォーマンスをよりよくする

パフォーマンスの観点で、タプルはリストよりもよいようです。

ただし、よっぽどパフォーマンスが重要となるシビアな場面で無いかぎり、「リストじゃだめだ!やっぱりタプルじゃないと!」となるようなケースは稀なのではないかと思います。パフォーマンスの観点でデータ型を気にするのであれば、「リストかタプルか」の選択ではなく、もっと他の選択肢を選んだ方がよいような気がします。

・・・こんなところでしょうか。

ポイントは「変更ができないという特徴を活かす」「リストではできないことをやる」といったところになるかと思います。

Python のタプルについて理解を深めたいという方の参考になれば幸いです。

最後に、タプルの挙動を確認するユニットテストのサンプルコードを載せておきます。興味のある方は python3 -m unittest などで走らせてみてください。

# coding: utf-8

import unittest

class TupleBasicTest(unittest.TestCase):
    '''タプルの基本的な使い方を確認する
    '''

    def test_creation(self):
        # タプルを生成する
        t1 = ('ギン', 'ギラ', 'ギン')
        self.assertIsInstance(t1, tuple)

        # 実践的な場面で使うことはまずないが空のタプルを生成する
        t2 = ()
        self.assertIsInstance(t2, tuple)

        # 要素数が 1 のタプルについては `,` が必須となるため注意が必要
        t3 = ('さりげなく', )
        self.assertIsInstance(t3, tuple)
        s1 = ('さりげなく')
        self.assertIsInstance(s1, str)

    def test_access(self):
        # タプルの要素にアクセスする
        t1 = ('炎', '天', '下')

        self.assertEqual(t1[0], '炎')
        self.assertEqual(t1[1], '天')

        self.assertEqual(t1[-1], '下')

        self.assertEqual(t1[0:2], ('炎', '天'))

    def test_conversion(self):
        l1 = ('岡山', 15)
        self.assertIsInstance(tuple(l1), tuple)

        t1 = ('香川', 18)
        self.assertEqual(list(t1), ['香川', 18])

    def test_methods(self):
        self.assertEqual((3, 4, 4, 5).count(4), 2)
        self.assertEqual((3, 4, 4, 5).index(5), 3)

    def test_in(self):
        self.assertTrue(3 in (3, 4, 4, 5))
        self.assertFalse(108 in (3, 4, 4, 5))


参考

3. Data model — Python 3.6.3 documentation
I'm able to use a mutable object as a dictionary key in python. Is this not disallowed? - Stack Overflow