ひびのログ

日々ではないけどログを出力していくブログ

Pythonの好きになれないところ5選

最近機械学習でPythonが日本でもにわかに話題となっていますが、ワタシパイソンスキジャナイ......
その理由をつらつらと書いていきたいと思います。

注意

あくまでPythonという言語に対する個人的な感想であり、Pythonに関わる方々を批判しているわけではありません。 「そう思う人もいるんだー」程度に見ていただけると幸いです。

1. 2系と3系

よく言われることだと思いますが、移行するなら早く移行してほしいです。 すぐにでも2系をdeprecatedにして、どんどん3系に移行してください。 2系でしか使えないライブラリとかがあると萎えます。 2系のEOLが2020年らしいので、そこまでの辛抱ですが……

とりあえず、3系で学習した後にわからないことがありググったときに2系の情報が出るのは、とても紛らわしいのでやめていただきたいです。

2. メソッドと関数の使い分け

例えばJavaなら、ソートしたい場合は、list.sort((a, b) -> a - b)とします。Listオブジェクトのメソッドを呼び出します。
あるいはリストの長さを取得しようと思ったら、list.size()とします。こちらもListオブジェクトのメソッドを呼び出します。

一方Pythonの場合、
リストをソートしたい場合は、list.sort()とします。リストのメソッドを呼び出しています。
一方リストの長さを取得する場合は、len(list)とします。関数にリストを渡しています。

Javaではリストに対する操作はListのメソッドを呼べばよかったのに対し、 Pythonではメソッドか関数か判断してコーディングしなければいけません。 本題とはちょっと違いますが、自分はJavaを書くときに、オブジェクト名.まで打つと表示されるメソッド一覧から選ぶことがあるため、 それが通用しないとなると途端にコーディング速度が落ちます。

要するに、いちいちどっちか自分で判定したくないんです。


実はPythonにはsorted(list)list.sort()の2種類のソート方法があります。 前者は関数、後者はメソッド呼び出しですね。 「ただでさえわかりづらいのに2種類あるのかよ!」と言いたくなる気持ちはありますが、これには合理的な理由があります。

関数呼び出しの方は「副作用なし(listが変更されない)」、メソッド呼び出しの方は「副作用あり(listが変更される)」です。 つまり、使用するときにはnew_list = sorted(list)list.sort()のように、使い分けが必要です。

これは先程のソートと長さの取得でも当てはまります。 list.sort()は副作用ありなのでメソッド、len(list)は副作用なしなので関数となっています。 わかりやすい! ここは思わず「なるほど」となりました。

ただし。

len()は副作用なしの関数と言いましたが、内部では__len___メソッドを呼び出しているようです。

(参考:Pythonを書くための最低限の文法メモ - Qiita

……関数とメソッド分けてる意味ないじゃん?


とPythonばっかり責めていますが、JavaもJavaでひどいです。 list.sort()と同様にソートするCollections.sort()があります。 これ、両方とも副作用ありで、listオブジェクト自体が変更されます。 CollectionsのJavaDocにあるように、

この実装は、指定されたリストとnullコンパレータを使用し、List.sort(Comparator)メソッドに従います。

とあるため、なんで存在するのかわかりません。

ちなみにScalaだとリスト(Seq)は基本immutableなので、副作用がないメソッドしかありません。 mutableのリストのソートについては知りません。使わないので。

3. リスト内包表記

これもわりとよく言われるかなと思います。

[ i * 10 for i in range(100) if i % 3 == 0]

こんな感じのやつ。

個人的には

for i in range(100):
    if i % 3 == 0:
        yield i * 10

こっちのほうが読みやすくて好きです。 でもこう書くと、「Pythonらしくないー」とか「実行速度がー」とか言われるのです。 これも慣れですかね?

4. インデント構文

自分は C→Java→JavaScript→Scala→TypeScript みたいな感じで言語を学んできたので、ブラケットで囲むのが普通なんですよね。 なのでインデント構文というのが好きじゃないです。 ちなみにRubyとかのif-endif構文もあまり好きじゃないですが、シェルスクリプトを最近触っているので、なんとなく慣れてきました。 インデント構文も慣れればいいんですかね。

ただ、これにはわりと明確な理由があります。 前述したリスト内包表記ですが……

[ i * 10 for i in range(100) if i % 3 == 0 ]


一体どこで改行できるんだ……!?


今この長さだから大丈夫だけど長くなったら見づらいし、 インデント構文だから変に改行したらプログラム実行できなくなりそう……

初見だとこうなりませんか?

ちなみに試したところ、forとかifの前で改行して、インデントあり・なし両方問題なく動きました。 ついでに最後の]だけ改行しても動きました。

