蛇ノ目の記

技術のことも。そうでないことも。

SEからPythonエンジニアにジョブチェンジして早くも3ヶ月経った話

某IT勉強会支援サイトとか某Python学習サービスPythonでやっている会社に入社してから3ヶ月が過ぎた。

そういうわけでビープラウドに入社してからの話。

3時間がんばれたら3日いける。3日いけたら3週間いける。そんな言葉を入社してすぐにもらった覚えがある。気付いたらもう3ヶ月。

まずはざっくりと今、何をやっているか。DjangoでWeb開発してます。入社前にやっていたDjango Girls Tutorialでは触ることのなかった Class-Based View で悩んだりユニットテストの修正で迷ったりしているけれど、私はげんきです。

転職の経緯

  • 新卒で入ったSIerではコードを書く機会がほぼ無く、エンジニアとしてこれでいいのか?と思ってた

    • 就職してから気付いたけど自分で思っていたよりコード書くのが好きだった
  • 学生の頃に少しPythonやってたので、勉強を再開しつつPythonやれる仕事を探そう

  • 転職エージェント使ったりPyCon JPスポンサーの会社を見てみたり

  • ビープラウドは猛者のたくさんいる会社という印象だったけど意を決してメールした

  • 入社できた

入社して変わったこと

  • Pythonで仕事ができる

  • コミュニケーションは全てSlack

    • 情報共有がスムーズ

    • #randomでの雑談も楽しい

  • タスクの管理はRedmine

    • 自分の仕事がチケット単位になっているとわかりやすい
  • コードの管理がGitHub

    • PR出してコードレビューしてもらってMergeという流れ

    • コードレビューからして新鮮な概念だった

  • (週5いつでも)リモートワークができる

    • 気軽に自宅で仕事ができる

    • 宅配の荷物が来ても大丈夫

    • ベランダにアウトドアチェア出して仕事したりした

  • 技術の話ができる

    • 前職は…お察しください
  • 猛者がたくさんいる

    • 困ったときの強い相談相手

    • PythonだけでなくVagrantとかDockerのことを教えてもらう機会もあった

  • スーツとか着なくていい

    • 似合わないので服装自由は嬉しいことこの上ない

    • 生活と仕事がくっきり分かれすぎる感じがあまり好きじゃなかった

  • 毎日、乳酸菌とってる

入社して驚いたこと

環境が激変したけど、今ではだいぶ順応してる。前より格段に働きやすい環境なので嬉しい変化しかなかった。

これからも増々Pythonでがんばっていこう。

フィルムで新宿周辺の夜を撮ってみる

これは フィルムカメラ Advent Calendar 12/19 の記事です。

フィルムで新宿・代々木の夜を撮ってきた。一度目は9月29日、二度目は11月17日。

カメラとフィルムは以下の通り。

  • Nikon FE2 Ai NIKKOR 35mm f2S

  • Natura 1600 (09/29)

  • CineStill 50D (11/17)

街角スナップはNatura 1600、光跡夜景にはCineStillという映画撮影用フィルムから作られたフィルムを使ってみた。CineStillは常温で放置してたせいでおかしな模様が出たコマがあった。やめよう常温放置。

まずはNatura 1600で撮った街角スナップ。9月29日。

Untitled

代々木の踏切手前にあるバーガー屋さん。店から漏れる灯りが暖かげ。

Untitled

Untitled

代々木駅横の高架下のあちらとこちら。

Untitled

新宿駅南口付近。ぼやっと光るネオンサイン。

Untitled

新宿駅西口。ヘッドライトの群れと光を反射する車体の金属感。

CineStill 50Dで撮った光跡夜景。11月17日。

ゴリラポッドをガードレールや歩道橋の欄干に括り付けて撮った。どれも露光時間は30秒くらい。

Untitled

代々木の踏切。電車の霊が通り過ぎたかのよう。

Untitled

新宿駅の南口・新南口のはざま。行く光と来る光。

Untitled

人混みはフィルムに焼き付かない。

Untitled

赤い光が分かれていく場所。

Untitled

半透明の人の群れ。

Flickr Albumを作ったのでどうぞよしなに。記事に載せていない写真も何枚かあります。

Shinjuku Night Scene | Flickr

Shinjuku Night Scene

Amazon Linux 2でPython3を有効にする

Discordのbotを動かす環境が欲しかったのでAWS EC2デビューを果たした。

設定もそこそこに、とりあえずインスタンスを立ててみた。

  • リージョン: 東京
  • AMI: amzn2-ami-hvm-2017.12.0.20171212.2-x86_64-gp2
  • インスタンスタイプ: t2.micro

