2020/05/20

Python Developers Survey 2019 の調査結果

Python Software Foundation と JetBrains の Python 利用調査 2019


Python Software Foundation と JetBrains が毎年行っている Python 利用状況調査の 2019 年版の調査結果が先日公表されました。
 

調査には 47,000 人以上もの人が回答したそうです。

Python に興味がある人にはとてもおもしろい内容なので、まだチェックしていない人はぜひチェックしてみてください。前年との比較もできるようになっているので、 Python の最新のトレンドがよくわかります。  

調査結果は見やすく図表にまとめられているので直接上の調査結果のページをご覧いただくとよいと思います。英語が苦手な方のためにどんな質問項目が並んでいるのか意訳であげておくので参考にしてください(ヌケモレがあればごめんなさい):

 
  • 全般的な Python の使用状況
    • Python はメイン言語?サブ言語?
    • Python と他の言語の使用状況は?
    • ウェブとデータサイエンスで使う言語は?
  • Python の使用目的
    • Python を使う主な目的は何?
    • Python をどんなことに使っている?
    • 以下の活動にどのくらい関わっている?
    • Python を何のために使うことが最も多い?
    • データ分析とマシンラーニングを行っている人が自分のことをデータサイエンティストだと考えているか?
  • Python のバージョン
    • Python 3 と Python 2 どちらを使っている?
    • Python のバージョンごとのユースケースは?
    • 使用する Python 3 のバージョンは?
    • Python のインストール・アップグレードの方法は?
    • Python 環境の隔離方法は?
  • フレームワークとライブラリ
    • ウェブフレームワークは何を使っている?
    • データサイエンスのフレームワークとライブラリは何を使っている?
    • ビッグデータツールは何を使っている?
    • その他のフレームワークとライブラリは何を使っている?
    • ユニットテスト用フレームワークは何を使っている?
  • 技術とクラウド
    • ORM ライブラリは何を使っている?
    • データベースは何を使っている?
    • クラウドプラットフォームは何を使っている?
    • クラウドでどのようにコードを実行している?(本番環境)
    • クラウドのための開発はどのようにしている?
  • 開発ツール
    • OS は何を使っている?
    • CI (継続的インテグレーション)システムは何を使っている?
    • 構成管理ツールは何を使っている?
    • エディタと IDE は何を使っている?
    • Python 開発に利用しているツールと機能は?
  • 雇用と働き方
    • 雇用状況は?
    • 肩書きは?
    • Python の経験はどのくらい?
    • プロフェッショナルなコーディング経験はどのくらい?
    • チームでの仕事?単独での仕事?
    • 複数のプロジェクトで働いている?
    • チームのサイズは?
    • 会社のサイズは?
    • 会社の業界は?
    • 会社のターゲット業界は?
    • 年齢層は?

オリジナルはこちらです。
   
  • General Python Usage
    • Python as main vs secondary language
    • Python usage with other languages
    • Languages for Web and Data Science
  • Purposes for Using Python
    • For what purposes do you mainly use Python?
    • What do you use Python for?
    • To what extent are you involved in the following activities?
    • What do you use Python for the most?
    • Do those involved in data analysis or machine learning consider themselves data scientists?
  • Python versions
    • Python 3 vs Python 2
    • Python versions’ use cases
    • Python 3 versions
    • Python installation and upgrade
    • Python environment isolation
  • Frameworks and Libraries
    • Web frameworks
    • Data science frameworks and libraries
    • Big Data tools
    • Other frameworks and libraries
    • Unit-testing frameworks
  • Technologies and Cloud
    • ORMs
    • Databases
    • Top Cloud platforms
    • How do you run code in the cloud (in the production environment)
    • How do you develop for the cloud?
  • Development Tools
    • Operating systems
    • Continuous Integration (CI) systems
    • Configuration Management tools
    • Editors and IDEs
    • Tools and features for Python development
  • Employment and Work
    • Employment status
    • Job roles
    • Python experience
    • Professional coding experience
    • Working in a team vs working independently
    • Working on multiple projects
    • Team size
    • Company size
    • Company industry
    • Target industry
    • Age range

以下、私の個人的な注目ポイントにコメントしていきます。
   

Python をどんなことに使っている? ― What do you use Python for?

1 位「データ分析」 58% 、 2 位「ウェブ開発」 49% 、3 位「 DevOps ・システム管理・自動化スクリプト」 39% で、その後「マシンラーニング」「ウェブパーサー・スクレイパー・クローラー」が続きます。  

データ分析とマシンラーニングでよく使われているところもそうですし、幅広い用途で使われているところなんかは Python ならではな感じですね。用途の広さとかんたんさという点では現状対抗馬がいなさそうなので今後も Python 人気は続くかと思います。
   

Python を何のために使うことが最も多い? ― What do you use Python for the most?

1 位は「ウェブ開発」で 26% (昨年対比 -1pt )です。

日本では Python といえばプログラミング入門やデータ分析・マシンラーニングのイメージが強いですが(例えば日本では Python 書籍は数年前から増えてきましたが Django や Flask の書籍が少ないです)、世界的には Python でウェブ開発をするのは結構多いようです。
   

Python 3 と Python 2 どちらを使っている? ― Python 3 vs Python 2

調査の時点でサポート終了間近の Python 2 を使っている人がまだ 10% もいます。

私は Python 2 を使う機会はもうほとんどありませんが、 Python 2 の資産が多い会社などでは Python 2 は今後も使い続けられるのかもしれません。
   

Python 環境の隔離方法は? ― Python environment isolation

上位は Virtualenv 56% 、 Docker 33% 、 Conda 23% 、 Pipenv 21% です。

 私は Conda のことをあまりよく知りませんが、 Conda 利用が結構多いようです。私は Poetry をよく使っていて不満が無いので、 Poetry を使う人がもっと増えればいいのになぁと思います。
   

ウェブフレームワークは何を使っている? ― Web frameworks

1 位 Flask 48% 、 2 位 Django 44% 、 3 位 Tornado 5% となっています(数字はおそらくフレームワークを利用する人の中での使用率です)。

Flask と Django が Python のウェブアプリケーションフレームワークで最も人気なことは知っていましたが、その他のフレームワークとここまで差が開いているのは意外でした。 Flask 人気ですね。

注釈によると、 Django はウェブ開発者によく使われていて、 Flask はウェブ開発者以外のユーザーによく使われている傾向があるようです。 個人的には、日本では Flask は「初学者の学習」以外の用途ではぜんぜん使われてなさそうなイメージを勝手に持っているのですが、わりと使われているんでしょうか。
 

