2018/07/03

2018 年上半期に GitHub でスターの多かった Python リポジトリ

2018 年の前半があっという間に終わりましたね。

2018 年も後半に差しかかったので、 2018 年上半期に登場した GitHub リポジトリのうち多くのスターが付けられた Python 関連リポジトリを調べてまとめてみました。次の記事と同様のまとめです。


具体的には次の基準でリポジトリをピックアップしました。

  • GitHub に認識されているリポジトリの言語が Python
  • 2018 年 7 月時点でスター数が 2000 以上

ちなみに、 2018 年のまとめでは「スター数 2500 以上」を条件としましたが、今回は少し条件を緩めて 2000 以上にしました。理由は、 2018 年前半が終わってすぐに調べるとスターが付くのに十分な時間が経っておらずリポジトリ数が少なかったためです。

2018 年上半期にスター数の多かった GitHub リポジトリ


上の条件で検索した結果、ひっかかったリポジトリの数は 10 個です。

以下、スター数が多かったものから順にあげていきます。 description の翻訳とかんたんな説明をつけているので、興味のある方はご覧になってみてください。

FastPhotoStyle

画像の雰囲気を他の画像に写す機能を提供するライブラリ。「コンテンツ画像」と「スタイル画像」の 2 つの画像があったときに、スタイル画像のスタイル(色味)をコンテンツ画像に適用した画像を自動生成してくれるようです。

NVIDIA 社製で、ライセンスは CC BY-NC-SA 4.0 。

リポジトリ名FastPhotoStyle
説明Style transfer, deep learning, feature transform
説明(翻訳)スタイルの転写、ディープラーニング、特徴変換
URLhttps://github.com/NVIDIA/FastPhotoStyle
ホームページ-
スター8330

black

Python コードを整形する black コマンドを提供するライブラリ。設定値(≒自由度)を極力少なくすることで、シンプルに使えることを目指したフォーマッタです。

ライセンスは MIT 。

リポジトリ名black
説明The uncompromising Python code formatter
説明(翻訳)妥協なしの Python コードフォーマッタ
URLhttps://github.com/ambv/black
ホームページhttps://black.readthedocs.io/en/stable/
スター4538

Douyin-Bot

中国語で書かれておりよくわかりませんが、美人の画像を自動で探してくるボットのようです。

リポジトリ名Douyin-Bot
説明Python 抖音机器人,论如何在抖音上找到漂亮小姐姐?😍
説明(翻訳)-
URLhttps://github.com/wangshub/Douyin-Bot
ホームページhttps://zhuanlan.zhihu.com/p/37365182
スター4058

Python-100-Days

中国語で書かれておりよくわかりませんが、「 100 日で Python をマスターしよう」的な Python チュートリアルのようです。

リポジトリ名Python-100-Days
説明Python - 100天从新手到大师
説明(翻訳)-
URLhttps://github.com/jackfrued/Python-100-Days
ホームページ-
スター3856

AutoSploit

Metasploit のモジュールを使ってセキュリティ脆弱性を突いた攻撃を大量に行うためのライブラリ(悪用厳禁のもの)。

リポジトリ名AutoSploit
説明Automated Mass Exploiter
説明(翻訳)セキュリティ攻撃の自動化
URLhttps://github.com/NullArray/AutoSploit
ホームページ-
スター3096

vibora

Python 3.6+ の async 機能を利用した高速ウェブアプリケーションフレームワークです。

リポジトリ名vibora
説明Fast, asynchronous and elegant Python web framework.
説明(翻訳)高速・非同期のエレガントな Python ウェブフレームワーク。
URLhttps://github.com/vibora-io/vibora
ホームページhttps://vibora.io/
スター2599

minigo

説明のとおり、 AlphaGo を越える性能を発揮した AlphaGoZero を Python で実装したリポジトリとのことです。

