@kotyのブログ

.NETとかJavaとかPythonとか勉強会のこととかを、田舎者SEがつづります。記事のライセンスは"CC BY"でお願いします。

第2回 オブジェクト指向設計実践ガイド 読書会 記録

nseg.connpass.com

読書会常連の @tmtms さんが今回は不在でrubyを教えてくれる人がいなくて困ったな〜と思っていたら、rubyを知っている方が3人も来てくれてとっても助かりました。

外部からの参加者が5名、ギーラボ関係者が5名と最近になく賑やかな読書会になりました。全員がちょうど一度づつ読めたのも良かったです。 今回はrubyの文法についての質問が多めでしたが、rubyを知っている人にとってはrubyの文法理解に時間が多く取られるのはあまり良くないな〜と思いました。徐々に慣れるとは思うので、次回以降もrubyist諸氏におかれては今日に懲りずにご参加いただきたいです。

書籍内に出てくるコードはこちらで参照できます。 github.com

以下、普段pythonを書いている人間からの所感をいくつか。

getter/setterについて

attr_readerを使ってフィールドを隠蔽する、という話がありました。

pythonだとプロパティとフィールドのアクセス方法が同じなので、後からプロパティにもできます。

class Gear
  attr_reader :chainring, :cog
  def initialize(chainring, cog)
    @chainring = chainring
    @cog       = cog
  end

  def ratio
    chainring / cog.to_f
  end
end

は、プロパティを介さない場合は

class Gear(object):
  def __init__(self, chainring, cog):
    self.chainring = chainring
    self.cog       = cog

  def ratio(self):
    return self.chainring / self.cog

となります。これをプロパティを介すように直すと

class Gear(object):
  _chainring = None
  _cog = None

  @property
  def chainring(self):
    return self._chainring
  @chainring.setter
  def chainring(self, value):
    self._chainring = value

  @property
  def cog(self):
    return self._cog
  @cog.setter
  def cog(self, value):
    self._cog = value

  def __init__(self, chainring, cog):
    self.chainring = chainring
    self.cog       = cog

  def ratio(self):
    return self.chainring / self.cog

となる。う~ん、ちょっと記述量は多くなりますが、、、言いたいのはchainringおよびcogフィールドを使っている__init__ratioメソッドに変更が無いこと。なので後からプロパティ化することも割と手軽にできます。

配列操作について

  def diameters
    wheels.collect {|wheel| diameter(wheel)}
  end

は、pythonだと普通は内包表記で書きます。

  def diameters(self):
    return [diameter(wheel) for wheel in self.wheels]

ruby は最後に評価された値が戻り値になるそうです。これはこれで便利かも。

次回も楽しみです。

nseg.connpass.com

jinja2+WeasyPrintでお手軽印刷

過去にこんな記事を書いております。 koty.hatenablog.com この記事から4年近く過ぎた今でも印刷の需要はあります。ありますよね?

上記の記事はWPFWindowsアプリ)ですがDjangoでPDFを出す機会があったのでメモ的に残します。

おおまかな方針としては、Jinja2でHTMLを生成→WeasyPrintでPDFに変換 という流れです。

自端末のPythonのバージョンは3.6です。

requirements.txtはこちら。

WeasyPrint==0.40
Jinja2==2.9.6
Django==1.11.5

この記事としては必須ではないのですが、templateを描画するbackendがJinja2になるようsettings.pyを変更します。

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.jinja2.Jinja2',
        'DIRS': [os.path.join(BASE_DIR, 'templates')],
        'APP_DIRS': True,
        'OPTIONS': {
        },
    },
]

Jinja2に入力するhtmlのテンプレートはこちら。

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>{{ title }}</title>
  <style>
    /* http://qiita.com/cognitom/items/d39d5f19054c8c8fd592 */
    @page {
      size: A4;
    }
  </style>
</head>
<body>
<h1>{{ title }}</h1>
<div>
  <ul>
    {% for skil in skills %}
      <li>{{ skil }}</li>
    {% endfor %}
  </ul>
</div>
</body>
</html>

viewはこちら。

from django.conf import settings
from django.http import HttpResponse
from jinja2 import Environment, FileSystemLoader
from weasyprint import HTML


def pdf_sample(request):
    # templateの読み込み
    template_dir = settings.TEMPLATES[0]['DIRS'][0]
    env = Environment(loader=FileSystemLoader(template_dir, encoding='utf8'))
    tpl = env.get_template('pdf_sample.html')
    # テンプレートにデータをbind
    html_str = tpl.render({
        'title':'私のスキル',
        'skills': ('python', 'django', 'C#', 'WPF', '立ち泳ぎ'),
    })
    # html文字列をPDFに変換
    pdf_file = HTML(string=html_str).write_pdf()
    return HttpResponse(pdf_file, content_type='application/pdf')