データサイエンスのフレームワークとライブラリは何を使っている? ― Data science frameworks and libraries

上位に Numpy ・ Pandas ・ Matplotlib 、続いて SciPy ・ SciKit-Learn ・ TensorFlow ・ Keras ・ Seaborn ・ PyTorch ・ NLTK の名前があがっています。

私はこのあたり詳しくないので、「ふむそうなんだ」という感じです。実際に使うときに参考にします。
 

ユニットテスト用フレームワークは何を使っている? ― Unit-testing frameworks

1 位 pytest 49% 、 2 位 unittest 30% 、 3 位 mock 15% で、その後に Tox ・ nose が続きます。

pytest は Python に同梱の unittest よりも多く使われているとのことです。私も pytest を使うことが多いですが、 pytest がこんなにも広く使われていたとは知りませんでした。 pytest は慣れると使いやすくてとても便利ですが、公式ドキュメントがわかりづらくとっつきづらい印象があります。日本語の情報は特に少ないと思うのですが、英語の技術書を読まない日本人の Pythonista の人たちはどこで pytest を学んでいるのでしょう……
 

ORM ライブラリは何を使っている? ― ORMs

1 位 SQLAlchemy 36% 、 2 位 Django ORM 32% がダントツの使用率で、その後は 4% とがくっと落ちて SQLObject ・ Peewee が続きます。その次の PonyORM になるとまたぐっと数字が下がります。

SQLAlchemy はおそらく Flask でデータベースを扱うときのデファクトスタンダードになっているから高いのではないかと思いますが、こんなにも使用率が高いことに驚きです。 GitHub のスター数等を見て、 SQLAlchemy のシェアは Peewee あたりとそう変わらないものと思っていました。 SQLObject は私はほとんど知りませんが、これも結構使われているのですね。
 

クラウドプラットフォームは何を使っている? ― Top Cloud platforms

1 位 AWS 55% 、 2 位 Google Cloud Platform 33% 、 3 位 DigitalOcean 22% 、 4 位 Heroku 20% 、 5 位 Microsoft Azure 19% です。昨年比では Google Cloud Platform が 2pt 、 Microsoft Azure が 4pt 上がり、 DigitalOcean ・ Heroku はシェアを下げたそうです。 この勢いが続けば 2020 年(来年)の調査では Amazon ・ Google ・ Microsoft がトップ 3 になりそうです。

Microsoft はソフトウェアエンジニアからの支持が年々高まっている感じがします。 ということで、 Python Software Foundation と JetBrains が毎年行っている調査の 2019 年版の結果のご紹介でした。 ちなみに、匿名化された回答データ(集計されていないもの)も Creative Commons Attribution 4 ライセンスで公表されているので、興味がある方はそちらも覗いてみるとおもしろいかもしれません( ページの下の方にリンク があります)。
 

クラウドでどのようにコードを実行している?(本番環境) ― How do you run code in the cloud (in the production environment)

1 位「コンテナ内」 47% (+7pt) 、 2 位「仮想マシン」 46% (-1pt) 、 3 位「 PaaS 上」 25% (-3pt) となっています。カッコ内は昨年比です。 昨年は 1 位が仮想マシンで 2 位がコンテナ内だったので、そこから順番が逆転しました。

本番環境でのコンテナ利用はこんなにも広まっているんですね。ここまでにもなると、プロフェッショナルのウェブエンジニアにとってコンテナはもはや必修科目と言ってもよいでしょう。
 

構成管理ツールは何を使っている? ― Configuration Management tools

1 位 Ansible 20% 、 2 位「独自のやり方」 9% 、 3 位 Puppet となっています。

Python 利用者へのアンケートだからか Ansible が 1 位ですね。
 

エディタと IDE は何を使っている? ― Editors and IDEs

1 位 PyCharm 33% 、 2 位 VS Code 24% 、 3 位 Vim 9% となっています。注釈によると VS Code は 2017 年に 7% でスタートして 2019 年には 24% になったとのことです。

PyCharm のシェアが高いですね。ただ、 Python Software Foundation と JetBrains が行った調査なので、 PyCharm の利用率が高いのはある意味当然かもしれません。 VS Code はずいぶん後発でしたが、完全に受け入れらた感じですね。 VS Code が登場したときは VS Code がここまで広まるなんて予想もしませんでした。来年は VS Code のシェアがもっと上がっていそうな感じです。
 

Python 開発に利用しているツールと機能は? ― Tools and features for Python development

「使っている」の割合が 70% 以上のものとして、以下の 8 つがあがっています。

  1. VCS
  2. リファクタリング 
  3. エディタの自動補完機能 
  4. Python virtualenv
  5. コードリンティング
  6. SQL データベース
  7. テストコード
  8. デバッガ 

おそらく「 VCS 」ニアリーイコール Git でしょう。「 SQL データベース」は他のものと少しレベル感が違うような気もします。

 プロフェッショナルのソフトウェアエンジニアになりたいと考えている学生さんや社会人の方は、 Python エンジニアとしてやっていくならこのあたりは使える必要があると思っておくとよいかもしれません(ただし、日本国内なら、これらを押さえてなくてもぜんぜん就職はできると思います)。

Python の経験はどのくらい? ― Python experience

Python 経験 5 年以内の人が 74% を占めています。

後↓の年齢層のところでも見ますが、この原因は回答者に若い人が多いのと Python が近年特に盛り上がっていることにあるのでしょう。
 

チームのサイズは? ― Team size

チームサイズは 2 - 7 人が 75% となっています。 小規模のチームが多いようです。確かに Python は「日本的システムインテグレーター」の大規模開発とは真逆のイメージがあります。
 

年齢層は? ― Age range

20 代が 42% といちばん多く、続いて 30 代 32% 、 40 代 12% 、それから 18-20 歳 7% と続いています。

 若い人が多いです。

ということで、 Python 利用調査のご紹介でした。ここに取り上げていない質問項目もあるので、興味のある方はぜひオリジナルの方を見てみてください。
 
参考
同様の調査として Stack Overflow が毎年実施している調査もあります。違った質問項目もたくさんあるので、こちらも興味がある人にはおもしろいと思います。

2020/04/30

Python Tips: Python のプロジェクトで GitHub Actions を使いたい

GitHub Actions

今回は Python のプロジェクトで GitHub Actions を導入する方法をご紹介します。実際に動くかんたんなサンプルを使って説明します。


GitHub Actions とは

最初に GitHub Actions についてかんたんに説明します。

GitHub Actions とは GitHub 公式のワークフロー自動化ツールです。

