タイトルの通りです
前回の割引のときに買いそびれて後悔してたんで、心残りをひとつ成仏させてやれた、よかったよかった
国際フレンドシップ・デーや、なんで割引なのか?について詳しくは↓を blog.jetbrains.com
↓の紹介リンクから買うと-¥500になります。よろしかったらどうぞ secure.samuraism.com
新規サブスクリプションだけじゃなく、継続にも適用されるらしいですね
タイトルの通りです
前回の割引のときに買いそびれて後悔してたんで、心残りをひとつ成仏させてやれた、よかったよかった
国際フレンドシップ・デーや、なんで割引なのか?について詳しくは↓を blog.jetbrains.com
↓の紹介リンクから買うと-¥500になります。よろしかったらどうぞ secure.samuraism.com
新規サブスクリプションだけじゃなく、継続にも適用されるらしいですね
Seleniumを使った時間のかかるPythonスクリプトがあり、こいつを動かすとフォーカス取られて仕事にならないので、docker化し、ヘッドレス環境で実行するようにした
ChromeDriverとSeleniumをインストール
FROM python:3.6-alpine3.7 ENV APP_ROOT=${APP_ROOT:-/opt/app} # update apk repo RUN echo "http://dl-4.alpinelinux.org/alpine/v3.7/main" >> /etc/apk/repositories && \ echo "http://dl-4.alpinelinux.org/alpine/v3.7/community" >> /etc/apk/repositories # install chromedriver RUN apk update RUN apk add chromium chromium-chromedriver # install selenium RUN pip install selenium WORKDIR $APP_ROOT/scripting
DOCKER=docker scripting: $(DOCKER) build -t scripting -f Dockerfile .
イメージをビルドする
make scripting
version: '2' services: script: image: scripting volumes: - .:/opt/app/scripting:rw
ヘッドレス環境で実行するテスト用スクリプト
from selenium import webdriver URL = 'https://www.google.co.jp/' def main(): options = webdriver.ChromeOptions() options.add_argument('--headless') options.add_argument('--no-sandbox') driver = webdriver.Chrome(chrome_options=options) driver.get(URL) print(driver.title) driver.close() driver.quit() main()
--headless
オプションでヘッドレスモード
--no-sandbox
オプションについては↓を参照
github.com
> docker-compose run scripting python3 script.py
Google
動いた
日本語を表示するためにフォントを入れます
FROM python:3.6-alpine3.7 ENV APP_ROOT=${APP_ROOT:-/opt/app} # update apk repo RUN echo "http://dl-4.alpinelinux.org/alpine/v3.7/main" >> /etc/apk/repositories && \ echo "http://dl-4.alpinelinux.org/alpine/v3.7/community" >> /etc/apk/repositories # install chromedriver RUN apk update RUN apk add chromium chromium-chromedriver RUN apk add ttf-freefont # install selenium RUN pip install selenium # install font RUN mkdir /noto ADD https://noto-website.storage.googleapis.com/pkgs/NotoSansCJKjp-hinted.zip /noto WORKDIR /noto RUN unzip NotoSansCJKjp-hinted.zip && \ mkdir -p /usr/share/fonts/noto && \ cp *.otf /usr/share/fonts/noto && \ chmod 644 -R /usr/share/fonts/noto/ && \ fc-cache -fv WORKDIR $APP_ROOT/scraping
Pythonicにリストをfilterするにはどうするか?といい話題がでたので、ちょっと考えてみた
話のもとになったslackの投稿(とその擬似コード
names = [x for x in names if x is CONDITION]
↑だとx
に何が入ってくるのかがわからない
names = [name for names in names if name is CONDITION]
↑長い
names = list(filter(lambda x: x is CONDITION, names))
lambda x:
の部分はフィルタの関数が定義されているだけなので、x
が何かわからなくてもいい?
どれがPythonicで良いコードなのか?
自分個人の見解としてはfilter
を使いたい
理由としては、filter
のほうがフィルターしていることが明示的だから。
フィルターしていることがわかると、引数にとったリストの要素の型と同じ型の要素を持つリストが帰ってくることが読み取れる
擬似的にScalaで型を表記すると、
val list:List[A]
なら、filter(f, list)
もList[A]
であるということ
それと、filterに渡すフィルタの関数の引数が何であるかは気にしなくて良いと思う
フィルターであれば、何が入ってくるかより、何になればフィルターの条件を満たすのかがわかればよくて、極論変数名もいらないと思う
Scalaの場合、関数のすべての引数が1回のみ使われる場合、引数宣言を省略して_で記述できる
ex)
names.filter( _ == HOGE)
なので、Pythonでもこんな風に書きたいくらい
names = list(filter(lambda _: _ is HOGE))
実際これでも動く
最終的にSlack上の議論では、内包表記がよいということになった
理由としては、可読性が良いから
可読性がよいと判断する理由としては、内包表記のほうがより一般的であるため
複数人でメンテナンスするコードであれば、尚更大事なポイント
それと、Effective Pythonでも内包表記を薦めているし
内包表記のほうがfilterの10倍くらい速いらしい
複数の値をまとめて扱いたい時ありますよね。value object欲しいなって時
そんなとき、Scalaだとcase class
を使いますね
Case Classes | Scala Documentation
case class Human(height:Int, weight:Int)
こんな感じで、1行だけで複数の値をまとめて扱うことができます。
付随して、コンパニオンオブジェクトやメソッド群が追加されるので
applyメソッドが実装され、new
を省略できたり
scala> val taro = Human(170, 60) taro: Human = Human(170,60)
unapplyメソッドが実装され、extractorパターンによるパターンマッチができたり
scala> taro match { | case Human(h,w) => w/(h*h/10000.0) | } res42: Double = 20.761245674740483
プロパティはimmutable
scala> taro.height = 100 <console>:12: error: reassignment to val taro.height = 100
equalsメソッドが適切に実装され、同じプロパティを持つインスタンス同士を同値と判定できたり
scala> taro == Human(170,60) res43: Boolean = true
と、いろいろ便利です
で、Pythonで複数の値をまとめていい感じに扱うには?
同僚に教えてもらったnamedtuple
が便利そうです
8.3. collections — コンテナデータ型 — Python 3.6.3 ドキュメント
case classと同じく、1行でパパっと定義できて
>>> Human = namedtuple('Human', ('height', 'weight'))
サクッと、インスタンスが作れます
>>> taro = Human(height=170, weight=60) >>> taro Human(height=170, weight=60)
__str__
メソッドを定義しなくとも、オブジェクトの中身を確認できてますね
オブジェクトの属性へアクセスできるし
>>> taro.height
170
属性はimmutable
>>> taro.height = 100 Traceback (most recent call last): File "<stdin>", line 1, in <module> AttributeError: can't set attribute
そしてunpacking assignmentで、値を取り出せる
>>> for h,w in [taro]: ... print(w/(h*h/10000)) ... 20.761245674740483
同じ属性を持つインスタンス同士の比較もOK
>>> taro == Human(height=170, weight=60) True
ぱっと見、クラスを定義してるように見えなくて、ナニコレ?ってなるけど、わかるととても便利なやつ
SQLArchemyを使ったアプリケーションで↓なコードがあった(詳細は割愛)
(age,) = db_session.query(User.age).filter(User.id == input.id).one()
右辺がUser.age
をラップしたなにかなのはわかるんだけど、左辺がよくわからない
これと似たようなもので、Scalaではdestructuring bindingというのがあって、
scala> case class User(age:Int, name:String) defined class User scala> val user = User(38, "Valentino Rossi") user: User = User(38,Valentino Rossi) scala> val User(age, _) = user age: Int = 38
のように、右辺のタプルを展開して、左辺の変数に代入される
これと同じことなんだろうと思ったんだけど、左辺でage
以外を握り潰しているっぽく見える書き方がよくわからない
(age,)
のカンマで終わってるのが、Scalaでいうval User(age, _)
のワイルドカードのような働きをしている?
でも、これだと
>>> user = (38, "Valentino Rossi") >>> (age,) = user Traceback (most recent call last): File "<stdin>", line 1, in <module> ValueError: too many values to unpack (expected 1)
となって、握りつぶせていない
よくよく調べてみると、これはunpacking assignmentと呼ばれるもので、右辺がリストなどのコレクションの場合、左辺にカンマ区切りで変数を並べると、左辺の各変数に対応する順で右辺の値が代入されるようだ
そして、今回のケースで混乱の素になったのが、左辺は単要素タプルだということ
Pythonのリファレンスでタプル型を確認すると、
3. データモデル — Python 3.6.3 ドキュメント
タプル型 (tuple)
タプルの要素は任意の Python オブジェクトです。二つ以上の要素からなるタプルは、個々の要素を表現する式をカンマで区切って構成します。単一の要素からなるタプル (単集合 'singleton') を作るには、要素を表現する式の直後にカンマをつけます (単一の式だけではタプルを形成しません。これは、式をグループ化するのに丸括弧を使えるようにしなければならないからです)。要素の全くない丸括弧の対を作ると空のタプルになります。
とあり、今回のケースではage
が単一の要素で、その直後にカンマがつくことでタプルになっている。そして、右辺も単要素のタプルであり、その値がage
に代入されているということ
左辺でage
以外を握り潰すことなんかしていなく、そもそも握り潰す値がなかったんや!
なので、右辺が2要素からなるタプルの場合、ValueError
となった
仕組みは理解できたからいいんだけど、こういうケースがggりずらくてつらくない?おれはつらい
会社のSlackでの1コマ
Pythonのor
やall
で、式の評価を途中でやめてくれない
>>> print('a') or pring('b') a b
↑の場合、直感的にはb
は出力されないはずだけど、バッチリでてる
Pythonって正格なのか、、?と思うも、
>>> 0 and print('b') 0
こっちだとor
は非正格に見える
この差異はprint
の返り値がNone
のために起きてる
Noneの真偽値は、False
>>> bool(None) False
なので、↑のケースでprint
の結果にTrue
を期待してると、裏切られると言う訳
次にall
print
がFalse
に評価されることを踏まえて、
def a(): print('a') return True def b(): print('b') return False def c(): print('c') return True
とすると
result = all([a(), b(), c()]) print(result) result2 = (a() and b() and c()) print(result2) a b c False a b False
という結果になる
and
は非正格だけど、all
は正格に見えてしまう
これにもタネがあって、↑のa,b,c
はall
実行時ではなく、リスト生成時に出力されているから
>>> [a(), b(), c()] a b c [True, False, True]
こんな感じ
なので、改めてall
を検証すると
class A: def __bool__(self): print('a') return True a = A()
とすると、
>>> all([True, a, False, a]) a False
となって、all
は非正格なことが確認できた