無料枠を使って動かす。AMIはAmazon Linux 2を選んでみた。初AWS、初EC2なのでわからないことだらけで苦労している。

参考:

dev.classmethod.jp

Python3が動くとのことだけど、ログインした直後は動かなかった。調べてみると、 Amazon-Linux-Extras というライブラリにPython3が含まれていた。

$ amazon-linux-extras list
  0  ansible2   disabled  [ =2.4.2 ]
  1  emacs   disabled  [ =25.3 ]
  2  memcached1.5   disabled  [ =1.5.1 ]
  3  nginx1.12   disabled  [ =1.12.2 ]
  4  postgresql9.6   disabled  [ =9.6.6 ]
  5  python3   disabled  [ =3.6.2 ]
  6  redis4.0   disabled  [ =4.0.5 ]
  7  R3.4   disabled  [ =3.4.3 ]
  8  rust1   disabled  [ =1.22.1 ]
  9  vim   disabled  [ =8.0 ]
 10  golang1.9   disabled  [ =1.9.2 ]
 11  ruby2.4   disabled  [ =2.4.2 ]
 12  nano   disabled  [ =2.9.1 ]
 13  php7.2   disabled  [ =7.2.0 ]

なるほど、確かにある。

ヘルプを見てみる。

$ amazon-linux-extras -h
  help      See list of commands.
  info      See details of a specific package.
  install   Enables specified topics and installs their packages.
  list      Lists topics in the catalog. Some may be enabled.

Amazon Linux Extras software topics give you access to the most-recent
stable software you specifically choose, without the uncertainty of a
wholly new environment.

install を使えばよさそう。というわけで $sudo amazon-linux-extras install python3 でインストール。

$ sudo amazon-linux-extras install python3
  0  ansible2   disabled  [ =2.4.2 ]
  1  emacs   disabled  [ =25.3 ]
  2  memcached1.5   disabled  [ =1.5.1 ]
  3  nginx1.12   disabled  [ =1.12.2 ]
  4  postgresql9.6   disabled  [ =9.6.6 ]
  5  python3=latest  enabled  [ =3.6.2 ]
  6  redis4.0   disabled  [ =4.0.5 ]
  7  R3.4   disabled  [ =3.4.3 ]
  8  rust1   disabled  [ =1.22.1 ]
  9  vim   disabled  [ =8.0 ]
 10  golang1.9   disabled  [ =1.9.2 ]
 11  ruby2.4   disabled  [ =2.4.2 ]
 12  nano   disabled  [ =2.9.1 ]
 13  php7.2   disabled  [ =7.2.0 ]
$ python3 -V
Python 3.6.2

やったぜ。

あとは git を入れて、 GitLabのプライベートリポジトリをクローンしてくれば動かせる、はず。

Bleachってなによ

ジャンプで連載してたやつではなく(念のため)

卍解とかもしない(念のため)

仕事で出会ったHTMLサニタイズライブラリなのだけど、日本語の情報がとても少ないのでちょっとまとめたい。

貴重な日本語の情報

www.ianlewis.org

Bleachとは

Bleach — Bleach 2.1.1 20171002 documentation によると、

Bleach is an allowed-list-based HTML sanitizing library

まずHTML sanitizing とはなにか。ググってみると「HTMLエスケープ処理」と出てくる。

例えば、フォームに<script>なんか悪いスクリプト;</script>XSSを試みる入力があったときに、<, >&lt ,&gtに置き換えることで 無効化する。というような感じだろうか。

Bleach can also linkify text safely

とあるので、HTML sanitizingだけでなくテキストを安全にリンクにすることもできる。

http://example.comが入力されたときに、<a href="http://example.com"> とリンクに変換できる、ということか。

applying filters that Django’s urlize filter cannot, and optionally setting rel attributes, even on links already in the text.

Djangoのurlize filterではできないフィルタを掛けたり、既にテキスト内に存在するリンクに属性を設定したりできる。

Bleach is intended for sanitizing text from untrusted sources.

信頼できないソースのサニタイズのためとあるので、ユーザに入力されたテキストが対象と考えてよさそう。

基本的な使い方

https://bleach.readthedocs.io/en/latest/#basic-use

>>> import bleach

>>> bleach.clean('an <script>evil()</script> example')
u'an &lt;script&gt;evil()&lt;/script&gt; example'

>>> bleach.linkify('an http://example.com url')
u'an <a href="http://example.com" rel="nofollow">http://example.com</a> url

