第29回チャレンジ富士五湖 4Lakes

チャレンジ富士五湖 4Lakesコース(100km)に参加してきました。初参加。

事前

ここ1年くらいの走行距離は月平均120km。まあ少ないが初めてウルトラ走った2年前は70km/月くらいだったのでそれに比べれば…。練習で一度に走った最長距離は50km。

100kmはこれまで柴又100Kに2回出ていて、それぞれ、水分摂りすぎで胃がダメになる・熱中症 にやられてしまったので、今回は「トラブルを起こさず、脚の疲労ボトルネックにする」を目標とする。

少し腰に張りがあるのが気になっていたけど、普段行ってない整骨院とかに直前にいきなり行って感覚が変になるのも避けたくてそのままで。

前日〜スタートまで

朝5時ごろに起きようと思っていたらうっかり9時まで寝てしまう。

中央線の特急かいじ富士急行を乗り継いで富士山駅まで。

駅の床屋で髪を切って、展望ロッジから富士山を拝み、地下のフードコートで吉田うどんを食べる。

宿はエバーグリーン富士だった。駅からは3kmほどあるのだけど、前日の準備運動にはちょうど良いかと思って歩いて行った。時刻がだいぶ遅くて暗くなってきてしまったので道がちょっと怖かった。

20時に布団に入ったけど朝寝すぎたせいか全然眠れない😇。とにかく目を閉じてじっとしておく。

2時に起床してホテルの朝ごはん。消化が遅いのでご飯と走り始めるまでの間は最低3時間は開けたいところだが、朝食時間が2時15分からなのはちょっときつい。あまり食べ過ぎないようにしておく。

会場に到着してトイレに並んでいたら、ちょうどいい感じに5時のスタートまでの暇を潰せた。

スタート前の補給にカステラ2切れとアミノバイタルPROを一袋。あとマイボトルエイドのクエン酸ドリンクをいただいてソフトフラスクに入れておく。

スタート地点は2℃ということで、寒いのは寒いのだけどスタートまでの少しの間だからがまん。防寒として100均のポンチョを被ってる。

エストポーチの中身は次の通り。

  • 塩熱サプリ x 9
  • メダリスト エナジージェル GRAPEFRUIT & HONEY x 1
  • PowerBar GEL トロピカルフルーツ x 1
  • 500円玉 x 1
  • 参加案内に同封されていた RUNNER's CARD

レース(前半)

基本は、練習のロング走でやってたのと同じペースの 6'20/km くらいで進んでいく。

スタート15分前にトイレ行ったものの、スタートしてすぐまた行きたくなってしまい、最初のトイレである8km地点のに行った。ら、大行列で8分もロスしてしまった。 さらに13km地点でもトイレへ。ここは混んでなかったので助かった。 スタート直後にトイレ行きたくなるの毎度で困る…。スタート直前の水分補給は控えめにしてたのだが。

とにかく胃が死ぬのが怖くて、お腹が減ってくるまでは固形物の補給は避ける。フルーツとスポーツドリンクを取るくらい。あとは走りながらクエン酸ドリンクをちょびちょびと。 まあ最後まで結局はっきりとした空腹を感じることはなかったんですが…。

20kmくらいから、脚よりも先に背中にちょっとずつ筋肉痛が出てくるが、どうしようもないのでそのまま。 山中湖の湖畔を走っているときが、まだ気温も上がっていなくて一番気持ちよかった。

練習で50km走ってたおかげで、50kmまではだいぶ余裕でペースを保って到達できた。38kmの北麓公園付近の登りも全部走りで。

50km地点のタイムは5時間33分。このペースのままいければ、トイレで8分止められたのを考えると11時間切れるくらいだなあ、いやいやそれは無理でしょ、とかいうことを考える。

レース(後半)

56kmの足和田エイドでは、日焼け止めを塗り直し、アミノバイタルPROを一袋飲んで、スポーツようかんをひとつウエストポーチに補充。