2018 年 10 月にパブリックベータ版が公開され、 2019 年の 11 月に一般公開されました。それまで GitHub で CI/CD を行うには外部のサービスを利用する必要がありましたが、 GitHub Actions が導入されたことでコード管理と CI/CD を GitHub 内で一元的に行えるようになりました。

GitHub Actions は、 GitHub で管理されているリポジトリにディレクトリ .github/workflows/ を作成してその下に自動化したい処理を記述した YAML 形式の設定ファイルを置けばすぐに利用し始めることができます。処理を実行するプラットフォームは Linux / macOS / Windows の中からひとつ(または複数)選ぶことができます。

GitHub Actions の特徴のひとつに、 GitHub や他の人が作成し GitHub 上に公開したアクションを利用できる点があります。たとえば、次の内容を設定ファイルに記述すると、 actions/checkoutactions/setup-python というアクションをかんたんに利用することができます。

name: pytest

on:
  push:

jobs:
  pytest:
    name: Run tests with pytest
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v2
      - name: Set up Python 3.7
        uses: actions/setup-python@v1
        with:
          python-version: 3.7

汎用度の高いアクションは誰かがオープンソースとして公開してくれていたりもするので、必ずしもすべての手順を自分でゼロから書く必要はありません。 CI/CD の経験があまり無い方やプログラミング初心者の方にも始めやすいかと思います。


Python プロジェクトでの GitHub Actions の使い方

Python プロジェクトでの GitHub Actions の使い方を説明します。

といっても、公式のドキュメントを読めば大体のことは書かれているので、スラスラと読んで理解できる方は公式のドキュメントを読むのがいちばんです。ここでは動くサンプルを使ってさわりの部分だけ説明します。

お話の前提として、 GitHub にアカウントを持っていて Python プロジェクトのリポジトリを置いているものとします。

GitHub Actions を使い始める方法は 2 つあります。

  • ブラウザで Actions のページを開く
  • ファイル .github/workflows/xxx.yml をコミットする

ブラウザで Actions のページを開くにはリポジトリの「 Actions 」タブをクリックします。ファイル .github/workflows/xxx.yml をコミットする場合は、手元のテキストエディタとターミナルをそのまま使います。どちらの形を選んでも最終的にできることは同じです。

以下、テストとスタイルチェックを行うかんたんなサンプルを使って説明します。次のツールを使用します。

  • poetry: 依存パッケージを管理する
  • pytest: テストを実行する
  • black: コーディングスタイルのチェックを行う

最初に GitHub 上に空のリポジトリを作成して、次の内容のファイル hello.py をコミットします。

hello.py:

"""関数 `hello()` を提供する"""


def hello(name: str = "world") -> None:
    """`Hello, xxx.` のメッセージを出力する"""
    print(f"Hello, {name}.")

続いて、テストファイルを作成します。 tests/test_hello.py あたりに作るのがよいでしょうか。

tests/test_hello.py:

from hello import hello


def test_hello_default(capsys):
    hello()
    out, err = capsys.readouterr()
    assert out == "Hello, world.\n"


def test_hello_with_name(capsys):
    hello("サザエ")
    hello("カツオ")
    out, err = capsys.readouterr()
    assert out == "Hello, サザエ.\n" "Hello, カツオ.\n"

これは pytest 用に作ったテストなので pytest をインストールします。私は Python のパッケージには poetry をよく使いますがここは pip でもそれ以外のものでもかまいません。

poetry add --dev pytest

pytest が無事にインストールできたら、 setup.py を作成してからテストを実行します。

setup.py:

from setuptools import setup, find_packages

setup(name="hello-github-actions", version="1.0", packages=find_packages())
poetry run pytest tests/test_hello.py
============================= test session starts ---===========================
platform linux -- Python 3.7.4, pytest-5.4.1, py-1.8.1, pluggy-0.13.1
rootdir: /app
collected 2 items

tests/test_hello.py ..                                                   [100%]

============================== 2 passed in 0.13s ---============================

成功することが確認できました。

続いて、このテストを GitHub Actions で実行するように設定ファイルを書きます。

リポジトリのルートに .github/workflows/ci.yml というファイルを作成し以下の内容で保存します。

.github/worflows/ci.yml:

name: pytest

on:
  push:
  pull_request:

jobs:
  pytest:
    name: Run tests with pytest
    # 実行環境として `ubuntu-latest` という名前のものを選ぶ
    runs-on: ubuntu-latest
    # 複数の Python のバージョンでテストするために `strategy.matrix` を設定する
    strategy:
      matrix:
        python-version: [3.7, 3.8]
    steps:
      # リポジトリをチェックアウトする
      # See: https://github.com/actions/checkout
      - name: Checkout
        uses: actions/checkout@v2
      # Python のランタイムをセットアップする
      # バージョンは `strategy.matrix` に並べたものを指定する
      # See: https://github.com/actions/setup-python
      - name: Set up Python ${{ matrix.python-version }}
        uses: actions/setup-python@v1
        with:
          python-version: ${{ matrix.python-version }}
      # Poetry そのものをインストールする
      # See: https://github.com/dschep/install-poetry-action
      - name: Install Poetry
        uses: dschep/install-poetry-action@v1.3
      # インストールした Poetry を使って必要な Python パッケージをインストールする
      - name: Install Dependencies
        run: poetry install --no-interaction
      # pytest を実行する
      - name: Run Tests
        run: poetry run pytest tests/

メインは jobs.pytest.steps の部分です。ここに指定したアクションが上から順に実行されます。

Python プロジェクトで GitHub Actions を使う場合はおそらく最初の 2 ステップ(チェックアウトと Python ランタイムのセットアップ)はほぼ共通になると思います。

各アクションがどういうことをしているかについてはインラインのコメントを見てください。

ちなみに、ディレクトリ .github/worflows/ はそのままこの名前にする必要がありますが、 ci.yml はこの名前でないといけないわけではありません。適当にわかりやすい名前を付けるとよいと思います。

ファイルが作成できたらコミットします。 poetry を使っている場合は pyproject.tomlpoetry.lock を、 pip を使っている場合は requirements.txt も忘れずにコミットしましょう。

コミットができたら GitHub に push します。すると、リポジトリの Actions のページに yaml ファイルで設定したワークフローが追加されていてテスト用のタスクが実行されるはずです。

これがうまく行ったら、おまけで black を使ったスタイルチェックも追加します。 poetry add をして、先ほどの .github/workflows/ci.yml を編集します。

poetry add --dev black