リポジトリ名minigo
説明An open-source implementation of the AlphaGoZero algorithm
説明(翻訳)AlphaGoZero アルゴリズムのオープンソース実装。
URLhttps://github.com/tensorflow/minigo
ホームページ-
スター2188

Tensorflow-Project-Template

マシンラーニングのフレームワーク TensorFlow のプロジェクトの参考テンプレートです。 base model trainer data_loader utils 等のディレクトリを提案しています。

リポジトリ名Tensorflow-Project-Template
説明A best practice for tensorflow project template architecture.
説明(翻訳)TensorFlow プロジェクトのテンプレートアーキテクチャのベストプラクティス。
URLhttps://github.com/MrGemy95/Tensorflow-Project-Template
ホームページ-
スター2121

rebound

python コマンドの代わりに使用することで、 Python スクリプトの実行中のエラー発生時に Stack Overflow でエラーを検索した検索結果を表示してくれる rebound コマンドです。

リポジトリ名rebound
説明Command-line tool that instantly fetches Stack Overflow results when you get a compiler error
説明(翻訳)コンパイラエラーが出たときに Stack Overflow での検索結果をすぐに取得するコマンドラインツール
URLhttps://github.com/shobrook/rebound
ホームページ-
スター2116

gif-for-cli

アニメーション GIF 画像または Tenor ( tenor.com )というサイトの GIF 画像からアニメーションアスキーアートを生成するライブラリ。

Google 社製。

リポジトリ名gif-for-cli
説明-
説明(翻訳)-
URLhttps://github.com/google/gif-for-cli
ホームページhttps://opensource.googleblog.com/2018/06/tenor-gif-for-cli.html
スター2050

以上 10 つの Python リポジトリが 2018 年上半期では人気でした。

所感


2017 年に引き続き、マシンラーニング関連のリポジトリが多かった印象です。

Python にかぎらない話ですが、近年は中国語のリポジトリが目に見えて増えてきたのも印象的でした。いつか中国語リポジトリが上位の大半を占めるような日が来るのでしょうか。

個人的には、 vibora rebound あたりに興味があるので、チャンスがあれば試してみようと思います :)

2018/06/26

Python Tips:月の初日や最終日を取得したい

Python で、月の初日や最終日を取得する方法をご紹介します。

標準ライブラリを使った方法


月の初日を取得する

月の初日を取得するには、 datetime.datetimedatetime.datereplace() メソッドを使った方法が便利です。

import datetime

def get_first_day_of_month(date=None):
    '''指定された日付の月の最初の日を返す'''
    if not date:
        date = datetime.date.today()
    return date.replace(day=1)

テストを書いてみましょう。

import unittest

class TestGetFirstDayOfMonth(unittest.TestCase):
    def test_特定の日(self):
        date = datetime.date(2018, 12, 15)
        first_day = get_first_day_of_month(date)

        self.assertEqual(first_day, datetime.date(2018, 12, 1))

    def test_当日(self):
        first_day = get_first_day_of_month()
        today = datetime.datetime.today()

        self.assertEqual(first_day.day, 1)
        self.assertEqual(first_day.month, today.month)
        self.assertEqual(first_day.year, today.year)

上の 2 つのコード片をあわせたスクリプトを python -m unittest スクリプト名 で実行すると、テストがパスすることが確認できます。

月の最終日を取得する

月の最終日を取得する場合は初日の場合より少し複雑です。月によって最終日が変わるからです。

calendarmonthrange() 関数には月の最終日を返す機能が備わっているのでこれを使用するのがかんたんです。

import calendar
import datetime

def get_last_day_of_month(date=None):
    '''指定された日付の月の最終日を返す'''
    if not date:
        date = datetime.date.today()
    last_day = calendar.monthrange(date.year, date.month)[1]
    return date.replace(day=last_day)

calendar.monthrange() は、年と月を引数に取り、要素数 2 のタプルを返します。タプルの要素は、その月の初日の曜日を表す整数( 0 が月曜日)と、その月の日数です。