エイドを出たところの坂も元気よく登ったが、登り切っての60km付近からいよいよ疲れを感じて歩きが入るようになってきた。

そして68kmで歩くのもきつくなってしばらく立ち止まってしまう。頭がすこしフラフラする感じもあり、これはエネルギー切れか〜? と思う。 手持ちのPowerBar GEL トロピカルフルーツを食べる→まずい…。マイボトルにドリンク入れていてよかった。飲み物で流し込まないと食べるの無理やなこれは。あとスポーツようかんも半分食べる。

これで少しは進めるようになったが、少し胃が気持ち悪くなってきたこともあって、72.5kmの旧精進小学校エイドでは腹をくくって一度じっくり休むことにする。 この時点で残り全部歩いたとしても時間内にゴールはできる状況だったので、とにかく胃の不調やエネルギー切れで動けないという状態にならないようにということを心がけて。 エネルギーになるものを取った上で、10分ほど安静にして消化を促進させる。

するとさっきの疲労感がスッキリ抜けて、次の10kmはエイドの小休止以外は 6'30/km ペースでノンストップで走れた!

が、また82.5kmでさっきよりも強いフラフラ感が…。まあ一度に補給できるエネルギー量を考えたら妥当ではある。スポーツようかんの残り半分を食べて、85kmの足和田エイドまでゆるゆる進み(下りだったので助かった)、またバナナやコーラをとって10分休憩する。 手持ち最後のジェルであるメダリスト エナジージェルも投入→これもまずい…。スポドリで流し込む。

ここのエイド、バナナを配っているおじいちゃんのテンションが高くて、休憩しているあいだ楽しませてもらった。

ここまでくればあと少し、と気合いを入れるが92kmくらいからの緩やかな登りが続く市街地はさすがにしんどくて歩きを交えつつ。歩き通しにならなければ13時間は切れる! というのが心の支えだった。

ステラシアター手前では、どうせエイドを出たところの坂では歩くのだからその前は走ってやろう!! と 5分/km くらいでダッシュしてしまった。 もちろん筋肉痛はきていて脚を運ぶのは重たいのだけど、エネルギー切れに比べれば筋肉痛なんて無視して足を動かすことはできるのでね。

最後の坂に挑む。基本歩くつもりだったけど、思っていたほどの傾斜ではなかった(足和田の坂くらいを想像していた)のでちょっとだけは走った。ずっと歩きというのも気分下がっちゃうし。この区間は 10分/km くらいでクリア。

ラスト2kmの下りは勢いで走り切ってあっという間だった。たぶん最後の1kmが100kmの中で一番ラップ速い。

ラップタイム

ランナーズアップデートより。

f:id:tomerun:20190422200635p:plain

前半が 6'30/km 、後半が 8'30/km という感じか。 後半でも走れてる区間は 6'30/km くらいではあるのだけど、エイドでしっかり休まないと走れないのが。

感想

過去2回のウルトラがかなり辛かったのに比べて、今回は途中一時ガス欠になった時はあったものの最後までしっかり脚は動いて基本楽しかったので、次回また出てもいいかなあという気になっている(朝食は自分のペースで食べたいからツアー以外で宿取りたいが)。

そのほか箇条書きで。

  • エネルギー切れで動けなくなるというのは初めての経験だった。ウルトラは補給の競技だということを理解した
    • フルだとエイドのスポーツドリンクだけで最後までいけてしまうから、補給のこと全然意識してなかったんだよなあ
    • 補給をしっかりできるようになったら、やっと走力の問題になってくる
    • 走ってる途中に食べるとすぐ吐きそうになるからかなり難しい問題だが
  • エネルギージェル系、何種類か試したけど基本まずくてテンション下がる…。スポーツようかんは普通にようかんでよかった
    • 今後は、エネルギーはようかん・ミネラルはタブレットで補う感じにしていこう
  • 胃が致命的に崩れることがなかったのは、塩熱サプリを時々摂ってたおかげかな?
  • ポンチョは胴体の前後が開いたタイプだったのでバサバサする感じがあったのがイマイチだった。カッパの方が良い
  • 100均で見かけて適当に買った女性用アームカバーがだいぶ活躍した。朝は防寒に、昼は紫外線よけに
  • アップダウンはこのくらいならまだ人道的、という感じ。エネルギー切れさえなければ全部走り切ることも可能そう
  • コースに日陰があるのは良い! 最近河川敷のレースばかり出ていたもので
  • 最高気温は20℃くらいだったみたいで、これよりも暑い環境では走りたくないかな…(ウルトラ出ようとするなら避けられないが)
  • 富士山をこれだけはっきりを楽しめる景色は最高だった!