ファイルの末尾に以下の内容を追加します( black: の行が jobs の下に来るようにします)。インデントがおかしいと正しく動かないので注意してください。

  black:
    name: Check code style with Black
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v2
      - name: Set up Python 3.7
        uses: actions/setup-python@v1
        with:
          python-version: 3.7
      - name: Install Poetry
        uses: dschep/install-poetry-action@v1.3
      - name: Install Dependencies
        run: poetry install --no-interaction
      - name: Check code style with Black
        run: poetry run black --check --diff .

コードに問題がなければ、このワークフローも GitHub Actions で実行され、結果が success として報告されるはずです。

ということで、かんたんではありましたが、 Python プロジェクトでの GitHub Actions の使い方についてでした。 GitHub Actions について深く知りたい方は公式のドキュメントその他を確認していただければと思います。

今回使ったサンプルを少し整理して GitHub にあげているので、興味のある方は参考にしてみてください:

この記事を書いている時点で GitHub Actions は無料で利用できますが、金額等は時間が経つと変わると思うので、ここには載せません。 GitHub 公式の情報を確認してみてください。


参考リンク

2020/04/17

Python ライブラリ: Pony ORM

Pony ORM


今回は Python の ORM ライブラリである Pony ORM を紹介します。

動作確認に使ったバージョンは次のとおりです。

  • python 3.7.7
  • pony 0.7.12

Pony ORM とは

Pony ORM (以下「 Pony 」)は Python の ORM ライブラリです。 ORM をご存知でない方のために念のために説明すると、 ORM とはオブジェクト・リレーショナル・マッパーの略で、プログラミング言語の「オブジェクト」とリレーショナルデータベースのテーブルとの間のデータのやりとりをよきようにやってくれる機能のことです。

Pony の特徴として公式では次の 4 つが謳われています。

中でも目を引く特徴は直感的なシンタックスで、次のような Python らしいコードで SQL クエリを生成することができます。

select(c for c in Customer if sum(c.orders.total_price) > 1000)
Customer.select(lambda c: sum(c.orders.total_price) > 1000)

実際に使ってみましょう。

Pony をインストールする

pip でインストールします。

python -m pip install pony

インストールが完了したら Python のコンソールでバージョンが確認できるはずです。

import pony

print(pony.__version__)
# => 0.7.12

Pony の基本的な使い方

データベースに接続する・テーブルを定義する

Pony を使うときには、最初にデータベースオブジェクトを生成し、それを使ってテーブルに相当するクラスを定義した上で、そのクラスを使ってデータベースを操作します。

from pony import orm

DB_NAME = 'db.sqlite'

# データベースオブジェクトを生成する
db = orm.Database()
db.bind(provider='sqlite', filename=DB_NAME, create_db=True)


# データベーステーブルに相当するクラスを定義する
class Article(db.Entity):
    """記事データを格納するテーブル"""
    slug = orm.Required(str)
    title = orm.Required(str)
    body = orm.Optional(str)
    data = orm.Optional(orm.Json)

    def __str__(self):
        return 'Article(title="{}")'.format(self.title)

テーブルのカラムは主に pony.orm.Requiredpony.orm.Optional を使用して定義します。引数には対象のカラムの型を渡します。ここでの説明は割愛しますが、その他のカラム設定を行いたい場合は第 2 引数以降で指定することができます。

その他カラムの定義に使えるものとして次のクラスが用意されています。

  • PrimaryKey
  • Set
  • Discriminator

参考:

テーブルに相当するクラスの定義ができたら、続いて実際のテーブルを作成します。テーブルの作成は pony.orm.Databasegenerate_mapping() メソッドを使用します。

db.generate_mapping(create_tables=True)
テーブルにレコードを挿入する

テーブルが作成できたら、テーブルに相当するクラス( Article )を使ってレコードを登録することができます。

with orm.db_session:
    a1 = Article(slug='hello', title='こんにちは')
    a2 = Article(slug='good-bye', title='さようなら')
    orm.commit()

レコードを登録するには、データベースセッションを開いてテーブルに相当するクラスのインスタンスを生成してから、変更をコミットします。データベースセッションを開くにはコンテキストマネージャ pony.orm.db_session を使います。変更のコミットには pony.orm.commit() を使用します。コンテキストマネージャ pony.orm.db_session はコンテキスト脱出時に自動的にコミットを行ってくれるので、上の例の場合は pony.orm.commit() による明示的なコミットは不要です。

pony.orm.db_session は、上の例のようにコンテキストマネージャとして使う方法に加えて関数のデコレータとして使う方法が用意されています:

@orm.db_session
def update_related_data():
    ...

コンテキストマネージャとしてもデコレータとしても使える pony.orm.db_session ですが、上述の「変更のコミット」の他にも「例外があがったときのロールバック」「コネクションプールへのデータベースコネクションの返却」「データベースセッションキャッシュのクリア」等を自動で行ってくれる機能が備わっています。

テーブルのレコードを取得する

テーブルにレコードが登録できたらそれを取得してみましょう。おそらく Pony の最大の特徴はこのレコードの取得方法です。pony.orm.select() とジェネレータ式を使ってシンプル・直感的に SELECT クエリを生成することができます。

全レコードの全カラムを順番に取得する:

with orm.db_session:
    for article in orm.select(a for a in Article):
        print(article)
# => Article(title="こんにちは")
# => Article(title="さようなら")
クラスメソッド select() を使った方法も用意されています。
with orm.db_session:
    for article in Article.select():
        print(article)
# => Article(title="こんにちは")
# => Article(title="さようなら")

全レコードの特定のカラムだけ順番に取得する:

with orm.db_session:
    for slug, title in orm.select((a.slug, a.title) for a in Article):
        print(slug, title)
# => hello こんにちは
# => good-bye さようなら

全要素の特定のカラムを取得しリスト化する:

with orm.db_session:
    slugs = orm.select(a.slug for a in Article)[:]
    print(slugs)
# => ['hello', 'good-bye']

条件に合致する 1 レコードだけ取得する:

with orm.db_session:
    article = orm.select(a for a in Article if a.slug == 'hello').get()
    print(article)
# => Article(title="こんにちは")
with orm.db_session:
    article = Article.get(slug='hello')
    print(article)
# => Article(title="こんにちは")

get() は、対象のレコードが複数件見つかったときには MultipleObjectsFoundError という例外をあげます。

複数件マッチしうるクエリで最初の要素のみ取得したい場合は次の first() を使用します。

条件に合致する最初のレコードを取得する:

with orm.db_session:
    article = orm.select(a for a in Article if 'kkk' in a.slug).first()
    print(article)
# => Article(title="こんにちは")

first() は該当するレコードが 0 件だったときは None を返します。

ORDER BY 句を使う:

with orm.db_session:
    for article in orm.select(a for a in Article).order_by(Article.slug):
        print(article)