import calendar

calendar.monthrange(2020, 7)
# => (2, 31)
# ( 2020 年 7 月の 1 日は水曜日で、日数は 31 日)

calendar.monthrange(2020, 8)
# => (5, 31)
# ( 2020 年 8 月の 1 日は土曜日で、日数は 31 日)

こちらもテストしてみます。

import unittest

class TestGetLastDayOfMonth(unittest.TestCase):
    def test_4月(self):
        date = datetime.date(2020, 4, 7)
        last_day = get_last_day_of_month(date)

        self.assertEqual(last_day, datetime.date(2020, 4, 30))

    def test_8月(self):
        date = datetime.date(2020, 8, 10)
        last_day = get_last_day_of_month(date)

        self.assertEqual(last_day, datetime.date(2020, 8, 31))

    def test_うるう年の2月(self):
        date = datetime.date(2020, 2, 5)
        last_day = get_last_day_of_month(date)

        self.assertEqual(last_day, datetime.date(2020, 2, 29))

こちらも実行するとすべてパスすることが確認できます。

前月の最終日を取得する

前月の最終日を取得する方法についてはアプローチがいくつか考えられますが、最もシンプルでかんたんなのは「前月の最終日 = 今月の初日の前日」と考える形ではないかと思います。

import datetime

def get_first_day_of_month(date=None):
    '''指定された日付の月の最初の日を返す'''
    if not date:
        date = datetime.date.today()
    return date.replace(day=1)

def get_last_day_of_prev_month(date=None):
    '''指定された日付の前月の最終日を取得する'''
    if not date:
        date = datetime.date.today()
    first_date = get_first_day_of_month(date)
    return first_date - datetime.timedelta(days=1)

こちらもテストを書いてみます。

import unittest

class TestGetLastDayOfPrevMonth(unittest.TestCase):
    def test_4月(self):
        date = datetime.date(2020, 4, 7)
        last_day = get_last_day_of_prev_month(date)

        self.assertEqual(last_day, datetime.date(2020, 3, 31))

    def test_1月(self):
        date = datetime.date(2020, 1, 20)
        last_day = get_last_day_of_prev_month(date)

        self.assertEqual(last_day, datetime.date(2019, 12, 31))

こちらも python -m unittest で実行すると、すべてパスすることが確認できます。

以上は Python に同梱の標準ライブラリを使った方法でした。続いて標準ライブラリ以外のパッケージを使った方法を見てみましょう。

標準ライブラリ以外のパッケージを使った方法


さまざまなパッケージがあるかと思うのですが、ここでは名前とインタフェースがイケている Delorean (デロリアン)というパッケージを使った方法をご紹介します。 Delorean を使うとかんたんにタイムトラベル(時間の変更)を行うことができます。




from delorean import Delorean

月の初日を取得する

from delorean import Delorean

def get_first_day_of_month2(delorean):
    '''指定された日付の月の初日を返す'''
    return delorean.truncate('month')

Delorean オブジェクトは datetime.datetime をラップしたオブジェクトです。アトリビュート datedatetime で日付や日時を返してくれます。

テストすると次のようになります。

import unittest

class TestGetFirstDayOfMonth2(unittest.TestCase):
    def test_4月(self):
        d = Delorean(datetime.datetime(2020, 4, 10), timezone='Asia/Tokyo')
        d_first = get_first_day_of_month2(d)

        self.assertEqual(d_first.date.day, 1)

        tz_tokyo = pytz.timezone('Asia/Tokyo')

        # *1
        self.assertEqual(
            d_first.datetime,
            tz_tokyo.localize(datetime.datetime(2020, 4, 1)),
        )

        # *2: pytz.timezone を datetime.datetime() に渡して
        # 使うと時間がズレることがあり次は等しくならない
        self.assertNotEqual(
            d_first.datetime,
            datetime.datetime(2020, 4, 1, tzinfo=tz_tokyo),
        )