第10回しまだ大井川マラソン

前日

藤枝駅の近くに宿泊した。 パソコン作業したかったので東海道線グリーン車を使って移動。快適。 藤枝駅のカレー屋 で夕食。おいしかった。

23時40分頃に就寝(もう少し早く寝たかったが)

スタート前

5時50分頃起床。 前日に買っておいたおにぎり2個とカステラ1切れを朝食に。

7時40分頃に宿を出て、8時10分頃にスタート位置到着。

男子更衣室が建物ひとつ丸々使う形だったので3階まで上がる。こういうとき1階に人が溜まりがちで、少し離れると比較的空いていて良い。 日焼け止めを塗ったりしながらカステラをもう1切れ。あとスタート前給水があったのでスポーツドリンクを少しいただく。

スタート30分前の8時30分頃にスタート地点(Bブロック)に並ぶ。こう気温が高い日は並んでるあいだ寒くないのはいいんだけど…。 DJとゲストの盛り上げのおかげで暇はしなかった。

スタートまでのロスは1分少々。

レースプランは、5分30秒/kmを保って、後半元気だったらペースを上げる、で。 練習では30kmを5分15秒/kmで余裕残して走れてたのでだいぶ保守的なペースではあるけど、記録を狙う大会と思ってはいないし、余裕を持たせて。

レース中(前半)

さすがにスタート4kmくらいまでの市街地コースは混雑であまりスムーズに走れない。けど5分30秒/kmから大きくは外れていないのでOK。 2,3km地点くらいで早速汗が垂れてきて、やっぱり暑いなーと思う。

河川敷に出てからは快調に走れるようになった。自然と5分20秒/kmくらいまではペース上がってしまうものの、まったく無理している感じはないのでこれでいいか、とそのままで。 コース脇に木が生えているところなど、思いのほか日陰があった(本当に日陰が一切ない柴又100Kに比べると、だいぶマシ)。

最近の練習により、給水、特に冷たいものを一気に飲むと胃が死亡することがわかってきたので、給水ではしばらく口に含んでちょっとずつ飲み込むことを心がける。水があまり冷たくなくて良かった。

7kmくらいで4時間のペースランナー集団を追い抜く。あれ、これまで5分30秒かそれより早いペースで走ってきたんだけど前にいたの? ポジティブスプリットで行ってるのかな…

9km地点あたりで一度トイレに入って1分弱ロス。ちょうど空いていてよかった。

20kmを前にしておなかが空いてきたんだけど、フルーツステーションでメロン、りんご、パイナップルをいただいて収まった。走ってる間のフルーツいいですね〜

20km地点くらいでは全然息は切れていないし、足の重さもまだ全くないし、良い調子だなーと思うけど、つぶれるときは同じ調子でも30kmからつぶれるので、油断せずに同じペースを保つ。

レース中(後半)

折り返して傾斜としては下りから上りに変わってるはずだけど、あまり実感はなかった。 多少の向かい風はあるが、かえって体感気温が下がって気持ちいいくらい。

もう一つの折り返しまで17km、ひたすら淡々と走る。河川敷コースはこの単調さが辛いという人が多いけど、個人的にはこういう黙々と進んでいくのは好き。

25kmくらいから少しずつ足が重くなってきて、気合を入れないとペースが保てない感じにはなってくるけど、まあこの辺はいつも通り。 自然と5分10秒/kmくらいまでペースが上がっていたので、意識してペースアップはせずに、それを保つ感じで。