# => Article(title="さようなら")
# => Article(title="こんにちは")

WHERE 句を使う:

with orm.db_session:
    for article in orm.select(a for a in Article if a.title.startswith('さ')):
        print(article)
# => Article(title="さようなら")
with orm.db_session:
    for article in orm.select(a for a in Article).where(lambda a: a.title.startswith('さ')):
        print(article)
# => Article(title="さようなら")

WHERE 句はジェネレータ式の ifでも where() メソッドでも書くことができます( filter() というまた別のメソッドも用意されています)。

発行されるクエリを確認する:

with orm.db_session:
    print(orm.select(a for a in Article).order_by(Article.slug).get_sql())
# => SELECT "a"."id", "a"."slug", "a"."title", "a"."body", "a"."data"
# => FROM "Article" "a"
# => ORDER BY "a"."slug"

発行されるクエリを確認したいときは get_sql() メソッドが使えます。

レコードを操作する

データベーステーブルの各レコードに相当するインスタンスにも便利なメソッドが用意されています。

primary key を取得する:

article.get_pk()
# => 2

複数のフィールドの値をまとめてセットする:

article.set(body='sayonara sayonara', data={'message': 'onara'})

dict に変換する:

article.to_dict()
# => {'id': 2, 'slug': 'good-bye', 'title': 'さようなら', 'body': 'sayonara sayonara', 'data': {'message': 'onara'}}

これらの他にも SQL と Python にある程度馴染みがあれば直感的に使える機能が多数用意されています。

この記事を書いたときに確認したかぎりでは API は 1 ページにまとまっているので、「こんなもの無いかな」と思ったときにも探しやすいと思います。

ということで、 Python のライブラリ Pony ORM の紹介でした。実際に利用してみたいと思う方は公式のドキュメント・リポジトリを見てみてください。

参考:

2020/04/10

Python Tips: Python で FTP のダウンロードを自動化したい

Python を使って FTP でファイルのダウンロードを行う方法についてです。以前次のような記事を書きましたが、今回はそのダウンロード版です。
   

Python には FTP を使うためのそのままずばり ftplib というパッケージが同梱されており、これを使えば Python でかんたんに FTP を使ったファイル送受信が行なえます。しかし、提供されているインタフェースが低レイヤーな感じで、 FTP に詳しい人以外にはあまり直感的ではありません。特にフォルダを扱う処理はかんたんなものでも数行〜のコードを書く必要があります。 

今回はそんな ftplib を使ってファイルやフォルダをダウンロードする方法について説明します。アップロードについては上の記事に書いているので興味のある方は参考にしてください。
   

ファイルをダウンロードする

まずは単一のファイルをダウンロードする方法からです。
from ftplib import FTP_TLS


config = {
    'host': 'xx.xx.xx.xx',
    'user': 'username',
    'passwd': 'password',
}

# sample.txt ファイルをダウンロードする
with FTP_TLS(**config) as ftp:
    with open('sample.txt', 'wb') as fp:
        ftp.retrbinary('RETR sample.txt', fp.write)

ファイルをダウンロードするには ftplib.FTP_TLS (以下 FTP_TLS )のインスタンスを生成してサーバーに接続した上で retrbinary() メソッドを使います。

FTP_TLSftplib.FTP (以下 FTP )のサブクラスで TLS をサポートした FTP (いわゆる「 FTPS 」)を行うためのクラスです。いまどき FTP を使うべき理由も無いので原則 ftplib.FTP_TLS 一択と思ってよいでしょう。 FTP_TLS の初期化時には host user passwd の引数を渡します。

retrbinary() メソッドの第一引数には 'RETR ファイルパス' という形式の文字列を渡します。「ファイルパス」というのはサーバー側でのファイルパスです。パスセパレータには / が使えます。

retrbinary() の第二引数には保存先のファイルオブジェクトの write() メソッドを渡します。 retrbinary() はデータをバイナリモードで取得してくるので、保存先のファイルオブジェクトは open('ファイル名', 'wb') で開いておく必要があります。 ちなみに、 retrbinary() と似たメソッドに retrlines() というものもあり、こちらは ASCII モードでデータを取得します。対象のファイルがテキストデータであることがわかっている場合は retrlines() を使ってもよいかもしれません。
   

ファイル名に日本語を含むファイルをダウンロードする

ファイル名に日本語を含む(正確には文字コード latin-1 では正しく扱えない文字を含む)ファイルを扱う場合は注意が必要です。なぜなら FTP_TLS は( FTP も同じ)デフォルトで文字エンコーディングに latin-1 を使うからです。ファイル名に日本語を含むファイルを扱うときは適切なエンコーディングを使うように設定する必要があります。

 参考:

from ftplib import FTP_TLS


ENCODING = 'utf8'

config = {
    'host': 'xx.xx.xx.xx',
    'user': 'username',
    'passwd': 'password',
}

# サンプル.txt ファイルをダウンロードする
with FTP_TLS(**config) as ftp:
    ftp.encoding = ENCODING
    with open('サンプル.txt', 'wb') as fp:
        ftp.retrbinary('RETR サンプル.txt', fp.write)

ポイントは ftp.encoding = ENCODING の行です。ここでは UTF8 に設定していますがサーバーの設定に合わせて適切なものを使うようにします。
   

ディレクトリをダウンロードする

続いて特定のディレクトリ(=フォルダ)以下のファイルをまとめてダウンロードしたい場合です。 Python 3.8 の時点ではこの用途にそのまま使えるメソッドは用意されていないため「ファイルをリストアップしてひとつずつダウンロードする」処理を自分で書く必要があります。

次の関数 pull_files() は特定のディレクトリ以下のファイルを一式ダウンロードしてくるものです。
 
def pull_files(ftp: FTP_TLS, remote_dir: str, local_root: Path) -> None:
    """特定のディレクトリ以下のファイルを指定されたパスに一式ダウンロードする"""
    ftp.cwd(remote_dir)

    def get_full_path(path, filename):
        return '{}/{}'.format(path, filename)

    # BFS ですべてのファイルをダウンロードする
    paths = ['.']
    while paths:
        path = paths.pop(0)
        for filename, info in ftp.mlsd(path):
            # カレントディレクトリ or ペアレントディレクトリ
            if info['type'] in ('cdir', 'pdir'):
                continue

            # ディレクトリ
            if info['type'] == 'dir':
                paths.append(get_full_path(path, filename))

            # ファイル
            if info['type'] == 'file':
                full_path = get_full_path(path, filename)
                print(full_path)
                local_path = local_root / full_path
                local_path.parent.mkdir(parents=True, exist_ok=True)
                with local_path.open('wb') as fp:
                    ftp.retrbinary('RETR {}'.format(full_path), fp.write)