インデント構文が嫌だという意見に対して大体言われるのが、「誰が書いても同じようなプログラムになるから見やすい」というものがあります。

……いやいや、それはコミット前にLintとかフォーマットとかやればいいじゃないかと。

構文レベルで縛る必要はないんじゃないかと思います。 人それぞれ書きやすいフォーマットは違うわけですし、特に個人開発では好きにさせてください。

5. if __name__ = "__main__"

importされたときに実行されないようにするための魔法の呪文ですね。

これは発想が逆かなーと考えています。

すべてimportできるようにするのではなく、importされる側が許可したものだけがimportできるようにすべきではないでしょうか? 何でもかんでもimportできる現状では、モジュールとして使われると思っていなかったファイルまでモジュールとして使われるかもしれません。 「そんなふうに使われるなんて思ってなかった!」というのは、プログラマにとっては結構馴染み深い問題だと思っています。

実際JSはmodule.exportsしないとimportできないので理にかなっているかなと。

選外

  • from xxxx import yyyyの順番が英語的に変。
  • JSやRubyでも似たようにかけますが、Pythonのスライスはプラス一癖。Scalaの記号メソッド乱立に似た何かを感じます。
  • モジュールをディレクトリ分けするときに__init__.pyを置かないといけないのが不便。
  • 機械学習でほぼ強制的に触らされるのが嫌です。

おわりに

選外の最後に書いたように、最近機械学習で触る機会が多かったのでPythonが槍玉に挙げられましたが、 多かれ少なかれ、これまで学習した言語で不満がないものはありません。

また、Pythonにもナイスだと思うところはありますし、 上で紹介した内容でも時と場合によってはナイスだと思うこともあります。 2系と3系は時間が解決しますし、初学者の方にはインデント構文が力を発揮します。

触ってみないとわからないことが多々あったので、実際に書いてみるということは大事だなと思いました。

「上で触れた内容以外にもあるよ!」とか、「それはこういう理由があるよ!」とかあれば教えていただけると幸いです。

npmパッケージ作成奮闘録

先日作成したnpmパッケージについて、所感等まとめておこうと思います。

npmパッケージ作りました

詳しくはこちらのQiitaのページを参照してください。

qiita.com

内容としては、「insertAdjacentHTML」を使いやすくしよう!ということで作成しました。 一番の狙いは、文字列ではなく関数として定義しておくことにより、エディタによる補完の恩恵を受けられるようにすることでした。 あとは文字列を覚えるのが面倒だったので、リファレンス的にも使えるかな、と思っていました。 内容的には簡単なラッパーなので、上記の理由があってもライブラリ作るほどでもないかな?とは思ったのですが、練習の意味も込めて作ってみました。

でも初回リリース時は利用できませんでした

リリースしたはいいものの、結果を見たくてnpm installしたら使えない…… おいおい嘘だろ頼むよと、仕事そっちのけで原因の検証を行いました。いや、仕事はちゃんとしました、はい。 すぐに原因の特定と修正ができたのが幸いでした。

原因は Browserify

調べた結果、ビルドの際 Browserify を使っていたからのようです。 ちゃんと Browserify のことを知っている人からすれば「そりゃそうだ」って感じですよね。

使ってしまった理由としては、他のプロジェクトで TypeScript を使って開発していたときの package.json を流用したからです。 コピペがまずいほうに影響してしまった結果です。反省してます。

ちゃんと export していたにもかかわらず、そのビルドのタイミングで使えなくなってしまいました。

そもそも Browserify ってなんだろう