上は、なんか悪いスクリプトサニタイズして無害化している例。

下はテキスト内のURLをリンク化している例。テキストフォームに入力された内容を、HTMLにして表示する掲示板のようなものと考えるのがいいのかな。

今回は仕事で扱ったリンク化をメインにする。

bleach.clean()

http://bleach.readthedocs.io/en/latest/clean.html#sanitizing-text-fragments

前述の通り、今回のメインはリンク化なのでざっくりと。

悪意のあるHTMLを綺麗にする

bleach.clean(text, tags=[u'a', u'abbr', u'acronym', u'b', u'blockquote', u'code', u'em', u'i', u'li', u'ol', u'strong', u'ul'], attributes={u'a': [u'href', u'title'], u'acronym': [u'title'], u'abbr': [u'title']}, styles=[], protocols=[u'http', u'https', u'mailto'], strip=False, strip_comments=True)

パラメータ

  • text(str) - 綺麗にするテキスト
  • tags(list) - 許可するタグのリスト。デフォルトはbleach.sanitizer.ALLOWED_TAGS
  • attributes(list) - 許可する属性。リストまたは辞書。デフォルトはbleach.sanitizer.ALLOWED_ATTRIBUTES
  • styles(list) - 許可するCSSスタイルのリスト。デフォルトはbleach.sanitizer.ALLOWED_STYLES
  • protocol(list) - 許可するリンクのプロトコル。デフォルトはbleach.sanitizer.ALLOWED_PROTOCOLS
  • strip(bool) - 許可していない要素を除去するか否か
  • strip_comments(bool) - HTMLコメントを除去するか否か

許可するタグと属性はセットなこともあると思うので、例えばattributes

ALLOWED_ATTRIBUTES = {
        'a': ['href', 'title', 'rel'], 
         . . . 
}

という辞書としたとき、tags

ALLOWED_TAGS = ALLOWED_ATTRIBUTES.keys()

とするのもよさそう。

bleach.linkify()

http://bleach.readthedocs.io/en/latest/linkify.html#linkifying-text-fragments

テキストからURLやメールアドレスを探してリンク化する。

bleach.linkify(text, callbacks=[<function nofollow>], skip_tags=None, parse_email=False)

パラメータ

  • text - リンク化するテキスト
  • callbacks(list) - タグの属性を調節するコールバック関数のリスト。デフォルトではbleach.linkifier.DEFAULT_CALLBACKS
  • skip_tags(list) - リンク化したくないタグのリスト。例えば、<pre>タグ内を飛ばしたい場合は['pre']とする。
  • parse_email(bool) - メールアドレスをリンク化するか否か

戻り値

  • リンク化したテキストをunicodeで返す

bleach.linkify()callbacksでは属性の設定や削除、置き換えができる。

http://bleach.readthedocs.io/en/latest/linkify.html#callbacks-for-adjusting-attributes-callbacks

属性を設定してみる

属性の設定を仕事で使ったので、そのあたりを書いてみる。

title属性とtarget属性を設定してみる

import bleach

def set_title(attrs, new=False):
    attrs[(None, 'title']) = 'my title'
    return attrs

def set_target(attrs, new=False):
    attrs[(None, 'target']) = '_blank'
    return attrs

callbacks = bleach.DEFAULT_CALLBACKS + [set_title] + [set_target]

html = 'abc http://example.com def'

html_linkify = bleach.linkify(html, callbacks = callbacks)

これによってリンク化したテキスト

abc <a href="http://example.com" rel="nofollow" target="_blank" title="my title">http://example.com</a> def

が得られる。

bleach.DEFAULT_CALLBACKS はデフォルトの属性設定コールバック関数でrel=nofollowを設定する (rel=nofollowを指定するとクローラーがリンク先を認識しなくなる)。

 

公式ドキュメントではbleach.linkify.Linkerを使っているので、それについて。

http://bleach.readthedocs.io/en/latest/linkify.html#setting-attributes

たくさんのテキストに同じリンク化の設定を適用したいときに便利そう。

パラメータ

  • callbacks(list) - タグの属性を調節するコールバック関数のリスト。デフォルトではbleach.linkifier.DEFAULT_CALLBACKS
  • skip_tags(list) - リンク化したくないタグのリスト。例えば、<pre>タグ内を飛ばしたい場合は['pre']とする。
  • parse_email(bool) - メールアドレスをリンク化するか否か
  • url_re(re) - URLの正規表現
  • email_re(re) - メールアドレスの正規表現

url_reとかemail_reにマッチするテキストだけリンク化ができる。