33km過ぎの大エイド。といってもそんな食べられないので、小さなお饅頭だけもらって後は眺めるだけで通り過ぎる。走りながら饅頭食べたら口の中の水分が完全に吸い取られてしまってウケた(次の給水所が待ち遠しかった)。

2回目の折り返しを過ぎて、最後少しはペース上げようともしたけどそこまでの余裕はなく若干しか上がらなかった。完璧に近いペース取りだったということなのでこれはこれでOK。

ゴールタイムは3時間45分14秒(ネット)。11年前に初フルマラソンで出した自己ベストにあと2分まで迫るセカンドベスト。やっとここまで戻せてきた。

ラップタイムと順位経過 応援ナビより。道中で1000人以上追い抜いたんですね。

補給としてジェルを一つポケットに入れていたけど邪魔なだけだった。フルではエイドにある給食だけで十分だなー。

ゴール後

ペットボトルを2本もらえたのはありがたい。

ジェラート食べたかったけど着替えてる間に売り切れてしまった…。 かわりに、と緑茶ソフトに並んだらそれも前のお客さんで売り切れてしまった…。

マグロ丼を食べる。最近長く走ると気持ち悪くなって食べられなくなるのが続いていたので、走った後に普通にご飯を食べられたのはかなり収穫。

ネガティブスプリットだと後のダメージが少なくて良い。これまでのフルマラソンの中で一番のゴール後の元気だったんじゃなかろうか。

しばしゴール地点を眺めたのち、島田駅まで徒歩で移動。徒歩20分と書いてあったけど10分で着いた。疲労困憊で歩くのがやっと、というランナーを基準にした時間?

まとめ

ネガティブスプリット最高!!

今シーズンのうちに自己ベスト更新するぞ。

Tokyo Westerns CTF 3rd 2017

Tokyo Westerns CTF 3rd 2017 に Ofuna Gourmet Explorers で参加しました。

https://github.com/tomerun/CTF/tree/master/TokyoWesterns2017

Rev Rev Rev

gdbで実行しながら追っていく。main関数の中で4回サブルーチンに飛んでいる。

  • 1回目は末尾の改行を除去しているだけ
  • 2回目は文字列の前後をreverseしている
  • 3回目は、何かビット演算を色々やっている。結果をよく見ると各バイトのビット上位下位を反転させているように見えて、きっとそうだと思う
  • 4回目は、ビット単位で0-1を反転

メモリアドレス 0x8048870にあるデータを期待値として入力と比較しているので、上記の操作を逆にやるプログラムを書いてflagを得た。

3回reverseしてるからこの問題名なんですね。

BabyRSA

qがp*19くらいなことから、p,qの上位桁半分くらいは特定できる。

rsa attack ctf とかでググって http://inaz2.hatenablog.com/entry/2016/01/20/022936 にたどり着き、これの「素数pの上位bitまたは下位bitがわかっている場合」を適用して解けた。

BabyDLP

1bitずつ決めていく。

$i$ を入力したときに得られる値を $f(i)$ とする。

$f(2^{k}) = f(0) * 2^{2^{k}} \pmod{p}$ の場合、flagのkビット目は $0$ 。そうでなければ $1$。

BabyPinhole

messageの下半分のbitを求めてから上半分のbitを求める。

  • 下位bitは、ciphertextに $g^{x}$ を掛けたものをデコードさせることで $message+x$ が得られるので、デコード結果から繰り上がりがあったかどうかを観測することによって上から1bitずつ決定できる
  • 上位bitは、ciphertextを $2^{-k}$ 乗してデコードさせることで下から1bitずつ決定できる
    • $message = 2^{k}*m^{\prime} + m^{\prime\prime} (0 \le m^{\prime\prime} \lt 2^{k})$ とする
    • ciphertext を $2^{-k}$ 乗してデコードさせた結果は、 $message * 2^{-k} = (2^{k}*m^{\prime} + m^{\prime\prime}) * 2^{-k} = m^{\prime} + m^{\prime\prime} * 2^{-k}$
    • で、messageの下位bitである $m^{\prime\prime}$ の値は知っているので、観測した値をもとに $m^{\prime}$ の b bit目、つまり messageの (b + k) bit目がわかる
    • これを$k=1$から順に1つずつ増やしていって実行する