間違えてしまったものは仕方ないので、そもそも Browserify はどんなことをしているのか見てみましょう。

ブラウザで動かない AltJS をトランスパイルする Babel や TSC(TypeScript コンパイラ)とは違い、簡単に言えば、import,export,requireを解決してファイルを繋げてくれるツール、という認識です。 ただそれだけではなく、プラグインで AltJS をトランスパイルしたりしてくれます。今回使用したtsifyなんかもそうです。 また、ファイルを繋げた後に、グローバルスコープ汚染を防ぐための処理が入ります。 要するに処理全体を即時関数で包むというアレです。

(function () {
    // 処理
})();

今回発生したエラーは、この処理が走ったがために、外部へexportされなかったのではないかと思います。

実際に Browserify でトランスパイルされたJSはどうなっている?

勉強のため、トランスパイルされたコードを見てみます。 簡単にするために、今回のモジュールの一部を抜き出しています。 (実際のソースが見たい方はこちら

(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
"use strict";
exports.__esModule = true;
function insertHTMLBeforeBegin(element, text) {
    element.insertAdjacentHTML("beforebegin", text);
}
exports.insertHTMLBeforeBegin = insertHTMLBeforeBegin;
;
},{}]},{},[1])

↑を見やすいように整形したのが↓です。

処理が整形後の32~38行目に書かれています。 31行目を見ると、無名関数に包まれています。(function (require, module, exports)) 1行目の関数eの第一引数として渡されているようです。

そのため、insertHTMLBeforeBeginを代入しているexports変数が外部から見えないので、このファイルをimportできないということがわかります。

再発防止のために

今回やらかしたことはもうどうしようもないので、今後このようなミスを避けるためにはどうしたらいいか考えてみます。 ぱっと思いつくのは3つありました。

まず、作ったあとにちゃんと検証していれば、こんなことにはならなかったはずです。 時間がない朝にデプロイしたのが問題でした。 デプロイ後に、ちゃんと自分で使えるか検証した上で告知(もしくは今回のように Qiita への投稿)を行うべきでした。

次に、使うツールが何をするのかをしっかりと把握しておくことが大切だと感じました。 今回であれば、Browserify とそのプラグインたち、そして npm, Github, TypeScript あたりです。 特にコードをいじるツールについては、しっかりと理解をしておくことが大切だと思います。 実際に動かしてみたときにエラーとなって動かないとなったときに、どこが原因か特定するのに役立つはずです。

最後に、テンプレートを作っておくといいと感じました。 そもそも他のプロジェクトで使用していたpackage.jsonをひっぱって来たのが問題でした。 ですので、それ専用のpackage.jsonなりなんなりを作っておけばいい話でした。

ということでテンプレート作りました

今回のプラグインで使用したツール等をまとめ、「npmパッケージ用のTypeScript + Browserifyテンプレート」を作成しました。 ご自由にお使いください! ライセンスはCC0なので、著作権表示等は不要です。

github.com

おわりに

みなさんもnpmパッケージ作ってみませんか? 少なくとも自分が不便だから使いたいモジュールを作れば、利用者が1人はいるので無駄にはならないです。 今回みたいに、もしデプロイしたパッケージが利用できなくても、個人開発なら怒られないですし。

あと、Git(Github)でどういうふうに管理するのがいいのか未だにわからないので、もっと経験を積んだり知識をつけたりしたいです。

おまけ:パッケージとモジュールの違いについて

ファイルをまとめたものがパッケージ、requireでロードされるのがモジュールだそうです。

参考↓

better-than-i-was-yesterday.com

Githubのリポジトリ名がnpm-moduleになっていて、「やっちまったなぁ」と若干後悔しています。

OSSのライセンスについて今一度考える

ReactがMITライセンスになりました

9/26にReactのv16.0がリリースされ、MITにリライセンスされました。 以前のような追加の文面もなく、正式なOSSとして使用できます。

github.com

ただし今回の件で、大きな企業が作っている≒安心して使用できるOSSでも、 開発が止まるという面だけではなくて、ライセンスの問題で使用できないという場合があることを再認識しました。 よりOSSの選定に気を向ける必要がありそうです。