まとめ

ざっくりと。

Bleach

  • HTMLから悪意のある要素を取り除いて綺麗にできる

  • テキスト中のURLやメールアドレスをリンク化できる

正規表現でURLを処理する話 - グループ化と繰り返し-

URLを正規表現で処理するということをやった。そのメモ的なやつ。

処理の概要

処理したいURLは以下のような構成とする。

https://example.com/users/@{username}/article/{yyyy}/{mm}/{article_id}/

ユーザが入力したURLを受け取って、usernamearticle_idを取り出したい。

正規表現の設計

入力されるURLの正規表現の設計として以下の3点を考慮する。

  • https:// が抜けていてもマッチする

  • http:// となっていてもマッチする

  • example.com 以下は完全一致させる

正規表現

以下が概要・設計に基づいて作った正規表現

(?:https?:/{2})?example.com/(?:users/@)(\w+)(?:/article/)(?:\d{4}/\d{2})/(\w+)/
正規表現まとめ

参考:

6.2. re — 正規表現操作 — Python 3.6.3 ドキュメント

正規表現 HOWTO — Python 3.6.3 ドキュメント

  • (?:...)

    • グループ化。ただしこのグループにマッチする文字列は後で参照されることがないmatch.groups() の戻り値に含まれない。今回は正規表現でマッチさせたいが、後から参照することのない部分に用いた。
  • (...)

    • グループ化このグループにマッチする文字列は後から参照することができる(match.groups()の戻り値に含まれる。今回は username article_idを取り出すための用いた。
  • ?

    • 繰り返し直前の正規表現を0回または1回繰り返したものにマッチする
  • \w

    • [a-zA-Z0-9_]に相当する。
  • \d

    • [0-9]に相当する。

正規表現を書くときは regex101.com をよく使っている。

regex101.com

それに加えて、@shimizukawaさんにDebuggex.comを教えてもらった。正規表現が文字列にどうマッチしているか可視化してくれる便利サイト。

https://www.debuggex.com/

実案件での失敗

(?:https?:/{2})?(?:example.com/)? ...のように必ずマッチさせたいURLのホスト部をグループ化した直後で ?に置いていた。

これでは https://users/@{username}/ariticle/{yyyy}/{mm}/{article_id}のような不正なURLがマッチする。他の関数が使っている既存の正規表現を見よう見真似で書いたことが原因の一つ。その正規表現では ?(?:https?:/{2})? のようにスキームに対して使っていた。ここを拡大解釈して、その他の部分でも使ってしまった。

既存のコードを参考にするときは、その意味を自分で咀嚼してから使うべきだと思い知った次第。

正規表現の先読み・後読みを知った話

実に2週間ぶりの更新。転職してそろそろ2ヶ月になる頃だけど、実案件で苦戦しまくっている。今回の話もそこから出てきたネタ。

正規表現には先読みアサーション 後読みアサーション なるものがあることを知ったので、今回はそれについて書く。

公式ドキュメントはこちら

6.2. re — 正規表現操作 — Python 3.6.3 ドキュメント

正規表現 HOWTO — Python 3.6.3 ドキュメント

先読み

先読みアサーション

  • (?=...)

... が続く文字列にマッチする。例えば以下のようになる。

>>> import re
>>> m = re.search(r'Nikon(?=FE2)', 'NikonFE2')
>>> m.group(0)
'Nikon'

マッチしないものも混ぜてみる。

>>> import re
>>> cameras = ['NikonFE', 'NikonFE2', 'NikonFM2']
>>> for camera in cameras:
...     m = re.search(r'Nikon(?=FE2)', camera)
...     if m is not None:
...             print(m.group(0), camera)
...     else:
...             print('None', camera)
...
None NikonFE
Nikon NikonFE2
None NikonFM2

カメラの機種名でやってみたけど、実践的に考えると特定のsuffixのある文字列を引っ掛けるのに使えそう。もちろん (?=...) には正規表現が使える。

>>> m = re.search(r'Nikon(?=[A-Z0-9]{3})', 'NikonFE2')
>>> m
<_sre.SRE_Match object; span=(0, 5), match='Nikon'>
>>> m.group()
'Nikon'

否定先読みアサーション

  • (?!...)

...が続かない文字列にマッチする。例えば以下のようになる。

>>> cameras = ['NikonFE', 'NikonFE2', 'NikonFM2']
>>> for camera in cameras:
...     m = re.search(r'Nikon(?!FE2)', camera)
...     if m is not None:
...             print(m.group(0), camera)
...     else:
...             print('None', camera)
...
Nikon NikonFE
None NikonFE2
Nikon NikonFM2

機種名がFE2でない機種がマッチしている。

特定のsuffixを持たない文字列を引っ掛けるのによさそう。

仕事で<meta property="og:image:type" ~ />ではなく<meta property="og:image" ~ />正規表現で引っ掛ける方法を探していて今回のネタに行き着いた、という裏事情がある。

BeautifulSoupを使って属性を条件にしてタグを取得する、というのが絡んでいる処理なのだけど、その振る舞いを勘違いしており先読みアサーション氏には出る幕がなかったというオチがつく。無念。この反省もブログのネタとして活かしたい。

後読み

後読みアサーション

  • (?<=...)

文字列内の現在位置の前に、現在位置で終わる ... とのマッチがあれば、マッチします。

とある。

(?<=abc)def は abcdef にマッチを見つけます。

>>> import re
>>> m = re.search('(?<=abc)def', 'abcdef')
>>> m.group(0)
'def'

含まれるパターンは、固定長の文字列にのみマッチしなければなりません。

先読みアサーションとは異なって、パターンに正規表現を使うことはできない。

シャーロック・ホームズの相棒だけを探す。

>>> import re
>>> watsons = ['JamesWatson', 'JohnHWatson', 'IBMWatson']
>>> for watson in watsons:
...     m = re.search(r'(?<=JohnH)Watson', watson)
...     if m is not None:
...             print(m.group(0), watson)
...     else:
...             print('None', watson)
...
None JamesWatson
Watson JohnHWatson
None IBMWatson

同じファミリーネームの人物がなかなか思いつかなくて苦労した。一つだけ人間じゃないのが混ざってるけど気にしない。

否定後読みアサーション

  • (?<!...)

文字列内の現在位置の前に ... とのマッチがない場合に、マッチします。

とある。また後読みアサーションと同様にパターンに正規表現は使えない。

今度はシャーロック・ホームズの相棒じゃない人たちを探す。

>>> for watson in watsons:
...     m = re.search(r'(?<!JohnH)Watson', watson)
...     if m is not None:
...             print(m.group(0), watson)
...     else:
...             print('None', watson)
...
Watson JamesWatson
None JohnHWatson
Watson IBMWatson

DNAの二重螺旋構造を見つけてノーベル賞取った人とIBMのすごいやつがマッチした。

後読みアサーションは特定のprefixを持つ(あるいは持たない)文字列をマッチさせる、というような使い方ができそう。

そういう感じで正規表現の先読み・後読みアサーションについてわかったことを書いてみた。

Python入門者向けハンズオンでTAしてきた

Python入門者向けハンズオンでTAをしてきた。 普段は教えてもらうことばかりなので新鮮な体験だった。

python-nyumon.connpass.com

教える立場というと、前職で新入社員のJava研修のTAを一日だけやったことがあった程度だったので割と心配だった。Python基礎とWebスクレイピングをやるということなので、予習としてスクレイピングするやつを書いてみたりもした。

github.com

Fetch_M3_2017Fall_Circle_list · GitHub

参加者が自由にコードを書く時間は1時間程度ということもあり、そこまで難しいことは聞かれなかった。質問の内容としてはBeautiulSoup4の使い方とHTMLの構造(classとか)の割合が半々くらいだったように思う。HTMLの話はそんなにしないから、初めての人はたしかに迷うところだよね。

あと、講義パートでは教えてないzip関数を教えたりもした。

参加者のもくもく結果の中では、Weblioに載ってる英単語の意味を教えてくれるツールが面白かった。<td class="content-explanation"> の部分を取ってきてる(ここの部分は自分が聞かれたところなので覚えてる)。WeblioのURLはhttp://ejje.weblio.jp/content/英単語 なので、この構造に目をつけたのだと思う。ナイスな着目点。

#PyNyumonにも弱冠小学1年生の参加者がいた。Progateで幾つかの言語に触れているらしい(PyQもぜひ!)

PyCon JP 2017に凄い小学生がいたり、BPStudyでScratchでマイクラを作る小学生の話を聞いたりして子どもの凄さに驚いていた今日このごろだ。なんなの最近の小学生。すごい。

個人的な学びとしては、リポジトリをforkしてからPRを出すやり方を覚えた(TAの傍ら、#PyNyumonテキストに直した方がよさそうなところを見つけてた)。

そんな感じでTAをやる実績を解除した。今後は、行ってみたい地方でPyCampのTAをやってみるのもいいかもしれない