Palindromes Pairs - Challenge Phase -

よくわからないのでとりあえずランダム生成して投げてみたらflagが出てきて、は??!!?!? となった

サーバーに問題があったらしく、修正されてflagが追加された。それは取れず

その他

Reversing(TW interpreterとか)も少し見ようとしたけど、ほとんど進まず。

objdumpとgdbで地道に読んでいくだけだと膨大に時間がかかるので、適切にツールとか使えるようにならねば

Crystal言語のドキュメントを読む(Literals)

Crystalドキュメントの Literals を読んだのでメモ。

コメント

  • # による一行コメントのみ

Nil

  • nil 。普通

Bool

  • truefalse の二つの値を持つ
  • RubyではBool型は存在しなくて、それぞれ TrueClassFalseClass の唯一の値という形なので、そこがちょっと違う

Integers

  • 8bit, 16bit, 32bit, 64bit。unsignedあり。
    • リテラルの接尾辞で型を明示できる。 _i16, _u32 など。
  • 整数リテラルの型は、サイズによって signed 32bit, signed 64bit, unsigned 64bit のどれかに決まる。
  • アンダースコア区切りOK
  • 0b でbinary、 0o でoctal、 0x でhexadecimal

Floats

  • デフォルト64bit。32bitを使いたいときは接尾辞に _f32 を付ける。
  • アンダースコア区切りOK

Char

  • Unicodeコードポイントを表す32bit値
  • \uxxxx で16進数4桁、 \u{xxxxxx} で16進数6桁までのコードポイント指定

String

  • immutable
  • 引用符は " のみ
  • %(...) のようなクォートもできる。括弧は他の種類もOK
  • \u に続けて16進数でコードポイント指定(Charと同じ)
  • リテラル内で改行できる
  • ヒアドキュメントは <<-IDENT から IDENT まで
  • #{} でstring interpolation
    • string interpolationを無効にするには %q(...) で囲う

Symbol

  • :hello など。普通

Array

  • genericな型を持つ。型は構築時に決まる。
    • たとえば [1,2,3]Array(Int32)[1,"Hello",'x']Array(Int32|String|Char)
    • 空配列を作るときは型を明示する必要がある。 [] of Int32
  • Rubyと同じように、 %w(one two three) で Array(String) が作れる
  • %i(one two three) で Array(Symbol) が作れる

Hash

  • genericな型を持つ。型は構築時に決まる。
    • たとえば {1=>2, 3=>4}Hash(Int32, Int32){1=>2, 'a'=>3}Hash(Int32|Char, Int32)
    • 空ハッシュを作るときは型を明示する必要がある。 {} of Int32 => Int32

Range

  • x..y とか x...yRubyと同じ

Regex

  • スラッシュ区切りで /foo|bar/ とするか、 %rと括弧で %r(foo|bar) とするか
  • PCRE というのが文法らしい

Tuple

  • タプルがある!
  • {1, "Hello", 'x'}Tuple(Int32, String, Char) 型になる
  • 空のタプルを作るときは Tuple.new を使う
  • メモリが静的にスタックに割り付けられる。関数から複数の戻り値を返すようなときに使うと良い

NamedTuple

  • NamedTupleがある!
  • {name: "Crystal", year: 2011}NamedTuple(name: String, year: Int32) 型になる(キーの値も型に含まれるということ?)
  • tuple[:name] のようにアクセスする
  • キーはStringでもOK

Proc

  • -> で作る。 lambdaproc キーワードはない
  • ->(x : Int32, y : Int32) { (x + y).to_s }Proc(Int32, Int32, String) 型になる
  • 引数の型は基本的に指定が必須
  • メソッドをProc化できる