あら簡単ですね!実際にはcssでいろんな調整が必要だろうと思います。この辺を参考に。 qiita.com

PythonでPDF印刷について調べるとReportLABとかがヒットします。これだとAPIがプリミティブ過ぎて使いづらいように思います。線を一本一本引いていくなんて辛すぎます。何らかの雛形を用意してそこにデータを突っ込む、ということをしたいわけです。それにはjinja2が最適です。

1ページだけの単票(死語か?)であればこの仕掛けで十分なように思います。 一方、改ページつきで各ページに共通なヘッダを出すといった帳票の場合は何か他の、例えばWPFとかを使う必要があるかもしれません。HTMLでもできるんかな??

本件のサンプルプロジェクトは以下です。 github.com

参考サイト:

PythonユーザのためのJupyter[実践]入門

PythonユーザのためのJupyter[実践]入門

PyCon JP 2017 に参加してきた

参加された皆さま、スタッフの皆さま、お疲れさまでした。簡単ですが参加報告です。

ブースを出した

昨年からスポンサーをさせていただいていましたが、今年はブースも出しました。その辺の様子は以下をご覧ください。

katekichi.hatenablog.com

私が転職してきてから数えても3回目の参加となり、だいぶ知り合いも増えてきました。そういった方々と立ち話しするだけでも楽しくすごせました。

セッションの所感

聴講セッション一覧

1日目

2日目

今年は英語のセッションを多めに聴講してみたのですが、正直言ってほとんど分かりませんでした。。。pyconに来るたびに英語力の無さを痛感します。*1その他に印象に残ったセッションについて所感を書きます。