具体的なOSS選定方針

基本的には大企業が作っているOSSなら安心ですが、 ただ単に「大企業が作っているから安心」とだけ言える状態じゃなくなったのは事実。

今後どんな基準で選んだらいいのか考えてみました。 ※あくまで個人的な素人基準です。

  1. 大きな非営利団体
    • ライセンス問題が少なそう&開発停止になることがなさそう。
      • e.g. Apache Software Foundation
  2. 大きな企業(ただしFacebookを除く)
    • 少なくとも開発がストップすることはないため。ライセンスに関して縛りがあれば、3以降のOSSを優先してもいいかも。
      • e.g. Google, Microsoft
  3. 枯れている
    • みんな大好きしおしおになったライブラリ。
      • e.g. ぱっと出てこないけど、変更の必要がないくらい小さなライブラリとかはここに当てはまる?
  4. みんな使っているもの
    • 需要があれば誰かが直してくれる。ただし、日本人だけが使っているようなものは基本的にNG
  5. 自分で作ったもの
    • いざとなれば直せる。(直すとは言っていない。)

まとめ

上記はあくまで個人的な感想ですし、むしろフォークして自分(達)でメンテしていく覚悟があるのであれば、どんなOSSを使っても大丈夫かと思われまーす。

はてなブログがHTTPS対応するので、AOSSLとはどんなものか簡単に説明してみる

祝・はてなブログHTTPS化(予定)!

はてなブログがHTTPSになるようです。やったぜ!

staff.hatenablog.com

ということで、AOSSL化(全ページHTTPS化)とは何なのかを少し書いておこうかなと思います。

いやそんなことより対応どうすんのさ?

まだなにもしなくても大丈夫です。多分。

気になる方は、いくつものブログで紹介されていますのでぜひググって見てみてください。(他力本願) ただし、HTTPSに対応したとしても勝手に移行されないみたいなので、自分で行う必要があります。

AOSSLについて

AOSSL(Always on SSL*1) は、これまで保護されていなかったページも含めて全部保護しちゃいましょう、というものです。

もうちょい詳しく

SSLはインターネットの通信を暗号化し、自分と通信相手以外に内容を見せないようにする技術です。 例えば住所、クレジットカード番号とかですね。

最初は「決済とか個人情報とかを取り扱うところだけHTTPSにすればいいよね!」って思っていたけど、「実際他のところも通信を保護したほうがよくない?」と考え、その方向で動いています。

実際、自分がTwitterとかに投稿した内容って、知らない人に見られたくないですよね? そこで、Googleという神のお告げによって、すべてHTTPSで通信する方向になっています。 すべてHTTPS(SSL)なので、AOSSLと名前がついています。

何がいいの?

メリットとして3つ挙げると、大体ここらへんになるのではないかと。

メリット1:「すべての通信が保護される」

特に公共施設にあるFree Wi-Fiなんかは、意外と簡単に誰がどことどんな通信しているのかがわかってしまいます。俗に言う「通信の盗聴」です。

それがなくなるため、安心してTwitterに"ピー"だとか"ピー"だとかを投稿できるようになります。やったね!

メリット2:「SEOにいい」

これも天下のGoogle様のお告げにより、HTTPSのサイトは検索順位を上げていただけるそうです。

Google様愛してます!(媚び売り)

メリット3:「ブラウザが安全だと表示してくれる」

正直これメリットというか、デメリットの回避なんですよね……

Google Chromeを使っている方とかはご存知だとは思いますが、アドレスバーに「保護された通信」とか「」とか表示されます。

HTTPのサイト
f:id:tee-talog:20170926210524p:plain

HTTPSのサイト
f:id:tee-talog:20170926210545p:plain

何も知らない人が見ても、「保護された通信」って書いてあったら安心しますよね。 HTTPSの場合はそれが表示されるため、利用者に安心感を与えられます。

また、今後配信されるバージョンのChromeでは、HTTPのサイトで危険であるという旨の表示を行うそうです。 「危険だぞー!」と書いているサイトにはあまり長くいたくないですよね。

デメリットってないの?