def increment(x)
    x + 1
end
proc = ->increment(Int32) # 引数の型を限定している
  • レシーバを指定してProc化もできる
str = "abc"
proc = ->str.count(Char)
puts proc.call('x')

Rails環境をDockerで作る

Railsチュートリアルを始めた。せっかくなので環境をDockerで作ってみた。

これをかなり参考にさせてもらった RailsアプリをDockerで開発するための手順 - Qiita

Rails入りのDocker Imageを作る

RailsのDocker Imageは Docker Hubにある のだけど、なんかDEPRECATEDと書いてあるし、勉強も兼ねて自分でRails入りのイメージを作った。

RubyのイメージにRailsをインストールするために、次のDockerfileとGemfileを作成し、空の Gemfile.lock も作成した(内容は、参考にしたリンク先のものからバージョン番号を変えたくらいでほぼそのまま)。

Dockerfile:

FROM ruby:2.4.0

ENV APP_ROOT /usr/src/workspace

WORKDIR $APP_ROOT

RUN apt-get update && \
    apt-get install -y nodejs \
                       mysql-client \
                       postgresql-client \
                       sqlite3 \
                       --no-install-recommends && \
    rm -rf /var/lib/apt/lists/*

COPY Gemfile $APP_ROOT
COPY Gemfile.lock $APP_ROOT

RUN \
  echo 'gem: --no-document' >> ~/.gemrc && \
  cp ~/.gemrc /etc/gemrc && \
  chmod uog+r /etc/gemrc && \
  bundle config --global build.nokogiri --use-system-libraries && \
  bundle config --global jobs 4 && \
  bundle install && \
  rm -rf ~/.gem

Gemfile:

source "https://rubygems.org"
gem 'rails', '5.0.1'

ここからdocker buildしてRailsが入ったイメージを作成する。

$ docker build -t tomerun/rails_5_0_1 .
(snip)
$ docker images
REPOSITORY                     TAG                 IMAGE ID            CREATED             SIZE
tomerun/rails_5_0_1            latest              afc9c9a9ef93        3 minutes ago      801 MB

apt-getするときの --no-install-recommendsrm -rf /var/lib/apt/lists/* は、ファイルサイズを削減するため。参照: http://d.hatena.ne.jp/mainyaa/20140203/p1 の10番

作成したイメージからアプリのひな形を作成

アプリケーションを作成するディレクトリへ移動し、上で使ったGemfileをコピーしてくる(これがないとrails newが失敗するのだけど、Railsの仕組みを理解していないのでなぜ必要なのかわかっていない)。 その後、Docker内で rails new してアプリケーションのひな形を作る。 途中でGemfileを上書きして良いか聞かれるので Y で進める。

$ mkdir app_dir
$ cd app_dir
$ cp ../rails_template/Gemfile .
$ docker run --rm -it -v "$PWD":/usr/src/workspace tomerun/rails_5_0_1 rails new . -BT
(snip)
$ ls
Gemfile       README.md     app/          config/       db/           log/          tmp/
Gemfile.lock  Rakefile      bin/          config.ru     lib/          public/       vendor/

ライブラリをインストールしたDocker Imageを作る

使用するgemをインストールしたイメージを作っていく。

まず、Gemfileを必要に応じて書き換える。

次のDockerfileを作って、更新したGemfileを元にbundle installを実行したdocker imageを作成する。

FROM tomerun/rails_5_0_1

ENV APP_ROOT /usr/src/workspace

WORKDIR $APP_ROOT

COPY Gemfile $APP_ROOT

RUN bundle install --without production

EXPOSE  3000
$ docker build -t tomerun/rails_toy_app .
(snip)
$ docker images
REPOSITORY                     TAG                 IMAGE ID            CREATED             SIZE
tomerun/rails_toy_app          latest              e5c44f8a1a2b        3 minutes ago       854 MB
tomerun/rails_5_0_1            latest              afc9c9a9ef93        About an hour ago   801 MB

Gemfile.lockをイメージ内からホスト側へ持ってくるために適当なことをする

$ docker run -d tomerun/rails_toy_app sleep 100
$ docker cp 1ec8e208d405:/usr/src/workspace/Gemfile.lock .
# 1ec8e208d405 はコンテナID
$ docker stop 1ec8e208d405

次のようにするとRailsが起動する。localhost:3000 にアクセスすると動いている様子が見える。

$ docker run -d -p 3000:3000 -v "$PWD":/usr/src/workspace tomerun/rails_toy_app rails server -b 0.0.0.0

-v でファイル共有しているため、ホスト側でソースコードを書き換えると自動的に反映される。

Effective Python 1章

Effective Pythonの内容を身につけるため、各項目に関連した内容を書いていく。

  • 本の内容そのまま写経ではなく、できるだけ独自に例を作る
  • Python2に固有の話は基本的に無視する

項目1:使っている Pythonのバージョンを知っておく

自分のパソコンの環境

$ python --version
Python 2.7.10
# システムデフォルトのやつ

$ python3 --version
Python 3.6.0
# homebrewで入れた。virtualenvとかの仮想環境的な仕組みは使っていない

項目2:PEP 8スタイルガイドに従う

PEP8の本家ドキュメント

SublimeTextでは、SublimeLinter-pep8を使ってスタイルチェックができる。

項目3:bytes, str, unicodeの違いを知っておく

unicodeはpython2用なので気にしない。

>>> # ascii文字値から構成されるbytesは、先頭に'b'をつけたリテラルで表現できる
>>> bytes = b'a lazy fox'
>>> bytes
b'a lazy fox'
>>> # bytesへのインデックスアクセスで得られる要素はint
>>> bytes[0].__class__
<class 'int'>
>>> list(bytes)
[97, 32, 108, 97, 122, 121, 32, 102, 111, 120]
>>> 
>>> str = 'a lazy fox'
>>> str
'a lazy fox'
>>> # strへのインデックスアクセスで得られる要素はstr
>>> str[0].__class__
<class 'str'>
>>> list(str)
['a', ' ', 'l', 'a', 'z', 'y', ' ', 'f', 'o', 'x']

項目4:複雑な式の代わりにヘルパー関数を書く

たいへん当たり前の内容なので特に何も言うことはないが、本文に登場する例について。

def get_first_int(values, key, default=0):
    found = values.get(key, [''])
    if found[0]:
        found = int(found[0])
    else:
        found = 0
    return found

変数 found に再代入しているのがイマイチと感じる。このくらいの分岐だったら個人的には条件演算子の方が好み。

Pythonの条件演算子、ちょっと独特なのでそこでの読みづらさはある

def get_first_int(values, key, default=0):
    found = values.get(key, [''])
    return int(found[0]) if found[0] else 0

項目5:シーケンスをどのようにスライスするか知っておく

スライスの範囲がシーケンスの長さをはみ出ている場合、スライスの範囲長よりも小さいサイズのシーケンスが返される。

>>> l = [0, 1, 2, 3]
>>> l[:10]
[0, 1, 2, 3]
>>> l[10:20]
[]  # 範囲をはみ出ている場合、空リスト
>>> l[3:2]
[]  # スライスの範囲が大小逆転している場合、空リスト

ちなみにRubyの場合、指定するRangeが配列の長さの範囲から完全にはみ出ている場合、空の配列ではなくてnilが返ってくることが異なる。

> a = [0, 1, 2, 3]
 => [0, 1, 2, 3] 
> a[0...10]
 => [0, 1, 2, 3] 
> a[10...20]
 => nil  # 範囲をはみ出ている場合、nil
> a[3...2]
 => [] 

インデックスアクセスは、 __getitem__()__setitem__() で実装されている。これらのメソッドを定義すると、自作クラスでインデックスアクセスを提供できる。

class IndexedObject:

    def __init__(self):
        self._prop0 = 0
        self._prop1 = 1

    def __getitem__(self, key):
        print("getitem:" + str(key))
        if key == 0:
            return self._prop0
        elif key == 1:
            return self._prop1
        else:
            return None

    def __setitem__(self, key, value):
        print("setitem:" + str(key))
        if key == 0:
            self._prop0 = value
        elif key == 1:
            self._prop1 = value
        else:
            pass
>>> o = IndexedObject()
init
>>> o[1]
getitem:1
1
>>> o[1] = 'str'
setitem:1
>>> o[1]
getitem:1
'str'

このコードでは、あくまでサンプルとして key は整数にしか対応していない。スライスを渡す形式に対応したかったら、 __getitem__()__setitem__() の中で key の型を判定して処理を分ける。

項目6:1つのスライスでは、 start, end, strideを使わない

ややこしいスライスを行おうとしてコードが複雑になる場合、それを避けるためにスライスを複数回適用すると、リストのコピーが複数回行われて効率が悪い。

>>> a = [0,1,2,3,4,5,6,7,8,9]
>>> b = a[1:6]
>>> c = b[::2]
>>> c
[1, 3, 5]
# a[1:6:2] と同じ結果

itertools#islice() を使うと、イテレータの形で中間状態を持てるため、効率が良い。

>>> from itertools import islice
>>> a = [0,1,2,3,4,5,6,7,8,9]
>>> iter = islice(a, 1, 6)
>>> list(islice(iter, None, None, 2))
[1, 3, 5]

項目7:mapやfilterの代わりにリスト内包表記を使う

適用したい関数がはじめからある場合には、別にmapでも問題なさそう。

あと、mapだと変換したシーケンスそのものではなくてイテレータが返ってくるという違いがある。 まあ、リスト内包表記のほうもジェネレータにすればいいだけではあるが。

>>> import math
>>> a = [1, 2, 4, 8]
>>> [math.log2(x) for x in a]
[0.0, 1.0, 2.0, 3.0]
>>> list(map(math.log2, a))
[0.0, 1.0, 2.0, 3.0]

filterも一緒に実行したい場合は、map/filterだと関数のネストになってしまうのでさすがにリスト内包表記の方が読みやすい。 (とはいえ、Rubyのメソッドチェーン形式のほうがより読みやすいんだけど)

項目8:リスト内包表記には、 3つ以上の式を避ける

リスト内包表記のネストは本当に読むの厳しいのでやめた方が良いですね…

項目9:大きな内包表記にはジェネレータ式を考える

ジェネレータを使うと無限リストも扱える

>>> from datetime import *
>>> 
>>> def generate_int():
>>>    i = 0
>>>    while True:
>>>        yield i
>>>        i = i + 1
>>> 
>>> start_time = datetime.now()
>>> for v in generate_int():
>>>    if (datetime.now() - start_time).total_seconds() > 1:
>>>        print(v)
>>>        break
661670

項目10:rangeよりは enumerateにする

enumerate、要はeach_with_indexだ

初期値を指定できるところは便利

>>> for i, e in enumerate(['a', 'b', 'c'], start=1):
...     print(f'{i}:{e}')
1:a
2:b
3:c

項目11:イテレータを並列に処理するには zipを使う

zipは3つ以上の引数も扱える。

>>> a = [1, 2, 3]
>>> b = [4, 5, 6]
>>> c = [7, 8, 9]
>>> for x, y, z in zip(a, b, c):
...     print(f'x:{x} y:{y} z:{z}')
x:1 y:4 z:7
x:2 y:5 z:8
x:3 y:6 z:9

項目12:forとwhileループの後の elseブロックは使うのを避ける

はい。

項目13:try/except/else/finallyの各ブロックを活用する

  • finallyブロック
    • Pythonでfinallyブロックを使った覚えがない。これまでのところだいたいwith文で事足りている気がする
  • elseブロック
    • 成功時の処理をtry本体と切り離して明示できるのはいいですね
    • 変数のスコープがtryブロック内に閉じないからこれが実現できるのか