mlsd() は指定されたディレクトリ直下のファイル・ディレクトリ一式を返してくれるメソッドです。 nlst() dir() 等同様のメソッドがいくつか用意されていますが、サーバー側が対応していれば mlsd() が最も豊富な情報を返してくれるので使えるならこれを使うのがよいと思います。 pull_files() は次のように使用します。

from ftplib import FTP_TLS
from pathlib import Path


ENCODING = 'utf8'

config = {
    'host': 'xx.xx.xx.xx',
    'user': 'username',
    'passwd': 'password',
}

with FTP_TLS(**config) as ftp:
    ftp.encoding = ENCODING
    remote_dir = 'home'
    local_root = Path('archive').resolve()
    local_root.mkdir()
    pull_files(ftp, remote_dir, local_root)

以上です。

GitHub Gist に私が実際に作って使ったスクリプトを汎用化したサンプルを置いているので興味のある方はそちらも参考にしてみてください。
   

当然のことながら、中身を理解せずにコピペで利用するのはお控えください。参考にされる際は自己責任でお願いします :D

2020/02/29

サンプルコード:数独ソルバー



Image from Wikipedia

数独パズルを解くソルバーを Python で実装してみたので、考え方も含めてご紹介します。

きっかけは Awesome Python Newsletter で紹介されていた次の動画です。


Python の基本的な道具だけを使って数独を解く方法を順序立ててとてもわかりやすく説明しています。 programatically に数独を解いた経験のある方にとってはなんてことのないことかもしれませんが、やったことの無い方にはとても elegant でおもしろい動画だと思います。

ちなみに、今回説明する方法は上の動画の方法をきっかけ・参考にしていますが、コードの書き方・解き方は若干異なります。

数独をプログラムで解くときのポイント


おそらくここまで読み進めている方は数独がどんなものかはある程度ご存知だと思うので、数独とは何ぞやという説明は割愛します。

数独を解くアルゴリズムに関係するルールだけまとめると以下のとおりです。

  • R1. 縦横 9 x 9 のマス(以下「グリッド」)のそれぞれに 1 から 9 の数字を入れられる
  • R2. すべてのマスを埋めたら完成
  • R3. 同じ行、同じ列、 3 x 3 のブロックに同じ数字を入れることはできない
  • R4. 初期状態として、いくつかのマスに数字が入ったグリッドが与えられる

ということで、これをプログラムで解く方法を考えます。

データ構造


まずはデータ構造から。 9 x 9 のマスが必要なので、その状態を表せるデータタイプを用意します。

いちばんかんたんなのは、次のように要素が 1 - 9 の整数からなる 2 次元リスト(入れ子のリスト)を使うことです。空のセルは 0 で表します。

grid1 = [
    [5, 3, 0, 0, 7, 0, 0, 0, 0],
    [6, 0, 0, 1, 9, 5, 0, 0, 0],
    [0, 9, 8, 0, 0, 0, 0, 6, 0],
    [8, 0, 0, 0, 6, 0, 0, 0, 3],
    [4, 0, 0, 8, 0, 3, 0, 0, 1],
    [7, 0, 0, 0, 2, 0, 0, 0, 6],
    [0, 6, 0, 0, 0, 0, 2, 8, 0],
    [0, 0, 0, 4, 1, 9, 0, 0, 5],
    [0, 0, 0, 0, 8, 0, 0, 7, 9],
]

シンプルでわかりやすいですね。

ただ、これをそのまま使うよりは、ラップするデータクラスを作っておいた方が後が楽そうなので、今回はそれを用意することにします。

ROWS = COLS = 9
NUMBERS = [x for x in range(1, 9 + 1)]


class Grid:
    """数独のクイズを表すグリッド"""

    _values: List[List[int]]

    def __init__(self, values: List[List[int]]):
        assert isinstance(values, list)
        assert len(values) == ROWS
        for row in values:
            assert isinstance(row, list)
            assert len(row) == COLS

        self._values = values

アサーションをいくつか入れておくとケアレスミスで悩むのを防げそうです。

これは次のような形で使います。

grid1 = Grid([
    [5, 3, 0, 0, 7, 0, 0, 0, 0],
    [6, 0, 0, 1, 9, 5, 0, 0, 0],
    [0, 9, 8, 0, 0, 0, 0, 6, 0],
    [8, 0, 0, 0, 6, 0, 0, 0, 3],
    [4, 0, 0, 8, 0, 3, 0, 0, 1],
    [7, 0, 0, 0, 2, 0, 0, 0, 6],
    [0, 6, 0, 0, 0, 0, 2, 8, 0],
    [0, 0, 0, 4, 1, 9, 0, 0, 5],
    [0, 0, 0, 0, 8, 0, 0, 7, 9],
])

これで上の R1 ・ R2 のふたつを実現できそうです。続いてアルゴリズムを考えます。

アルゴリズム


アルゴリズムの全体的な流れは次のとおりです。

  • S0. パズルの状態を表すグリッドを渡される
  • S1. グリッドのすべての空のセルに対して、現時点で入りうる数字をすべて洗い出す
  • S2. 空のセルのうち、入りうる数字が最も少ないセルに仮に数字を入れる
  • S3. S2 のグリッドを使って 1 に戻る
  • S4. 空のセルがなくなったらパズルが解けたとみなす
  • S5. 入りうる数字が無い空のセルが残った場合はそのルートは間違っているので終了

これを実装することで R2 ・ R3 を満たせるはずです。

S3 の部分は再帰を使って書くとよさそうです。

S1 と S2 では、空のセルの抽出、そして入りうる数字のリストアップが必要です。ここをきちんと書かないとうまく再帰が回らずに無限ループに陥ったりします。

再帰の停止条件は S4 と S5 です。

続いてこれをコードで書いていきます。

実装


まずは全体の半擬似コードを書きます。

from typing import List, Set


def main():
    # グリッドを定義してから解く
    grid1 = [...]
    grid = Grid(grid1)
    results = solve_all(grid)
    for r in results:
        print(r)


class Grid:
    """数独のクイズを表すグリッド"""

    _values: List[List[int]]

    def __init__(self, values: List[List[int]]):
        assert isinstance(values, list)
        assert len(values) == ROWS
        for row in values:
            assert isinstance(row, list)
            assert len(row) == COLS

        self._values = values