上の方で書きましたが、ページの運営側の対応が必要です。 はてなブログに関しては、ある程度向こうでなんとかしてくれるみたいです。

しかし、HTTPとHTTPSが混ざっている「混在コンテンツ」となると、表示がブロックされてしまう危険がありますので、必ず自分で見直しをして混在コンテンツをなくしましょう。 見直しできないほどコンテンツが多いのであれば……まぁ、はてなを信じましょう。

後は保護するための処理の増加に伴う通信速度の低下がありますが、日々PCスペックが上がっている現代においては誤差ですので、心配しなくても大丈夫かと思われます。

さいごに

AOSSL化には傷を伴いますが、ブログ運営している方にとっては比べ物にならないメリットがあります。

何もしなくてもいいのですが、できれば皆さんもHTTPSに向けて準備を整え、すぐに移行できるようにしておきましょう!

蛇足

インターネットを安全に使いたいので、こういった記事が量産されて、Web業界全体のHTTPSへの対応が早まれば嬉しいなと思います。

*1:当ページでは「SSL/TLS」を略して「SSL」とします

じゃらんの検索結果からプラン詳細を別タブで開くJavaScript

タイトルのとおりです。

動機

じゃらんから予約しようとして、プラン詳細を別タブで開こうと思ったときに、どうやっても同じタブで開かれてしまって「うがああああああ!!!!」と声を上げるのも今日まで。

ある雨の日、ある風の日。

そして今日。

悲しみと喉の不調をもたらす根源を断ち切るッ!

使い方

じゃらんで検索した後、ブックマークレットにしてクリックして実行する(推奨)か、F12のコンソールにコピペして実行する。

動作の保証はしません。

ライセンスはCC0なので、お好きに改変等OKです。

ソースコード

const _my_baseUrl = "http://www.jalan.net/uw/uwp3200/uww3201init.do";

const _my_screenId  = document.querySelector("input[name='screenId']").value;
const _my_distCd    = document.querySelector("input[name='distCd']").value;
const _my_rootCd    = document.querySelector("input[name='rootCd']").value;
const _my_stayYear  = document.querySelector("input[name='stayYear']").value;
const _my_stayMonth = document.querySelector("input[name='stayMonth']").value;
const _my_stayDay   = document.querySelector("input[name='stayDay']").value;
const _my_stayCount = document.querySelector("input[name='stayCount']").value;
const _my_roomCount = document.querySelector("input[name='roomCount']").value;
const _my_roomCrack = document.querySelector("input[name='roomCrack']").value;
const _my_smlCd     = document.querySelector("input[name='smlCd']").value;


const _my_planAnchors = document.querySelectorAll("div.search-result-cassette div.hotel-detail-plan table tr td a");
_my_planAnchors.forEach((elm) => {
    const _my_parsedHref = elm.href.match(/javascript:openPlanSyosaiStatic\('(.*)', *'(.*)', *'(.*)', *'(.*)'.*/i);
    const _my_yadNo           = _my_parsedHref[1];
    const _my_planCd          = _my_parsedHref[2];
    const _my_roomTypeCd      = _my_parsedHref[3];
    const _my_pageListNumPlan = _my_parsedHref[4];


    const _my_url = _my_baseUrl + "?" +
            "screenId="        + _my_screenId        + "&" +
            "distCd="          + _my_distCd          + "&" +
            "rootCd="          + _my_rootCd          + "&" +
            "stayYear="        + _my_stayYear        + "&" +
            "stayMonth="       + _my_stayMonth       + "&" +
            "stayDay="         + _my_stayDay         + "&" +
            "stayCount="       + _my_stayCount       + "&" +
            "roomCount="       + _my_roomCount       + "&" +
            "roomCrack="       + _my_roomCrack       + "&" +
            "smlCd="           + _my_smlCd           + "&" +
            "pageListNumPlan=" + _my_pageListNumPlan + "&" +
            "yadNo="           + _my_yadNo           + "&" +
            "planCd="          + _my_planCd          + "&" +
            "roomTypeCd="      + _my_roomTypeCd      + "&" +
            "callbackHistFlg=" + "1";

    elm.href   = _my_url;
    elm.target = "_blank";
});