以下少し本題から外れた余談です。

pytz のタイムゾーンが謎の LMT 9:19 になる問題

*2 のコメントに書いているとおり、 pytz.timezonedatetime.datetime() に渡して使うと思わぬ挙動をすることがあるので、この方法で日時オブジェクトを生成してはいけません。代わりに *1 のように pytz.timezone.localize() を使う必要があります。

というのは、 pytz.timezonedatetime.datetime() に渡して使うと、 pytz.timezone('Asia/Tokyo') が指すタイムゾーンが JST+9:00 ではなく LMT+9:19 になることがあるためです。

import pytz

# JST じゃない何か変なのが出てきた
pytz.timezone('Asia/Tokyo')
# => <DstTzInfo 'Asia/Tokyo' LMT+9:19:00 STD>

# localize() 後のものは正しい JST になっている
pytz.timezone('Asia/Tokyo').localize(2018, 5, 27).tzinfo
# => <DstTzInfo 'Asia/Tokyo' JST+9:00:00 STD>

この原因はどうも、 pytz のタイムゾーンオブジェクトは実はタイムゾーンではなく場所を表していて、時代によって指し示すタイムゾーンが自動的に切り替わるように作られているらしいのですが、日時をまったく指定しなかったときのデフォルト値の問題のようです。このデフォルト値は pytz が使用している IANA のタイムゾーンデータベースの次の部分から来ているそうです。

# Zone  NAME    GMTOFF  RULES FORMAT  [UNTIL]
Zone  Asia/Tokyo  9:18:59 - LMT 1887 Dec 31 15:00u
      9:00  Japan J%sT}
# Since 1938, all Japanese possessions have been like Asia/Tokyo.

確かに LMT でほぼ 9:19 ですね。

ちなみに、この 1887 年というのは、日本の標準時が明石に定められた 1888 年の前年です。 pytz.timezone('Asia/Tokyo') は、自動的に、 1887 年 12 月 31 日までは LMT 、 1888 年 1 月 1 日以降は JST になります。

import datetime
import pytz

pytz.timezone('Asia/Tokyo').localize(datetime.datetime(1880, 5, 27))
# => datetime.datetime(1880, 5, 27, 0, 0, tzinfo=<DstTzInfo 'Asia/Tokyo' LMT+9:19:00 STD>)

pytz.timezone('Asia/Tokyo').localize(datetime.datetime(2030, 5, 27))
# => datetime.datetime(2030, 5, 27, 0, 0, tzinfo=<DstTzInfo 'Asia/Tokyo' JST+9:00:00 STD>)

このあたりは IANA のタイムゾーンデータベースや pytz のバージョンが更新されると変わる可能性があるようです。私がこの挙動を確認したときの pytz のバージョンは 2018.4 です。

このあたりの詳細に興味のある方は次のページ等をご覧になってみてください。


LMT 9:19 の意味や日本標準時について知りたい方には次のページ等が参考になります。



余談終わり。

続いて今月と前月の最終日です。これらはシンプルなのでテスト無しで関数だけ書いておきます。

月の最終日を取得する

from delorean import Delorean

def get_last_day_of_month2(delorean):
    '''指定された日付の月の最終日を返す'''
    return delorean.truncate('month').next_month().last_day()

前月の最終日を取得する

from delorean import Delorean

def get_last_day_of_prev_month2(delorean):
    '''指定された日付の前月の最終日を返す'''
    return delorean.truncate('month').last_day()

Ruby の gem でよく見られるような直感的でわかりやすいインタフェースですね。

...

以上、月の初日や最終日を取得する方法についてでした。

このあたりは使うときにはよく使うパターンなので、必要なときにサッと書けるようにひきだしとして持っておくとよいかと思います。

参考

2018/06/19