def solve_all(grid: Grid) -> Set:
    """指定された数独に対する解を全件返す"""
    solutions = set()

    def _solve(grid: Grid):
        # S4. 空のセルがなくなったら正解として追加

        # S1. すべてのセルに対して入りうる数字をリストアップする

        # S2 + S3. 入りうち数字が最も少ないセルに仮に数字を入れて再帰

        # S5. 入りうる数字がひとつも無い空のセルがある場合はそのルートは間違いなので終了

    _solve(grid)

    return solutions


if __name__ == '__main__':
    main()

これでよさそうなので実際のコードを入れていきます。

from __future__ import annotations
from pprint import pformat
from typing import List, Set, Tuple

ROWS = COLS = 9
NUMBERS = [x for x in range(1, 9 + 1)]

def main():
    # グリッドを定義してから解く
    grid1 = [
        [5, 3, 0, 0, 7, 0, 0, 0, 0],
        [6, 0, 0, 1, 9, 5, 0, 0, 0],
        [0, 9, 8, 0, 0, 0, 0, 6, 0],
        [8, 0, 0, 0, 6, 0, 0, 0, 3],
        [4, 0, 0, 8, 0, 3, 0, 0, 1],
        [7, 0, 0, 0, 2, 0, 0, 0, 6],
        [0, 6, 0, 0, 0, 0, 2, 8, 0],
        [0, 0, 0, 4, 1, 9, 0, 0, 5],
        [0, 0, 0, 0, 8, 0, 0, 7, 9],
    ]

    grid = Grid(grid1)
    results = solve_all(grid)
    for r in results:
        print(r)


