LoginSignup
148

More than 3 years have passed since last update.

データサイエンティストがPythonでシステム開発する前に読む記事

Last updated at Posted at 2020-05-24

広告運用自動化関連のプロジェクトで、他部署のデータサイエンティストがアルゴリズムの開発担当として、私がシステム担当兼レビュワーとしてアサインされました。

ただまだ分析以上のプログラミング経験はあまり無いそうで、知らない単語が芋づる式に出てくる状態のようです。それぞれの考え方を教えようにも、このコロナ影響下ではホワイトボードも使えないし、zoomのミーティングの時間を取るのもお互い負担が大きかったので、まずQiitaで共有することにします。

同僚に間違ったことを教えていたら困るので、もし誤っている箇所やアドバイスがあればコメント頂けると嬉しいです。

バッチプログラムの実行について

現在開発している広告運用自動化システムでは、AWS Data Pipelineを使っておおよそ以下のような環境で動いています。

  • 事前にソースコードをまとめてzipファイルにしてS3にアップロードしておく
    • この処理はリポジトリのmasterブランチにpushしたときにCI/CDツールが動いて自動で行っています
  • Data Pipelineの定期実行機能により、EC2インスタンスを立ち上げる
  • Data Pipelineが「S3からプログラムや依存ライブラリをインストールして実行する」シェルスクリプトを実行する
  • EC2インスタンスをシャットダウンする

もし今後Dockerを使ったシステムに移行した場合は「ソースコードをまとめてzipファイルにしてS3にアップロード」が「DockerイメージをビルドしてAmazon ECRにプッシュする」に変わります。少し余談ですが、「アプリ開発者自身がDockerfileでプロダクトに必要な言語やミドルウェアを記述できるようになり、インフラ部門に依存せずに素早く設定を更新できる」というメリットもあります。

そもそも、AWSのサービスが多すぎて「このサービスとこのサービスって何が違うんだ?」などと困る場面もあるかもしれません。私も苦労しました。AWSのソリューションアーキテクトのテキストの関連項目に目を通しておくことといいかもしれません。

また、現状では各バッチ処理を指定時間にcron実行しているだけで、連鎖的に失敗して再実行にも手間がかかっています。それをバッチの依存関係を明示して解決するのがAirflowやDigDagなどのワークフローエンジンです。(※AWS Datapipelineもワークフローとして実行するためのサービスなのですが、GUIの管理が煩雑でうまく設定できていません)

他の問題を優先して対処していたのですが、ずっと解決しないのも問題なのできちんと動こうと思っています。

開発用ツールについて

gitやpoetryなどについて質問された項目です。

Pythonやライブラリのバージョンについて

開発では「プロジェクトAでは最新のPython3.8を使いたいが、プロジェクトBではライブラリが対応してないのでPython3.7を使いたい」というような場合があります。

私は pyenv でPython本体のバージョンを切り替え、ライブラリは poetry を使って管理しています。次の記事を参考にしてください。

少し前まで poetry と同じ用途で Pipenvが流行っていたのですが、現在開発が滞っているようです。例えば「If this project is dead, just tell us」というISSUEが作られてしまっていました。(※ただし、先程確認したら2020年4月1日に2年越しにリリースされているようで、完全に開発が止まっているわけでは無さそうです)

poetryを利用すると pyproject.tomlpoetry.lock というファイルが生成されます。 pyproject.toml が利用しているPythonライブラリのバージョンなどのプロジェクトの設定で、 poetry.lock が実際にインストールした依存ライブラリのバージョンを固定するためのものです。それには以下のようなメリットがあります

Committing this file to VC is important because it will cause anyone who sets up the project to use the exact same versions of the dependencies that you are using. Your CI server, production machines, other developers in your team, everything and everyone runs on the same dependencies, which mitigates the potential for bugs affecting only some parts of the deployments.

(Google翻訳)このファイルをVCにコミットすると、プロジェクトを設定したすべてのユーザーが、使用している依存関係とまったく同じバージョンを使用するようになるため、重要です。CIサーバー、プロダクションマシン、チーム内の他の開発者、すべておよび全員が同じ依存関係で実行されるため、デプロイメントの一部にのみ影響するバグの可能性が軽減されます。