Python Tips: Chrome のブックマークを Python で確認したい

Python で Google Chrome のブックマークを確認する方法をご紹介します。

この記事を書いている時点では、私の Mac では Chrome のブックマークのデータは次の場所の Bookmarks というファイルに格納されています。
/Users/{ユーザ名}/Library/Application Support/Google/Chrome/Default/Bookmarks
Chrome のバージョンは 67.0.3396.87 です。

拡張子は付いていませんが中身は JSON テキストなので、そのまま普通の JSON ファイルとして取り扱うことができます。

データを読み込む


import json
import getpass

# `getpass.getuser()` でカレントユーザの名前を取得する
CHROME_BOOKMARK_PATH = (
    '/Users/{username}/Library/Application Support/'
    'Google/Chrome/Default/Bookmarks'
).format(username=getpass.getuser())


def get_chrome_bookmark_data() -> dict:
    '''Get the json of user's Chrome bookmark.'''
    with open(CHROME_BOOKMARK_PATH) as f:
        return json.load(f)


# JSON 内のデータを取得する
bookmark_data = get_chrome_bookmark_data()

print(type(bookmark_data))
# => <class 'dict'>

# ルートには 3 つの要素が入っている
print(bookmark_data.keys())
# => dict_keys(['checksum', 'roots', 'version'])

# checksum と version はメタ情報なので使わない
print(bookmark_data['checksum'])
# => 2f4a0ccbaba3f63a811870efbeff5dbb
print(bookmark_data['version'])
# => 1

# 実際のブックマークデータは `roots` の下に分かれて入っている
print(bookmark_data['roots'].keys())
# => dict_keys(['bookmark_bar', 'other', 'sync_transaction_version', 'synced'])

# 試しにブックマークバーのデータを表示する
bookmark_bar = bookmark_data['roots']['bookmark_bar']
print(bookmark_bar.keys())
print(bookmark_bar['name'])
for entry in bookmark_bar['children']:
    if entry['type'] == 'folder':
        print('{type}: {name}'.format(**entry))
    else:
        print('{type}: {name} - {url}'.format(**entry))
# =>
# url: ブックマーク1 - http://example1.com
# url: ブックマーク2 - http://example2.com
# folder: フォルダA
# folder: フォルダB
# folder: フォルダC
# ...

ルートにある辞書の下に roots というキーがあり、その下に bookmark_bar / other 等のグループに分かれて実際のブックマークデータが格納されています。 bookmark_bar は名前のとおりそのままブックマークバーのことで、 other はその他のブックマークを表すようです(公式のドキュメントが無いので、中身を見て判断しています)。

ブックマークの要素は type というキーを必ず持ち、これが folderurl のどちらかの値を持ちます。 folderurl のどちらかによって存在するその他のキーは異なります。私が見たかぎりそれぞれが持つキーは次のとおりになっていました。

folder:
  • id ブックマーク ID
  • type タイプ( folder
  • name ブックマークの名前
  • children 含まれるブックマークのリスト
  • date_added 作成日時を表す独自のタイムスタンプ?
  • date_modified 更新日時を表す独自のタイムスタンプ?

url:
  • id ブックマーク ID
  • type タイプ( url
  • name ブックマークの名前
  • url URL
  • date_added 作成日時を表す独自のタイムスタンプ?

id / type / name / date_added の 4 つについては共通しているようです。

私自身は大量のブックマークを手作業で確認したくなかったので、この方法で確認しました。同じようにブックマークをデータとして確認したい方の参考になれば幸いです。

CSV その他の形式に出力するもう少し本格的なスクリプトを GitHub に置いたので、興味のある方はよろしければ参考にしてください。


注意点として、間違って Bookmarks ファイルを上書きしてしまうと大変なことになる可能性があるので、参考にする際は 1) バックアップを取ってから触る、 2) 書き込みモードでは絶対にファイルを開かない、等の対策をするようにしてください。