class Grid:
    """数独のクイズを表すグリッド"""

    _values: List[List[int]]

    def __init__(self, values: List[List[int]]):
        assert isinstance(values, list)
        assert len(values) == ROWS
        for row in values:
            assert isinstance(row, list)
            assert len(row) == COLS

        self._values = values

    def __hash__(self):
        """hashable 化するための __hash__ 定義

        - set() で利用するため
        """
        return hash(''.join(str(x) for row in self._values for x in row))

    def __str__(self):
        """`print()` で出力されたときの表現を定義する"""
        return '{}(\n{}\n)'.format(type(self).__name__, pformat(self._values))

    def solved(self) -> bool:
        """空セルがなくなったかどうかを判定する"""
        all_values = [x for row in self._values for x in row]
        return 0 not in all_values

    def possible_numbers(self) -> List[Tuple[int, int, List[int]]]:
        """すべての空セルと入りうる数字の組み合わせを全件洗い出す"""
        return [
            (row, col, self._possible_numbers_for_cell(row, col))
            for row, values in enumerate(self._values)
            for col, x in enumerate(values)
            if x == 0
        ]

    def clone_filled(self, row, col, number) -> Grid:
        """特定のセルに指定された値が入った新しい grid を返す"""
        values = [[x for x in row] for row in self._values]
        values[row][col] = number
        return type(self)(values)

    def _possible_numbers_for_cell(self, row, col) -> List[int]:
        row_numbers = [x for x in self._values[row]]
        col_numbers = [row[col] for row in self._values]
        block_numbers = self._block_numbers(row, col)

        return [
            x
            for x in NUMBERS
            if (x not in row_numbers)
            and (x not in col_numbers)
            and (x not in block_numbers)
        ]

    def _block_numbers(self, row, col) -> List[int]:
        row_start = (row // 3) * 3
        col_start = (col // 3) * 3
        return [
            x
            for row in self._values[row_start : row_start + 3]
            for x in row[col_start : col_start + 3]
        ]


def solve_all(grid: Grid) -> Set[Grid]:
    """指定された数独に対する解を全件返す"""
    solutions = set()

    def _solve(grid: Grid):
        # S4. 空のセルがなくなったら正解として追加
        if grid.solved():
            solutions.add(grid)
            return

        # S1. すべてのセルに対して入りうる数字をリストアップする
        possible_numbers = grid.possible_numbers()

        # S2 + S3. 入りうち数字が最も少ないセルに仮に数字を入れて再帰
        row, col, numbers = min(possible_numbers, key=lambda x: len(x[-1]))

        # S5. 入りうる数字がひとつも無い空のセルがある場合はそのルートは間違いなので終了
        if not numbers:
            return

        for number in numbers:
            next_grid = grid.clone_filled(row, col, number)
            _solve(next_grid)

    _solve(grid)

    return solutions


if __name__ == '__main__':
    main()

最大のポイントは _solve() 内の再帰の部分です。

for number in numbers:
    next_grid = grid.clone_filled(row, col, number)
    _solve(next_grid)

clone_filled() の中身は次のとおりにしています。

def clone_filled(self, row, col, number) -> Grid:
    values = [[x for x in row] for row in self._values]
    values[row][col] = number
    return type(self)(values)

Python のリストは再代入のときに参照渡しをするので、 _values を新しい Grid インスタンスと共用すると思わぬ挙動を招きかねません。そのためここでは意図して 2 次元リストを展開して別のリストを作成しています。


ちなみにここは標準ライブラリの deepcopy.deepcopy() を使うとよりシンプルに書けます。

他にも Grid クラスに特殊メソッド __hash__()__str__() を定義する等細かなポイントはいろいろありますが、興味のある方は読んでみてください。

上のコードを Python 3.7 で実行すると次のような出力が得られます。

Grid(
[[5, 3, 4, 6, 7, 8, 9, 1, 2],
 [6, 7, 2, 1, 9, 5, 3, 4, 8],
 [1, 9, 8, 3, 4, 2, 5, 6, 7],
 [8, 5, 9, 7, 6, 1, 4, 2, 3],
 [4, 2, 6, 8, 5, 3, 7, 9, 1],
 [7, 1, 3, 9, 2, 4, 8, 5, 6],
 [9, 6, 1, 5, 3, 7, 2, 8, 4],
 [2, 8, 7, 4, 1, 9, 6, 3, 5],
 [3, 4, 5, 2, 8, 6, 1, 7, 9]]
)

このパズルの場合は解はただひとつだけですが、例えば次のような空セルが多いパズルだと複数の解があるため、それが全件返されます。

grid2 = [
    [0, 0, 0, 0, 0, 0, 0, 0, 0],
    [6, 0, 0, 1, 9, 5, 0, 0, 0],
    [0, 9, 8, 0, 0, 0, 0, 6, 0],
    [8, 0, 0, 0, 6, 0, 0, 0, 3],
    [4, 0, 0, 8, 0, 3, 0, 0, 1],
    [7, 0, 0, 0, 2, 0, 0, 0, 6],
    [0, 6, 0, 0, 0, 0, 2, 8, 0],
    [0, 0, 0, 4, 1, 9, 0, 0, 5],
    [0, 0, 0, 0, 8, 0, 0, 7, 9],
]

出力結果:

Grid(
[[5, 3, 4, 6, 7, 8, 9, 1, 2],
 [6, 7, 2, 1, 9, 5, 3, 4, 8],
 [1, 9, 8, 3, 4, 2, 5, 6, 7],
 [8, 5, 9, 7, 6, 1, 4, 2, 3],
 [4, 2, 6, 8, 5, 3, 7, 9, 1],
 [7, 1, 3, 9, 2, 4, 8, 5, 6],
 [9, 6, 1, 5, 3, 7, 2, 8, 4],
 [2, 8, 7, 4, 1, 9, 6, 3, 5],
 [3, 4, 5, 2, 8, 6, 1, 7, 9]]
)
Grid(
[[3, 4, 5, 6, 7, 8, 9, 1, 2],
 [6, 7, 2, 1, 9, 5, 3, 4, 8],
 [1, 9, 8, 3, 4, 2, 5, 6, 7],
 [8, 5, 9, 7, 6, 1, 4, 2, 3],
 [4, 2, 6, 8, 5, 3, 7, 9, 1],
 [7, 1, 3, 9, 2, 4, 8, 5, 6],
 [9, 6, 1, 5, 3, 7, 2, 8, 4],
 [2, 8, 7, 4, 1, 9, 6, 3, 5],
 [5, 3, 4, 2, 8, 6, 1, 7, 9]]
)

以上です。ということで Python で数独を解くサンプルでした。

冒頭に紹介した動画が非常におもしろいので興味のある方はそちらも見てみてください。

参考


2020/01/06

Python Tips: Poetry の tips あれこれ



あけましておめでとうございます。

ここで言う Poetry とは Python のパッケージ管理ツールのことです。


Poetry はアプリケーション開発とパッケージ開発のどちらでも利用できるものですが、私は現状アプリケーション開発のみで Poetry を使っています。そのため、以下にあげるのはすべてアプリケーション開発で Poetry を利用するときの tips です。

この記事では Poetry のバージョン 1.0.0 が対象です。 Poetry はわりと安定している(=使い方がコロコロと変わりづらい)ツールですが、バージョンが上がるとやり方が変わる可能性があるので参考にされる際はご注意ください。

  • venv のファイルをプロジェクトディレクトリの下に置きたい
  • venv を作らずグローバルにパッケージをインストールしたい
  • アップデート可能なパッケージをチェックしたい
  • Poetry 自体をアップデートしたい
  • Docker コンテナでインストールしたい
  • どんなサブコマンドがあるのか忘れた
  • サブコマンドのオプションにどんなものがあるか忘れた
  • サブコマンドをタブ補完してほしい

venv のファイルをプロジェクトディレクトリの下に置きたい


→ 設定値 virtualenvs.in-project の値を true に変更します。

すべてのプロジェクトに適用する場合は次のとおりにします。

poetry config virtualenvs.in-project true

カレントプロジェクトにのみ適用する場合は --local オプションを付けます。

poetry config --local virtualenvs.in-project true

設定した値を初期状態にリセットしたいときは poetry config コマンドの --unset オプションを利用します。

グローバルな設定を削除:

poetry config --unset virtualenvs.in-project

プロジェクトローカルな設定を削除:

poetry config --local --unset virtualenvs.in-project

ちなみに、デフォルトでは Poetry は venv のファイルをホームディレクトリ以下の特定のディレクトリに集約して置くようになっています(私の手元の macOS 上で確認すると /Users/USERNAME/Library/Caches/pypoetry/virtualenvs でした)。

参考:

virtualenvs.in-project: boolean

Create the virtualenv inside the project's root directory. Defaults to false.


venv を作らずグローバルにパッケージをインストールしたい


→ 設定値 virtualenvs.create の値を false にします。

poetry config virtualenvs.create false

この設定が有用な場面はかぎられますが、このオプションの存在を覚えておくといざというときに便利です。

参考:

virtualenvs.create: boolean

Create a new virtual environment if one doesn't already exist. Defaults to true.


アップデート可能なパッケージをチェックしたい


poetry show コマンドの --outdated オプションを使用します。

poetry show --outdated

実際のアップデート処理を仮実行して確認したい場合は poetry update コマンドの --dry-run オプションが使えます。

poetry update --dry-run

参考:


Poetry 自体をアップデートしたい


poetry self update サブコマンドを使用します。

poetry self update

Poetry のバージョン 1.0.0 未満の頃は self:update というサブコマンドでしたが、変更されたようです。

万が一うまく行かない場合は公式の手順に従っていったんアンインストールしてからインストールし直すと解決する可能性があります。

Docker コンテナでインストールしたい


→ 単純に公式の手順に従ってインストールします。

公式の python:3.8 イメージの上にインストールする場合のサンプルは次のとおりです。

Dockerfile:



どんなサブコマンドがあるのか忘れた


→ サブコマンド無しで単に poetry と打つか help サブコマンドを使用します。

poetry

poetry help

サブコマンドのオプションにどんなものがあるか忘れた


poetry help コマンドにサブコマンド名を引数として渡します。

poetry help self

次のような内容が見やすい色付きで出力されます。

USAGE
      poetry self
  or: poetry self update [--preview] []

COMMANDS
  update
    Updates Poetry to the latest version.

                The version to update to.

    --preview            Install prereleases.

GLOBAL OPTIONS
  -h (--help)            Display this help message
  -q (--quiet)           Do not output any message
  -v (--verbose)         Increase the verbosity of messages: "-v" for normal output, "-vv" for more verbose output and "-vvv" for debug
  -V (--version)         Display this application version
  --ansi                 Force ANSI output
  --no-ansi              Disable ANSI output
  -n (--no-interaction)  Do not ask any interactive question

これを読んでもわからないことがあれば公式のドキュメントを読みます:


サブコマンドをタブ補完してほしい


poetry completions コマンドの出力を起動時のスクリプトに追加します。

公式で紹介されている macOS + Homebrew を使用している場合のサンプルは次のとおりです。

# Bash (macOS/Homebrew)
poetry completions bash > $(brew --prefix)/etc/bash_completion.d/poetry.bash-completion

参考:


古い設定値を削除したい


→ Poetry のバージョンアップによって使われなくなった設定値を削除するには、設定値が格納されたファイルを直接編集して該当行を削除します。これは poetry config --unset が効かない場合のみ使用します。

私が確認した環境( macOS / CentOS 7 )ではそのファイルは ~/.config/pypoetry/config.toml にありました。拡張子が示すとおり中身は TOML フォーマットです。

vi ~/.config/pypoetry/config.toml

以上 Poetry の tips 集でした。