Pythonに限らず、他のプログラミング言語でも同じようなツールが存在します。例えばRubyのrbenvやBundler、node.jsならnvmやnpm(他にもいろいろあるらしい)などを使います。他言語でもツールごとに微妙にカバーしている範囲は違いますが、お互い影響を与え合って似ている部分も多く、例えば ***.lock が用意されていたりします。

コードのバージョン管理(git)について

gitについては長くなってしまうので入門記事を読んでください。最初は難解だと思いますが、「ブランチ」の必要性を理解すると自分で調べられるようになると思います。

基本的に「別のブランチで開発(コミット)して、リリース用ブランチにマージする」というスタイルで開発します。これにより複数人が別の機能を追加したいとき、同時に別ブランチで開発できます。

SubversionとGitの大きな違いはブランチ管理あると私は考えています。最近のソースコードのバージョン管理では、機能開発やバグ修正の専用ブランチを作ってリリース用ブランチへマージする開発スタイルが主流になってきており、開発プロジェクトにおいてコードのブランチ管理は必要不可欠であると言えます。

上記にある通り、git以前からSubversionというツールもあったのですが、ブランチの使い勝手が非常に悪いせいでほぼ駆逐されてしまったそうです。余談ですが、リーナス・トーバルズ(gitやLinuxカーネルの開発者の偉大なプログラマーだが、口が悪いことでも有名)が特にこの点でSubversionをこき下ろしているというニュースもありました。

「Github上でリリース用ブランチにマージすることを依頼する」のがよく聞くプルリクエストです。また、ブランチの運用方法にはgitflowやgithubflowなどがあり、今やってる広告運用自動化のシステムではgitflowを採用しています。

sshについて

ssh接続して作業することもあると思います。その際に、秘密鍵のほうを絶対に他人に漏らさないように気をつけてください。

公開鍵認証において、サーバに登録するのはユーザの公開鍵です。そして、認証時には秘密鍵そのものではなく、生成した署名データ、それも流用困難なものをサーバに提示します。そのため第三者はもちろんサーバであっても秘密鍵を入手したり、署名を悪用することは困難です。

実装について

Pythonのコードを実装する際に気をつける点をまとめておきます。

ドキュメンテーションや型ヒントについて

他の人が読みやすい状態にしておく。ひとまずこれやっといてください。

  • README.mdをきちんと書く
  • Docstrings(関数にあるコメント)をきちんと書く

Docstringsも何種類かスタイルがあり、numpyスタイルやGoogleスタイルなどがあります。自分たちはGoogleスタイルを採用しています。

私はVSCodeのプラグインのPython Docstring Generatorで補完するように設定しています。他のIDEやエディタでも同様のツールがあると思います。

型ヒントも基本的にはドキュメンテーションの役割だけを果たしています。そのためコードの実行時に間違った型を入れてもエラーが出ません。

def greeting(name: str) -> str:
    return f"Hello, {name}"

print(greeting(1))
# => "Hello, 1"

「基本的」というのは、 mypy という静的解析ツールで型情報が間違ってないか自動でテストを行うことができることと、一部のライブラリ(後述するpydanticなど)では実行時に型の情報を使う場合もあるからです。

型ヒントを使う際は、 List[str] だとか Dict[str, str] などの記述方法を知っておく必要があります。余裕があるときに次の記事を読んでおくと良いです。

ロギングについて

私のコードの中に logging を使ったコードがあり、Pythonの公式ドキュメントのLogging HOWTOも圧があるので困ってしまったと思います。

簡単に説明すると、 print 関数に比べてログを残す際に便利な以下のような機能があります。

  • 出力先(ログファイル、標準出力)を設定できる
  • ログレベル(DEBUG, INFO, ERRORなど)を切り分けられる。また「開発環境ではDEBUGを表示するが、本番ではINFO以上にする」などの切り捨て設定もできます。

より具体的には以下の記事を参考にすると良いと思います。

ただ、私も開発時は厳密にやっているわけではなく、実装時のデバッグは print を使って必要なら後で logging を使った形に置き換えるように実装しています。