プレゼンテーション:Industrial Test Automation with Asyncio | PyCon JP 2017 in TOKYO

  • async awaitは実用できるっぽい。すくなくとも発表者の用途では
  • pytestっていうテストのためのパッケージがあるらしい。unittestよりもイケてるらしい
  • まとめでこんなようなことを言っていたと思う(英語なので自信なし
    • プログラマは内にこもりがち。外に出ていきコミュニケーションをとろう
    • blogを書こう
    • OSSに貢献しよう

プレゼンテーション:Building a Customized Personal Assistant with Python | PyCon JP 2017 in TOKYO

フロントエンドにfacebook messenger、バックエンドに ChatterBotなるフレームワークを使ったという事例。面白そうだけど日本語の辞書がないっぽい。。

プレゼンテーション:ドローンのフライトコントローラをPythonで制御してみた話 | PyCon JP 2017 in TOKYO

大学の研究発表みたいだった。モーター動かせたって所までだったけど実際には制御系を作る必要があってその辺りの調整が難しかろうと思う。フライトコントローラーをOSSで作るという取り組みは素晴らしいです。

プレゼンテーション:The theory of Serverless development by Python (理論から学ぶPythonによるサーバレス開発) | PyCon JP 2017 in TOKYO

AWS Lambdaの話。質疑応答の時間が取られなかったので発表者に直接聞いた。

補足をいただいた。

ということで参考になりました。 発表の中で「Web APIアプリケーションには別に向いてない」というような説明がありましたが、それだけそういった使われ方が多いんでは、ということを帰社後にお客さんと話していました。


確かにこれは思った。

来年は少なくともプロポーザルは出したいなぁ。

*1:ちなみに翌日TOEICを受検している

drf_writable_nestedを試してみた

Django Restframeworkを使ってAPIを書くとコードの記述量が少なくて良いのですが、ネストしたオブジェクトの更新が煩雑になる点がちょっと不満です。ネストしたオブジェクトの更新をしようとすると途端に記述量が増えます。

実際、公式ドキュメントを見ると、

    def create(self, validated_data):
        tracks_data = validated_data.pop('tracks')
        album = Album.objects.create(**validated_data)
        for track_data in tracks_data:
            Track.objects.create(album=album, **track_data)
        return album

とあります。ForeignKeyフィールドであるtracksをpopしてからcreateする必要があります。updateも同様。これはイマイチ。。。

と思っていた所以下のパッケージがあったので使ってみました。 github.com

確認コードはこちら。 簡単に動かしてみた限りでは便利そうです。

今回のコードはこちらに置いてあります。 https://github.com/koty/drf_writable_nested_sample

時間をみつけて、いろんなパターンを試してみたいと思います。

vue.jsを始めてみた

本日の成果。


最近社内で突如としてSPA熱が高まっていて、勉強がてら作っている。

vue-cliでboilerplate的なものはできるので、そこからちょびちょびと機能追加していった。行の選択に応じて下部の詳細部分が書き換わる。reactiveで良い感じ。

Reactに取り組んでいる人がいて様子を見るにかなり苦戦している。慣れれば、あるいは大規模なシステムだと、Reactの方が効率が良いのかもしれない。だけど最初の入りやすさから自分はvueを選んだ。ちょっとかじっただけでもここまでできるのだからvueは最初のハードルは低いんじゃないだろうか。

この調子で、家業の伝票印刷システムのweb版を作りたい。どれくらいかかるか分からないけど。webpackの使い方とか全然分からないまま作っているけどとにかく動くものを作ることを優先する方が性に合う。

Django Background Tasksでお手軽バックグラウンド処理

バックグラウンドで動くバッチ処理を作りたい時にスケジューラを使うと思います。Pythonで代表的なタスクスケジューラはCeleryですかね。 でも、単に、長めの処理をキューイングしてシリアライザブルに実行していきたいといった簡単な要件だとちょっと大げさ過ぎるように感じ、他の仕掛けを探した所 Django Background Tasks というパッケージがあったの今回使ってみました。

基本的には、ドキュメントに書いてある通りですが。。

まずインストール。利用した時点でのバージョンは 1.1.0rc2でした。django-background-task (単数形)というパッケージも存在するのでご注意。今回紹介する複数形のは単数形のをforkしている模様です。

pip install django-background-tasks

settingsに設定を追記します。

INSTALLED_APPS = [
    ・・・
    'background_task',
    ・・・

キューを保持するためのテーブルを作るので、migrateします。migrationファイルが含まれていないので、makemigrationsから。migrateすると、background_taskおよびbackground_task_completedtaskというテーブルができます。

./manage.py makemigrations background_task
./manage.py migrate

タスクの定義。これだと、queue_name1というキューにキューイングし、5秒後に実行する、という設定です。

from background_task import background

@background(queue='queue_name1', schedule=5)
def some_long_duration_process(some_param1, some_param2):
    # 何か長い処理

タスクを呼び出す方の書き方です。普通にメソッドを呼んでいるように見えますが、実際にはキューイングされるだけでメソッドは呼び出されません。 @backgroundデコレータの内部でメソッド呼び出しが横取りされ、キューイングが行われます。background_taskテーブルを見ると分かるように引数はJSONシリアライズされるので、確認はしていませんがJSONシリアライズできる必要があると思われます。

# 登録側
some_long_duration_process('a', 'b')

説明の順番が若干前後しますが、予め以下のコマンドでバックグラウンドタスクを処理するプロセスを起動しておきます。

./manage.py process_tasks

サーバー構築時には、supervisordの下で動くようにしておきます。

[program:process_tasks]
command=/.../bin/python ./manage.py process_tasks --settings=my_proj.settings.prod
directory=/...
user=centos
autostart=true
autorestart=true
stdout_logfile=/.../...log ; 標準出力ログ
stdout_logfile_maxbytes=1MB
stdout_logfile_backups=5
stdout_capture_maxbytes=1MB
redirect_stderr=true

そこそこ細かい設定もできるようですし、Celeryは大げさだなという場面では有効に使えると思いました。

SPAのOpen Graph Protocol 対応について。さらにCloudFrontを経由した場合についてのつらい話。

もう半年以上前になってしまうけど、この話題。

サーバーサイドレンダリング不要論 - Qiita

ogp出すためにUAを見てアプリケーションサーバーに投げたりしてるけどcloudfrontでは既定でUAが書き換えられちゃうのでキャッシュヒット率落ちるのを覚悟の上でUAを通すという辛い状況。

2016/12/24 17:32
b.hatena.ne.jp

client → cloudfront → ELB → EC2(nginx → gunicorn )

という構成。

  • nginxのリバースプロキシにて /api はgunicornにルーティング
  • それ以外は try_filesで /index.html を返すという設定

基本的にはこれで動く。しかしOGPが問題。この作りだとOGPの生成にはJavaScriptが動く必要があるが、twittterやFBのクローラーJavaScriptを解釈しない。なので、nginxでUser-Agentを見てSNSクローラーだった場合はgunicornに処理を委譲しOGPのみを描画したドキュメントを返すようにした。

if ($http_user_agent ~ "facebookexternalhit") {
    ・・・
    proxy_pass http://127.0.0.1:8000;
    ・・・

ところが、User-AgentはCloudFrontの既定の設定だと"Amazon CloudFront"に書き換えられてしまい、上記の判定ができない。仕方ないので、以下のようにCloudFrontでUser-Agentヘッダを通すよう設定した。*1 f:id:kkotyy:20170801171259p:plain これで判定は通るようになる。しかしUser-Agentを通したことでキャッシュヒット率は落ちる。うむー。SNSクローラーJavaScriptを動かすようになるか、CloudFrontが CloudFront-Is-Crawler-Viewer といったヘッダを用意してくれるとありがたいんだけど。

*1:ちなみに画像にあるようにCloudFront-Is-*ヘッダも使ってみたのだけど、今回の要件には使えなかった