アプリケーションの設定について

アプリケーションの設定(どのS3パスを使うとか、不動産マーケットを実行するかとか)は以下のような方法で行います。

  • 環境変数にセットする
  • コマンドライン引数で渡す
  • 設定ファイル(jsonやyamlなど)を用意する

環境変数を使う

アプリケーションの設定は、基本的に環境変数にセットします。Twelve-Factor Appという有名なアプリケーション開発の指針にこの項目があります。

アプリケーションの 設定 は、デプロイ(ステージング、本番、開発環境など)の間で異なり得る唯一のものである。設定には以下のものが含まれる。

  • データベース、Memcached、他のバックエンドサービスなどのリソースへのハンドル
  • Amazon S3やTwitterなどの外部サービスの認証情報
  • デプロイされたホストの正規化されたホスト名など、デプロイごとの値

こうすることで、いろいろとメリットがあります。実感しやすいのだと以下のようなものです。

  • 開発環境と本番環境でデータソース(S3、RDB、BigQuery...)を切り替えたい場合にも、コードを変更せずに実現できる
  • 秘匿情報がコード内に現れないので安全で、OSSとしても公開できる

Pythonのコードでは普通、環境変数は os.environ を使って以下のようなコードを書くことになります。

import os
SOME_VALUE = os.environ['SOME_VALUE']

# 数値のリストがほしいときなど、少し型変換が必要だったり煩雑なんですよね…
SOME_INT = int(os.environ["SOME_INT"])

ただ、私は最近 pydantic というライブラリを使っています。それを使うと以下のようなコードになります。

from pydantic import BaseSettings

class Settings(BaseSettings):
    some_value: str
    some_int: int # 自動でintに変換してくれる

settings = Settings()

開発時に .env というファイルを利用することがあり、自分の使っているMacの環境変数をセットせずに、一時的にファイルから読み込むことができます。ちなみにpydanticも.envの利用もサポートしているので便利です。

ここで実際に私が実装したコードを見て、「pydanticで環境変数を読み込んでいるのに、osモジュールがimportされているのはなぜだろう」と疑問に思ったと思います。それは消し忘れなので後で隠滅しておきます。

また、今のシステムで動いているコードでも、環境変数として切り出すべき設定項目がコード内に含まれてしまっているものも多いです。他のコードを参考にする場合は気をつけてください。

コマンドライン引数を使う

環境変数との使い分けは明確な基準を持っていたわけではありませんが、コマンドライン引数と環境変数の使い分け基準という記事を見つけました。今回実装している広告運用費のアラート機能では、「開発時に賃貸マーケットごとだけ実行して確認する」みたいな用途も多そうだったのでコマンドライン引数で指定するようにしています。

Pythonの標準にも argparse というライブラリがありますが、多くのPythonプログラマーは clickを使っています。

少し余談ですが、awesome-pythonというリポジトリにPythonの便利ライブラリのうち、 click のような評判の良いものの情報がまとまっています。他のプログラミング言語に挑戦する場合もawesome-xxxで調べると大抵同じような情報が見つかるでしょう。

テストコードについて

今回は単純な通知処理なのでテストコードはサボってしまいましたが、各モジュールごとにユニットテストを行います。標準ライブラリと unittest とサードパーティ製の pytest がありますが、私は使い勝手の面で pytest をおすすめします。

あと、toxというツールも見つかると思いますが、「複数のPythonバージョンでpytestやunittestを実行するためのツール」であり、特定のシステム上で動かす場合はPythonバージョンは固定されているはずなので今は考えなくていいです。ライブラリを自作するときに導入を考えればいいです。

境界値分析などの知識が多少必要となります。コロナ騒ぎが明けたら、QAの人にこの本を借りると良いと思います。

その他、プログラミング全般で気をつけること

コードレビューで教えます。書籍であれば、このあたりがおすすめです。

また、こちらの本もピッタリかもしれません。今回WEBサーバーの話は全く書いていませんが、この本にはそこで詰まるだろうことも書かれています。

最後に

強くなれ💪

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
148