ようへいの日々精進XP

よかろうもん

2018 年 05 月 24 日(木)

ジョギング

日課

  • お休み

玉ねぎとそら豆が

  • 実家から玉ねぎとそら豆が送られてきた
  • 玉ねぎはベランダに干しておいた

奥さん

  • 久しぶりに体調が悪いということで寝込んでいた
  • 無理せず, 自分の体に正直に, 焦らずに

今日のるびぃ ~ REx - Ruby Examination にチャレンジ (9) ~

REx - Ruby Examination の問題を自分なりにアレンジした上で 1 〜 3 問くらいずつ解いていく. 正直言ってかなり難しい. 尚, irb に動作確認環境は以下の通り.

$ ruby --version
ruby 2.1.10p492 (2016-04-01 revision 54464) [x86_64-linux]
$ irb --version
irb 0.9.6(09/06/30)

refinements

以下のコードを実行するとどうなるか.

# file name: refinement_1.rb
class Cls1
  def method1(value)
    100 + value
  end
end

module Mod1
  refine Cls1 do
    def method1
      super 50
    end
  end
end

module Mod2
  refine Cls1 do
    def method1
      super 100
    end
  end
end

using Mod1
using Mod2

puts Cls1.new.method1

以下, 実行例.

$ ruby refinement_1.rb 
200

ということで, 200 が表示される.

なぜか, 以下, 解説より抜粋.

  • 同一メソッドに対して, refinements を用いて, 2 つのモジュールで再定義している
  • using を 2 行書いた場合, 1 つのメソッドで有効になる再定義は 1 つだけ, 最後に書いた using が優先される
  • 設問で有効になるのは using Mod2 となる為, super + 100 = 100 + 100 = 200 となる

そもそも, refinements について.

  • 再定義したメソッドの適用範囲を限定する機能
  • 再定義したメソッドは using メソッドを利用して利用する
  • using メソッドは, トップレベル, クラス構文, モジュール構文内で利用することが出来る
  • トップレベルで using を呼び出した場合, using が呼び出された箇所からファイルの最後までとなる

以下, refinements の例. (プロを目指す人のためのRuby入門 言語仕様からテスト駆動開発・デバッグ技法まで 「8.9 モジュールに関する高度な話題」より引用)

module StringShuffle
  refine String do
    def shuffle
      chars.shuffle.join
    end
  end
end
  • refinements を使う準備としてモジュールを作成する
  • モジュール内で refine メソッドを使って, refinements を適用するクラス (上記の例では String クラス) を指定し, そのブロック内に shuffle メソッドを定義する

refinements を有効にする為に, 以下のように using メソッドを利用することで, Foo クラス内でのみ shuffle メソッドが利用可能となる.

irb(main):001:0> module StringShuffle
irb(main):002:1>   refine String do
irb(main):003:2*     def shuffle
irb(main):004:3>       chars.shuffle.join
irb(main):005:3>     end
irb(main):006:2>   end
irb(main):007:1> end
=> #<refinement:String@StringShuffle>
# 通常は String#shuffle は存在していない
irb(main):008:0> 'kappa'.shuffle
NoMethodError: undefined method 'shuffle' for "kappa":String
irb(main):009:0> class Foo
irb(main):010:1>   using StringShuffle # using メソッドで StringShuffle を利用する (refinements を有効にする)
irb(main):011:1>   def initialize(name)
irb(main):012:2>     @name = name
irb(main):013:2>   end
irb(main):014:1>   def shuffle_name
irb(main):015:2>     @name.shuffle
irb(main):016:2>   end
irb(main):017:1> end                   # Foo クラスを抜けると refinements は無効となる
=> :shuffle_name
irb(main):019:0> user = Foo.new('kappa')
=> #<Foo:0x005563c35d7d80 @name="kappa">
irb(main):022:0> user.shuffle_name
=> "ppaak"

もう少し. サンプルコードをドキュメントより引用.

class C
  def foo
    puts "C#foo"
  end
end

module M
  refine C do
    def foo
      puts "C#foo in M"
    end
  end
end

以下, irb にて確認.

# refinements 無し
irb(main):014:0> x = C.new
=> #<C:0x0055b46a444cf0>
irb(main):015:0> x.foo
C#foo
=> nil
# refinements 有り, だけど irb だと意図したような結果にならない
irb(main):016:0> using M
=> main
irb(main):017:0> x = C.new
=> #<C:0x0055b46a42bc28>
irb(main):018:0> x.foo
C#foo
=> nil
# refinements 有り, 以下のように一行で書けば意図したような結果になる
irb(main):019:0> using M; x = C.new; x.foo
C#foo in M
=> nil

フムフム.

2018 年 05 月 23 日(水)

ジョギング

日課

  • お休み

夕飯

  • 初めての「香華
  • 可もなく不可もなく...

今日のるびぃ ~ REx - Ruby Examination にチャレンジ (9) ~

REx - Ruby Examination の問題を自分なりにアレンジした上で 1 〜 3 問くらいずつ解いていく. 正直言ってかなり難しい. 尚, irb に動作確認環境は以下の通り.

$ ruby --version
ruby 2.1.10p492 (2016-04-01 revision 54464) [x86_64-linux]
$ irb --version
irb 0.9.6(09/06/30)

クラス変数

以下のコードを実行するとどうなるか.

class Cls1
  @@v = 0
  def initialize
    @@v += 1
  end
end

class Cls2 < Cls1
  class << Cls2
    @@v += 1
  end

  def initialize
    @@v += 1
    super
  end
end

Cls2.new
Cls2.new
Cls1.new
Cls1.new

p Cls2.class_variable_get(:@@v)

以下のように各メソッドでどのような値になっているかを出力しながら irb で確認してみる.

class Cls1
  @@v = 0
  def initialize
    puts "Cls1 #{@@v}"
    @@v += 1
  end
end

class Cls2 < Cls1
  class << Cls2
    puts "Cls2 Class Method #{@@v}"
    @@v += 1
  end

  def initialize
    puts "Cls2 #{@@v}"
    @@v += 1
    super
  end
end

以下, irb の実行結果.

irb(main):022:0> Cls2.class_variable_get(:@@v)
=> 1
irb(main):023:0> Cls2.new
Cls2 1
Cls1 2
=> #<Cls2:0x005586f67be8a0>
irb(main):024:0> Cls2.class_variable_get(:@@v)
=> 3
irb(main):025:0> Cls2.new
Cls2 3
Cls1 4
=> #<Cls2:0x005586f67b40a8>
irb(main):026:0> Cls2.class_variable_get(:@@v)
=> 5
irb(main):027:0> Cls1.new
Cls1 5
=> #<Cls1:0x005586f67a9090>
irb(main):028:0> Cls2.class_variable_get(:@@v)
=> 6
irb(main):029:0> Cls1.new
Cls1 6
=> #<Cls1:0x005586f679dfb0>
irb(main):030:0> Cls2.class_variable_get(:@@v)
=> 7
  • Cls2 にクラスメソッドが定義された時点で +1 となる
  • Cls2.new は Cls2 自身の initialize メソッドが呼ばれた時点で +1, 更に super クラスである Cls1.new の initialize が呼ばれて + 1 = 2
  • Cls2.new = 2 が計 2 回呼ばれているので, Cls2.new x 2 = 4 となる
  • Cls1.new は initialize 時に +1 される
  • Cls1.new = 1 が計 2 回呼ばれているので, Cls1.new x 2 = 2 となる

ということで, 最初のクラスメソッド定義時点で 1 + Cls2.new が 2 度呼ばれて 4 + Cls1 が 2 度呼ばれて 2 = 合計 7 となる.

んー, イマイチ...解らない.

Proc

以下のコードを実行するとどうなるか.

def foo(*args, &block)
  block.call(*args)
end

foo(1,2,3,4) do |*args|
  p args.length > 0 ? "hello" : args
end

hello を出力される

以下, irb による出力例.

irb(main):001:0> def foo(*args, &block)
irb(main):002:1>   block.call(*args)
irb(main):003:1> end
=> :foo
irb(main):004:0> 
irb(main):005:0* foo(1,2,3,4) do |*args|
irb(main):006:1*   p args.length > 0 ? "hello" : args
irb(main):007:1> end
"hello"
=> "hello"

以下, 解説より抜粋.

  1. 1 行目で引数の値を配列として受け取り, ブロックに配列を渡している
  2. 2 行目で * を付けて引数を渡している為, 配列が展開される
  3. 5 行目でブロック変数を渡しているが, *args と宣言されている為, [1, 2, 3, 4] が渡される
  4. 6 行目で args.length > 0 の結果は真となり, hello が出力される

ブロックをメソッドの引数として受け取る例. (プロを目指す人のためのRuby入門 言語仕様からテスト駆動開発・デバッグ技法まで を参考にして.)

irb(main):001:0> def words(&b)
irb(main):002:1>   puts 'AAAAA'
irb(main):003:1>   text = b.call('BBBBB')
irb(main):004:1>   puts text
irb(main):005:1>   puts 'CCCCC'
irb(main):006:1> endフムフム
=> :words
irb(main):007:0> 
irb(main):008:0* words do |text|
irb(main):009:1*   text * 2
irb(main):010:1> end
AAAAA
BBBBBBBBBB
CCCCC
=> nil
irb(main):001:0> def words(*arg, &b)
irb(main):002:1>   puts 'AAAAA'
irb(main):003:1>   puts arg
irb(main):004:1>   text = b.call(*arg)
irb(main):005:1>   puts text
irb(main):006:1>   puts 'CCCCC'
irb(main):007:1> end
=> :words
irb(main):008:0> 
irb(main):009:0* 
irb(main):010:0* words('a', 'b') do |*text|
irb(main):011:1*   text * 2
irb(main):012:1> end
AAAAA
a
b
a
b
a
b
CCCCC
=> nil

フムフム.

2018 年 05 月 22 日(火)

ジョギング

日課

  • (腕立て x 50 + 腹筋 x 50) x 3

夕飯

  • 奥さんが作った「小松菜とお揚げの炊いたん」が美味しかった

今日のるびぃ ~ REx - Ruby Examination にチャレンジ (8) ~

REx - Ruby Examination の問題を自分なりにアレンジした上で 1 〜 3 問くらいずつ解いていく. 正直言ってかなり難しい. 尚, irb に動作確認環境は以下の通り.

$ ruby --version
ruby 2.1.10p492 (2016-04-01 revision 54464) [x86_64-linux]
$ irb --version
irb 0.9.6(09/06/30)

Enumerable#partition

以下のコードを実行するとどうなるか.

(1..5).partition {|n| n.odd? }
p a

[[1, 3, 5], [2, 4]]

以下, irb による実行例.

irb(main):004:0> (1..5).partition {|n| n.odd? }
=> [[1, 3, 5], [2, 4]]
# 以下と同義
irb(main):005:0> (1..5).partition(&:odd?)
=> [[1, 3, 5], [2, 4]]

以下, 解説より抜粋.

  • 各要素をブロックの条件を満たす要素と満たさない要素に分割する
  • 各要素に対してブロックを評価して, その値が真であった要素の配列と, 偽であった要素の配列の 2 つを配列に入れて返す
  • 設問では, Integer#odd? は self が奇数となる場合に true を返すので, 奇数配列と偶数配列が結合された配列を返す

もう少し確認.

Enumerable#partition はブロックの条件を満たす要素と満たさない要素を分割して返す.

irb(main):006:0> (1..10).partition {|n| n % 2 == 0}
=> [[2, 4, 6, 8, 10], [1, 3, 5, 7, 9]]

ちょっと遊んでみる. 大文字, 小文字をざっくりと判別するメソッドを追加して, Enumerable#partition してみる.

# 大文字小文字を判別するメソッドを String クラスに追加する
class String
  def islower?
    self =~ /[a-z]$/ ? true : false
  end
  
  def isupper?
    self =~ /[A-Z]$/ ? true : false
  end
end
['A', 'Az', 'Za', 'Bq', 'GG'].partition(&:islower?)
['A', 'Az', 'Za', 'Bq', 'GG'].partition(&:isupper?)

以下, irb での実行例.

irb(main):015:0> ['A', 'Az', 'Za', 'Bq', 'GG'].partition(&:islower?)
=> [["Az", "Za", "Bq"], ["A", "GG"]]
irb(main):016:0> ['A', 'Az', 'Za', 'Bq', 'GG'].partition(&:isupper?)
=> [["A", "GG"], ["Az", "Za", "Bq"]]

Integer#odd は self が奇数であれば true を返す. 対して, Integer#even は self が偶数である場合, true を返す.

irb(main):017:0> 1.odd?
=> true
irb(main):018:0> 2.odd?
=> false
irb(main):019:0> 2.even?
=> true
irb(main):020:0> 100.even?
=> true
irb(main):021:0> 101.even?
=> false

例外

以下のコードを実行するとどうなるか.

begin
  10/0
rescue ZeroDivisionError
  print "ZeroDivisionError."
rescue
  print "Error."
else
  print "Else."
ensure
  print "Ensure."
end

ZeroDivisionError.Ensure.

以下, irb による実行例.

irb(main):001:0> begin
irb(main):002:1*   10/0
irb(main):003:1> rescue ZeroDivisionError
irb(main):004:1>   print "ZeroDivisionError."
irb(main):005:1> rescue
irb(main):006:1>   print "Error."
irb(main):007:1> else
irb(main):008:1*   print "Else."
irb(main):009:1> ensure
irb(main):010:1*   print "Ensure."
irb(main):011:1> end
ZeroDivisionError.Ensure.=> nil

以下, 解説より抜粋.

  • ZeroDivisionError は整数に対して整数の 0 で除算を行ったときに発生する
  • エラーを受け取るためにはrescueで、例外を受け取った際の処理を記述する
  • エラーが発生しなかった場合の処理を行うには else を用いる
  • エラー発生有無に関わらず, 必ず実行される, 後処理を行うには ensure を用いる

フムフム.

2018 年 05 月 21 日(月)

ジョギング

  • 自宅 → 香椎宮香椎浜
  • 体はきついし, 左膝に痛みが走るし散々だった

日課

  • (腕立て x 50 + 腹筋 x 50) x 3

夕飯

  • ライトな天ぷら
  • ちゃんとした天ぷら用お鍋が欲しくなった

今日のるびぃ ~ REx - Ruby Examination にチャレンジ (7) ~

REx - Ruby Examination の問題を自分なりにアレンジした上で 1 〜 3 問くらいずつ解いていく. 正直言ってかなり難しい. 尚, irb に動作確認環境は以下の通り.

$ ruby --version
ruby 2.1.10p492 (2016-04-01 revision 54464) [x86_64-linux]
$ irb --version
irb 0.9.6(09/06/30)

モジュールメソッド探索

以下のコードを実行するとどうなるか.

module Mod1; end

module Mod2; end

class Cls
  include Mod1, Mod2
end

p Cls.ancestors

[Cls, Mod1, Mod2, Object, Kernel, BasicObject]

以下, irb にて確認.

irb(main):001:0> module Mod1; end
=> nil
irb(main):002:0> 
irb(main):003:0* module Mod2; end
=> nil
irb(main):004:0> 
irb(main):005:0* class Cls
irb(main):006:1>   include Mod1, Mod2
irb(main):007:1> end
=> Cls
irb(main):008:0> 
irb(main):009:0* p Cls.ancestors
[Cls, Mod1, Mod2, Object, Kernel, BasicObject]
=> [Cls, Mod1, Mod2, Object, Kernel, BasicObject]

以下, 解説より抜粋.

  • include はモジュールのメソッドをインスタンスメソッドとして追加する
  • メソッド探索順は self の後に追加される
  • 複数モジュールを指定した場合は, 最初に指定したメソッド探索される (設問だと Mod1 が優先される)

以下, 探索の順番を図示したもの.

以下, include でモジュールを指定する順番の違い.

module Mod1
  def foo
    p 'Mod1#foo'
  end
end

module Mod2
  def foo
    p 'Mod2#foo'
  end
end

class Cls1
  include Mod1, Mod2
  def bar
    foo
  end
end

p Cls1.new.bar #=> "Mod1#foo"

class Cls2
  include Mod1
  include Mod2
  def bar
    foo
  end
end

p Cls2.new.bar #=> "Mod2#foo"

定数探索

以下のコードを実行するとどうなるか.

module Mod1
  def refer_const
    CONST
  end
end

module Mod2
  CONST = '010'
end

class Cls1
  CONST = "001"
end

class Cls2 < Cls1
  include Mod2
  include Mod1
  CONST = '100'
end

c = Cls2.new
p c.refer_const

Mod1#refer_const が呼ばれるが, Mod1 内に CONST は未定義なので例外が発生する

以下, irb による動作確認.

irb(main):021:0* c = Cls2.new
=> #<Cls2:0x0055ce2eda1ab8>
irb(main):022:0> p c.refer_const
NameError: uninitialized constant Mod1::CONST

以下, 解説より抜粋.

  • refer_const はモジュール Mod1 にあるが, CONST はレキシカルスコープとなる為, モジュール Mod1 のスコープを探索する
  • 設問では CONST が見つからないため例外が発生する

以下のように Mod1 内に CONST を定義することで, 例外は回避出来る.

module Mod1
  CONST = '000'
  def refer_const
    CONST
  end
end

module Mod2
  CONST = '010'
end

class Cls1
  CONST = "001"
end

class Cls2 < Cls1
  include Mod1
  CONST = '100'
end

c = Cls2.new
p c.refer_const

irb で実行すると, 以下のように 000 が出力される.

irb(main):021:0* c = Cls2.new
=> #<Cls2:0x00562baf7e4fb0>
irb(main):022:0> p c.refer_const
"000"
=> "000"

フムフム.

2018 年 05 月 20 日(日)

ジョギング

  • 今日はお休み
  • 足もだるいし, 体もキツかったので

日課

  • ついでに休み

香椎宮

  • お参りに行った
  • お昼ごはんは香椎宮近くの中華屋さんで, 美味しかった

今日のるびぃ ~ REx - Ruby Examination にチャレンジ (6) ~

REx - Ruby Examination の問題を自分なりにアレンジした上で 1 〜 3 問くらいずつ解いていく. 正直言ってかなり難しい. 尚, irb に動作確認環境は以下の通り.

$ ruby --version
ruby 2.1.10p492 (2016-04-01 revision 54464) [x86_64-linux]
$ irb --version
irb 0.9.6(09/06/30)

定数探索

以下のコードを実行するとどうなるか.

class Cls1
  CONST = "AAA"

  def self.name
    const_get(:CONST)
  end
end

class Cls2 < Cls1
  CONST = "BBB"
end

puts Cls2.name

BBB と出力される

以下, irb による確認.

irb(main):001:0> class Cls1
irb(main):002:1>   CONST = "AAA"
irb(main):003:1> 
irb(main):004:1*   def self.name
irb(main):005:2>     const_get(:CONST)
irb(main):006:2>   end
irb(main):007:1> end
=> :name
irb(main):008:0> 
irb(main):009:0* class Cls2 < Cls1
irb(main):010:1>   CONST = "BBB"
irb(main):011:1> end
=> "BBB"
irb(main):012:0* puts Cls2.name
BBB
=> nil

以下, 解説より抜粋.

  • Cls1#nameClass#name をオーバーライトしている為, const_get が呼ばれる
  • const_get は, self に定義されている定数を探索する. 自クラスに定義がない場合は、メソッドと同様に探索を行う
  • 設問の 5 行目時点のインスタンスは Cls2 クラスで, Cls1#name は Cls2 クラスの定数の値 BBB を返します。

解説がイマイチ理解出来なかったので, 自分なり irb を使って確認.

Cls1#nameClass#name をオーバーライトしている為, const_get が呼ばれる

Class クラスに name というメソッドってあるのかな...と思ったら, 以下のように存在している.

irb(main):016:0> Class.methods.grep(/name/)
=> [:name]

これは, Module#name を継承している為, name メソッドを呼び出すことが出来る. このメソッド自体は, 以下の実行例のように, モジュールやクラスの名前を返す機能があるが, 設問ではオープンクラスしてこの機能を const_get の結果を返すように上書きしている.

irb(main):001:0> class Cls1; end
=> nil
irb(main):002:0> Cls1.name
=> "Cls1"
irb(main):003:0> class Cls1
irb(main):004:1>   def self.name
irb(main):005:2>       puts 'fooo'
irb(main):006:2>   end
irb(main):007:1> end
=> :name
irb(main):008:0> Cls1.name
fooo
=> nil

上記では, Class#namefooo を puts するように上書き (機能変更) している.

const_get は, self に定義されている定数を探索する. 自クラスに定義がない場合は、メソッドと同様に探索を行う

以下のように定数の探索が行われている.

irb(main):001:0> module Mod1
irb(main):002:1>   CONST = 'Mod1'
irb(main):003:1> end
=> "Mod1"
irb(main):004:0> 
irb(main):005:0* class Cls1
irb(main):006:1>   CONST = 'Cls1'
irb(main):007:1> end
=> "Cls1"
irb(main):008:0> 
irb(main):009:0* class Cls2 < Cls1
irb(main):010:1>   def self.name
irb(main):011:2>     const_get(:CONST)
irb(main):012:2>   end
irb(main):013:1> end
=> :name
irb(main):014:0> Cls2.name
=> "Cls1"
...
irb(main):001:0> module Mod1
irb(main):002:1>   CONST = 'Mod1'
irb(main):003:1> end
=> "Mod1"
irb(main):004:0> 
irb(main):005:0* class Cls1
irb(main):006:1>   CONST = 'Cls1'
irb(main):007:1> end
=> "Cls1"
irb(main):008:0> 
irb(main):009:0* class Cls2 < Cls1
irb(main):010:1>   include Mod1
irb(main):011:1>   def self.name
irb(main):012:2>     const_get(:CONST)
irb(main):013:2>   end
irb(main):014:1> end
=> :name
irb(main):015:0> Cls2.name
=> "Mod1"

フムフム.

直近で Python の unittest で試行錯誤していて得られた知見の幾つか (3)

環境

以下のような環境でやってます.

$ sw_vers
ProductName:    Mac OS X
ProductVersion: 10.11.6
BuildVersion:   15G19009

$ python --version
Python 3.6.4

前回の記事の続きです.

inokara.hateblo.jp

inokara.hateblo.jp

知見 (5) 例外発生をテストする

関数

以下のような関数を作成しました. 'hello' という引数が渡されることを想定していますが, 引数が無かったり, 'hello' 以外が引数として渡された際に例外が発生することを想定しています.

>>> def helloworld(args=None):
...     if args is None:
...         raise Exception('Noooooooooooooo Argumentttttttt.')
...     elif 'hello' not in args:
...         raise Exception('Invaliddddddddd Argumentttttttt.')
...     else:
...         return args
...
>>> helloworld()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 3, in helloworld
Exception: Noooooooooooooo Argumentttttttt.
>>> helloworld('foo')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 5, in helloworld
Exception: Invaliddddddddd Argumentttttttt.
>>> helloworld('hello')
hello

テスト

例外の発生をテストする場合には, assertRaises というアサーションを利用します. assertRaises は引数に例外クラス (SomeException) を指定します.

# 書き方 1
self.assertRaises(SomeException, do_something):

# 書き方 2
with self.assertRaises(SomeException):
    do_something()

以下のように assertRaises を利用したテストを書きました.

import unittest
import chiken

class HelloWorldTest(unittest.TestCase):
    def test_helloworld(self):
        self.assertEqual(chiken.helloworld('hello'), 'hello')

    def test_helloworld_no_argument_exception(self):
        self.assertRaises(Exception, chiken.helloworld, None)

    def test_helloworld_no_argument_exception_message(self):
        with self.assertRaises(Exception) as ex:
            chiken.helloworld(None)
        ex_message = ex.exception.args[0]
        self.assertEqual(ex_message, 'Noooooooooooooo Argumentttttttt.')

    def test_helloworld_invalid_argument_exception(self):
        self.assertRaises(Exception, chiken.helloworld, 'foo')

    def test_helloworld_invalid_argument_exception_message(self):
        with self.assertRaises(Exception) as ex:
            chiken.helloworld('foo')
        ex_message = ex.exception.args[0]
        self.assertEqual(ex_message, 'Invaliddddddddd Argumentttttttt.')

例外メッセージをテストしたい場合には, コンテキストマネージャを利用します.

...
    def test_helloworld_invalid_argument_exception_message(self):
        with self.assertRaises(Exception) as ex:
            chiken.helloworld('foo')
        ex_message = ex.exception.args[0]
        self.assertEqual(ex_message, 'Invaliddddddddd Argumentttttttt.')

コンテキストマネージャを利用した場合, 引数 exception で指定されたオブジェクトを格納する為, このオブジェクトを利用して例外発生時の詳細を確認することが出来ます. 上記の例では, 例外メッセージをオブジェクトから取得しています.

テストを実行する.

$ python -m unittest tests.test_chiken -v
test_helloworld (tests.test_chiken.HelloWorldTest) ... ok
test_helloworld_invalid_argument_exception (tests.test_chiken.HelloWorldTest) ... ok
test_helloworld_invalid_argument_exception_message (tests.test_chiken.HelloWorldTest) ... ok
test_helloworld_no_argument_exception (tests.test_chiken.HelloWorldTest) ... ok
test_helloworld_no_argument_exception_message (tests.test_chiken.HelloWorldTest) ... ok

----------------------------------------------------------------------
Ran 5 tests in 0.001s

OK

LGTM.

知見 (6) ログ出力をテストする

関数

先述の例外発生で利用した関数に logging モジュールを追加して, ログを出力するようにしました.

import logging

logger = logging.getLogger()
logger.setLevel(logging.INFO)

def helloworld2(args=None):
    if args is None:
        logger.error('Noooooooooooooo Argumentttttttt.')
    elif 'hello' not in args:
        logger.error('Invaliddddddddd Argumentttttttt.')
    else:
        print(args)

この関数を呼び出してみます.

>>> import logging
>>>
>>> logger = logging.getLogger()
>>> logger.setLevel(logging.INFO)
>>>
>>> def helloworld2(args=None):
...     if args is None:
...         logger.error('Noooooooooooooo Argumentttttttt.')
...     elif 'hello' not in args:
...         logger.error('Invaliddddddddd Argumentttttttt.')
...     else:
...         print(args)
...
>>> helloworld2()
Noooooooooooooo Argumentttttttt.
>>> helloworld2('foo')
Invaliddddddddd Argumentttttttt.
>>> helloworld2('hello world')
hello world

ということで, 意図したログが出力されることを確認したいと思います.

テスト

testfixtures

ログ出力を確認する為には, testfixtures モジュールの LogCapture クラスを利用します.

pypi.org

LogCapture クラスを利用することで, ログの出力を簡単にテストすることが出来るとのことです.

簡単に動作確認.

>>> import logging
>>> from testfixtures import LogCapture
>>> with LogCapture() as log:
...     logger = logging.getLogger()
...     logger.info('a message')
...
>>> log.check(('root', 'INFO', 'a message'))
>>>
>>> print(log)
root INFO
  a message
>>> log.check(('root', 'INFO', 'message'))
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/path/to/.pyenv/versions/chiken.py/lib/python3.6/site-packages/testfixtures/logcapture.py", line 180, in check
    recursive=self.recursive_check
  File "/path/to/.pyenv/versions/chiken.py/lib/python3.6/site-packages/testfixtures/comparison.py", line 563, in compare
    raise AssertionError(message)
AssertionError: sequence not as expected:

same:
()

expected:
(('root', 'INFO', 'message'),)

actual:
(('root', 'INFO', 'a message'),)

テストコード

以下のようなテストを書きました.

class HelloWorldTest2(unittest.TestCase):
    def test_helloworld_no_argument_error_log(self):
        with LogCapture(level=logging.INFO) as log:
            chiken.helloworld2(None)
            log.check(('root', 'ERROR', 'Noooooooooooooo Argumentttttttt.'))

    def test_helloworld_invalid_error_log(self):
        with LogCapture(level=logging.INFO) as log:
            chiken.helloworld2('foo')
            log.check(('root', 'ERROR', 'Invaliddddddddd Argumentttttttt.'))

テストを実行してみます.

$ python -m unittest tests.test_chiken.HelloWorldTest2 -v
test_helloworld_invalid_error_log (tests.test_chiken.HelloWorldTest2) ... ok
test_helloworld_no_argument_error_log (tests.test_chiken.HelloWorldTest2) ... ok

----------------------------------------------------------------------
Ran 3 tests in 0.001s

OK

LGTM.

以上

知見でした.

2018 年 05 月 19 日(土)

ジョギング

  • 自宅 → 香椎宮香椎浜で 30 分くらい
  • ペースはそこまで速くないのに, とてもキツイ...

日課

  • (腕立て x 50 + 腹筋 x 50) x 3

お味噌

  • 奥さんが手作り味噌教室で味噌をこねてきた
  • この教室で作るお味噌はとても美味しくてお気に入り
  • 出来上がりが楽しみ

手羽と大根の煮込み

  • 夕飯に奥さんが煮込んでくれた
  • 日本酒のお供にサイコーだった

今日のるびぃ ~ REx - Ruby Examination にチャレンジ (5) ~

REx - Ruby Examination の問題を自分なりにアレンジした上で 1 〜 3 問くらいずつ解いていく. 正直言ってかなり難しい. 尚, irb に動作確認環境は以下の通り.

$ ruby --version
ruby 2.1.10p492 (2016-04-01 revision 54464) [x86_64-linux]
$ irb --version
irb 0.9.6(09/06/30)

Proc (ブロック引数)

以下のコードを実行するとどうなるか.

def foo(&block, *args)
  block.call(*args)
end

foo('a', 'b', 'c') do |*args|
  p args.include?('d') ? "hello" : args
end

例外 (SyntaxError) が発生する

以下, irb による確認.

irb(main):001:0> def foo(&block, *args)
irb(main):002:1>   block.call(*args)
irb(main):003:1> end
SyntaxError: (irb):1: syntax error, unexpected ',', expecting ')'
def foo(&block, *args)
               ^
(irb):3: syntax error, unexpected keyword_end, expecting end-of-input

以下, 解説より抜粋.

  • ブロック引数は仮引数の最後に記述する
def foo(*args, &block)
...

以下, 改めて irb による確認.

irb(main):001:0> def foo(*args, &block)
irb(main):002:1>   block.call(*args)
irb(main):003:1> end
=> :foo
irb(main):004:0> 
irb(main):005:0* foo('a', 'b', 'c') do |*args|
irb(main):006:1*   p args.include?('d') ? "hello" : args
irb(main):007:1> end
["a", "b", "c"]
=> ["a", "b", "c"]

method_missing

以下のコードを実行するとどうなるか.

module Mod
  def method_missing(id, *args)
    puts "Mod#method_missing"
  end
end

class Cls1
  include Mod
  def method_missing(id, *args)
    puts "Cls1#method_missing"
  end
end

class Cls2 < Cls1
  class << self
    def method_missing(id, *args)
      puts "Cls2.method_missing"
    end
  end
end

Cls2.new.my_method

Cls1#method_missing

以下, irb による確認.

irb(main):021:0> 
irb(main):022:0* Cls2.new.my_method
Cls1#method_missing
=> nil

以下, 解説より抜粋.

  • method_missing は, 継承チェーンを辿った末に呼び出したメソッドが見つからなかった場合に呼び出される
  • Cls2 において class << self; end で定義されたメソッドは, 特異クラスのメソッドとなる為, 設問の Cls2.new.my_method を呼び出すと A#method_missing が出力される

以下は, Cls2 の特異クラスのメソッド, Cls1インスタンスメソッドを呼びだそうとした場合.

irb(main):023:0> Cls2.my_method
Cls2.method_missing
=> nil
irb(main):024:0> Cls1.new.my_method
Cls1#method_missing
=> nil

メソッドの可視性

以下のコードを実行するとどうなるか.

class Cls
  private
  def initialize
  end
end

p Cls.new.public_methods.include? :initialize

false と表示される

以下, irb による確認.

irb(main):001:0> class Cls
irb(main):002:1>   private
irb(main):003:1>   def initialize
irb(main):004:2>   end
irb(main):005:1> end
=> :initialize
irb(main):006:0> 
irb(main):007:0* p Cls.new.public_methods.include? :initialize
false
=> false

以下, 解説より抜粋.

  • initialize メソッドの可視性は private となる
  • initialize の可視性を public に設定したとしても, 必ず private になる.
irb(main):001:0> class Cls
irb(main):002:1>   public
irb(main):003:1>   def initialize
irb(main):004:2>   end
irb(main):005:1> end
=> :initialize
irb(main):006:0> 
irb(main):007:0* p Cls.new.public_methods.include? :initialize
false
=> false

ヘエ~

ちなみに, private メソッドを確認する場合には, private_methods メソッドで確認する.

irb(main):010:0> Cls.new.private_methods.grep(/init/)
=> [:initialize, :initialize_copy, :initialize_dup, :initialize_clone]

フムフム.

(今さらジロー) AWS Lambda の Dead Letter Queue を試す

tl;dr

今さらジローではあるが, AWS Lambda で実装されている Dead Letter Queue について簡単にチュートリアルしたので, その様子をダイジェストで.

docs.aws.amazon.com

尚, 本記事では, 以下のバージョン Serverless Framework を利用して Lambda ファンクションをはじめ各種リソースを構築することを前提としている.

$ sls version
1.24.1

また, 本記事で利用したコードは Python を利用し. 以下の Github リポジトリにアップしているので, 誤り等あればプルリクエストを頂けると嬉しいでござる.

github.com

うんちく

Dead Letter Queue について

Dead Letter Queue とは.

非同期で呼び出されてエラーになってしまった Lambda 関数について, その未処理イベントを Amazon SQS キューや Amazon SNS トピックに送信する

Lambda 関数を非同期で呼び出した場合, エラーとなった関数は 2 回のリトライが行われて, その後, そのイベントは破棄されてしまうが, Dead Letter Queue を Lambda 関数に設定すると, 未処理イベントをキューイング又は通知することが可能になる.

Dead Letter Queue のメッセージには, イベントメッセージ (Lambda ファンクションを呼び出す際に引数 event に渡される値)と, メッセージアトリビュートにはイベントが処理されなかった原因を特定する為に役立つ以下のような内容が記録されている. (以下, ドキュメントより引用.)

名前 タイプ
RequestID 文字列 一意のリクエスト ID
ErrorCode 数値 3 桁の HTTP エラーコード
ErrorMessage 文字列 エラーメッセージ (1 KB に切り捨て)

例えば, 以下のようなコードにて例外が発生した場合, ErrorMessage には Errooooooooooooooooooo! が入ることになる.

def error_function():
    raise Exception('Errooooooooooooooooooo!')

Lambda の呼び出しパターン

Dead Letter Queue を触るにあたって, Lambda を呼び出す場合の呼び出しパターン (Invovation Type) について確認する.

パターン (InvocationType) 概要
Event 非同期呼び出しを行い, 関数はキューインした後に実行される. 関数処理が失敗した場合, 自動的に 2 回のリトライが実施される (初回実行を含めて 3 回実行される)
RequestResponse 同期呼び出しで, デフォルトの Invocation Type となる. リトライ処理は SDK に依存しており, 独自に実装する必要がある
DryRun 関数は実行せず, 呼び出し元の関数の権限や入力が有効かどうか等の検証を行う

以下は, Invocation Type の Lambda 関数を実行して, エラーが発生した場合の CloudWatch Logs の一部を抜粋したもの.

$ awslogs get /aws/lambda/dlq-tutorial-debug-dlq_tutorial ALL --aws-region=ap-northeast-1 --start='60m ago' | grep 'START RequestId: 704466d2-5b67-11e8-86fa-9b55620f256a'
/aws/lambda/dlq-tutorial-debug-dlq_tutorial 2018/05/19/[$LATEST]1fb5c8a186c044f8bacef1f24d9a8746 START RequestId: 704466d2-5b67-11e8-86fa-9b55620f256a Version: $LATEST
/aws/lambda/dlq-tutorial-debug-dlq_tutorial 2018/05/19/[$LATEST]1fb5c8a186c044f8bacef1f24d9a8746 START RequestId: 704466d2-5b67-11e8-86fa-9b55620f256a Version: $LATEST
/aws/lambda/dlq-tutorial-debug-dlq_tutorial 2018/05/19/[$LATEST]1fb5c8a186c044f8bacef1f24d9a8746 START RequestId: 704466d2-5b67-11e8-86fa-9b55620f256a Version: $LATEST

先述の通り, リトライを含めて 3 回関数が実行されていることがわかる. (同一 RequestId が 3 回実行されている.)

尚, Invocation Type については, 以下のドキュメント, 記事が参考になった.

有難うございました.

ということで, 呼び出しパターン Event の Lambda 関数を利用して Dead Letter Queue をチュートリアルしていく.

Serverless Framework で Dead Letter Queue (以後, DLQ とする)

各種コードについて

サンプルプロジェクトの作成とデプロイ

以下のように Python 3 でプロジェクトを作成する.

sls create --template=aws-python3 --path=dlq-tutorial

デプロイについては,

sls deploy

一発なのが嬉しい限り.

serverless.yml

Dead Letter Queue を設定する場合, serverless-plugin-lambda-dead-letter プラグインを利用すると設定も簡単で便利. 以下, serverless.yml の一部を抜粋.

# file name: serverless.yml
...
  iamRoleStatements:
    - Effect: Allow
      Action:
        - sqs:SendMessage
      Resource:
        - Fn::Join: [ ":", [ "arn:aws:sqs", Ref: "AWS::Region", Ref: "AWS::AccountId", "${self:custom.dlq_tutorial}" ] ]

plugins:
  - serverless-plugin-lambda-dead-letter

custom:
  dlq_tutorial: dlq-tutorial
  
functions:
  dlq_tutorial:
    handler: handler.hello
    memorySize: 128
    timeout: 300
    deadLetter:
      sqs: ${self:custom.dlq_tutorial}
...

Lambda ファンクション

以下のように Exception 例外を発生させてみる. Lambda では例外を発生させることで, DLQ にメッセージを送信する.

# file name: handler.py
...
def hello(event, context):
    if event == {}:
        raise Exception('Noooooooooooooo eventtttttttttt.')
    else:
        return world(event)


def world(event):
    v = event.get('hello')
    if v is not None:
        return 'hello world.'
    else:
        raise Exception('Erroooooooooooooooooooor.')
...

Lambda ファンクションのエラーについては, 以下のドキュメントが参考になる.

docs.aws.amazon.com

ドキュメント読むの大事.

尚, 念の為, 上記の Lambda ファンクションについては, ユニットテストも書いた.

$ python -m unittest tests.test_handler -v
test_hello (tests.test_handler.HelloTest) ... ok
test_hello_exception (tests.test_handler.HelloTest) ... ok
test_hello_exception_message (tests.test_handler.HelloTest) ... ok
test_world (tests.test_handler.HelloTest) ... ok
test_world_exception (tests.test_handler.HelloTest) ... ok
test_world_exception_message (tests.test_handler.HelloTest) ... ok

----------------------------------------------------------------------
Ran 6 tests in 0.001s

OK

尚, unittest において例外発生をテストする方法については, 別の記事にまとめたいと思う.

DLQ を処理する Lambda ファンクション

以下のような書いた.こちらについても, 長くなるので一部抜粋. 基本的には SQS に蓄積されているメッセージを取得して何かする感じ.

# file name: handler.py
...
def dlq_message_handler(event, context):
    sqs = boto3.resource('sqs')
    queue_name = 'dlq-tutorial'
    q = sqs.get_queue_by_name(QueueName=queue_name)
    messages = []
    while True:
        q_messages = q.receive_messages(AttributeNames=['All'],
                                        MessageAttributeNames=['All'],
                                        MaxNumberOfMessages=10)
        if q_messages:
            for qm in q_messages:
                attr = qm.attributes
                body = qm.body
                msga = qm.message_attributes

                sent_time_stamp = attr.get('SentTimestamp')
                error_message = msga['ErrorMessage']['StringValue']
...

取得したメッセージは, その後に何をするかで加工の方法は異なると思うが, 改めて再処理の Lambda ファンクションを呼んで処理を完了させることも可能.

今回は, 取得したメッセージから, 以下の要素を取得して csv ファイルに生成するようにしてみた.

受信メッセージから dict 型のデータに加工して, csv モジュールの DictWriter クラスを用いることで簡単に csv 化することが出来た.

DLQ に蓄積されたメッセージ

エラーメッセージの処理例

ということで, 1 分毎に例外が発生しているので, しばらく放置しておくと SQS にメッセージが蓄積されている.

$ aws sqs receive-message \
  --queue-url https://ap-northeast-1.queue.amazonaws.com/012345678912/dlq-tutorial \
  --attribute-names All \
  --message-attribute-names All \
  --max-number-of-messages 10 \
  --query Messages[].MessageId
[
    "b512e862-3e13-4def-8591-732a280fe17b"
]

ひとまず, csv に書き出す処理を実行してみる.

$ sls invoke local --stage debug --function dlq_message_handler --data ''
null

csv ファイルが作成されているので, 以下のように確認してみる.

$ cat example.csv
SentTimestamp,ErrorMessage,EventBody
1526741198363,Erroooooooooooooooooooor.,{'hell': 'world'}
1526740906810,Erroooooooooooooooooooor.,{'hell': 'world'}
1526741150850,Erroooooooooooooooooooor.,{'hell': 'world'}
1526741089397,Erroooooooooooooooooooor.,{'hell': 'world'}
1526740981380,Erroooooooooooooooooooor.,{'hell': 'world'}
1526741027864,Erroooooooooooooooooooor.,{'hell': 'world'}

この csv ファイルを S3 にアップロードしておいて, Pre-Signed URL を発行して Slack に通知するとかが良さそうな気がしている.

尚, 今回は検証を目的としており, 1 分毎にエラーを吐いている為, とんでもないシステムになってしまいそうだけど, 実際の運用ではこのようなエラーが発生しないように注意を払って実装したい...

エラー検知

DLQ リソースに SQS を利用している場合, 「メッセージがキューに蓄積されていること = Lambda ファンクションにエラーが発生している」になると思うが, この場合, CloudWatch メトリクスの NumberOfMessagesReceived あたりを監視しておくと良さそうな気がする.

ところが, ドキュメントによると, SQS の CloudWatch では詳細モニタリングは提供されていない為, 5 分よりも短い間隔での監視が要件となる場合には, 今回のような監視を用途とした Lambda 関数 (DLQ を処理する Lambda ファンクション) を別途で用意する必要があると思われる.

以上

今さらジロー」であるが, Lambda の Dead Letter Queue 機能に触れてみた. エラー自体をキューに放り込むことが出来るということで, 未処理エラーの検知や再処理が比較的簡単に行えるのではと考えている. 今後は積極的に DLQ を利用していきたい.

2018 年 05 月 18 日(金)

ジョギング

  • 自宅からの香椎宮からの香椎浜で 34 min くらい
  • 引き続き, 今日もキツかった...

日課

  • (腕立て x 50 + 腹筋 x 50) x 5

不安定な天気

  • 暑かったり...
  • 大雨がふったり...
  • 夜になると急に冷え込んで...
  • 体調がアレ

今日のるびぃ ~ REx - Ruby Examination にチャレンジ (4) ~

REx - Ruby Examination の問題を自分なりにアレンジした上で 1 〜 3 問くらいずつ解いていく. 正直言ってかなり難しい. 尚, irb に動作確認環境は以下の通り.

$ ruby --version
ruby 2.1.10p492 (2016-04-01 revision 54464) [x86_64-linux]
$ irb --version
irb 0.9.6(09/06/30)

Enumerator::Lazy

以下のプログラムはEnumerator::Lazyを使っている. 先頭から 5 つの値を取り出すにはどのメソッドが必要か.

(1..10).lazy.map{|num| num + 1}

first(5) take(5).force

以下, irb にて動作確認.

irb(main):011:0> (1..10).lazy.map{|num| num + 1}
=> #<Enumerator::Lazy: #<Enumerator::Lazy: 1..10>:map>
irb(main):012:0> (1..10).lazy.map{|num| num + 1}.first(5)
=> [2, 3, 4, 5, 6]
irb(main):013:0> (1..10).lazy.map{|num| num + 1}.take(5)
=> #<Enumerator::Lazy: #<Enumerator::Lazy: #<Enumerator::Lazy: 1..10>:map>:take(5)>
irb(main):014:0> (1..10).lazy.map{|num| num + 1}.take(5).force
=> [2, 3, 4, 5, 6]

以下, 解説より抜粋.

  • Lazy から値を取り出すには, Enumerator::Lazy#force または Enumerable#first を呼び出す
  • 設問では「先頭から 5 つ」とあるので, first(5) として取り出す
  • Enumerator::Lazy#take は Enumerable#take と異なり, Enumerator::Lazyのインスタンスを返す為, Enumerator::Lazy#force を利用して値を取り出す

以下, 関連するメソッドについて整理.

メソッド 詳細
Enumerable#first Enumerable オブジェクトの最初の要素, もしくは最初の n 要素を返す
Enumerator::Lazy#take Enumerable#take と同じですが, 配列ではなく Enumerator::Lazy を返す
Enumerator::Lazy#force 全ての要素を含む配列を返. Lazy から実際に値を取り出すのに使う

mix-in (メソッドの探索)

以下のコードを実行するとどうなるか.

module Mod1
end

module Mod2
end

module Mod3
end

class Cls
  include Mod1
  include Mod2
  include Mod3
end

p Cls.ancestors

[Cls, Mod3, Mod2, Mod1, Object, Kernel, BasicObject]

以下, irb で確認.

irb(main):017:0> Cls.ancestors
=> [Cls, Mod3, Mod2, Mod1, Object, Kernel, BasicObject]

以下, 解説より抜粋.

  • include はモジュールのメソッドをインスタンスメソッドとして追加する
  • メソッド探索順は self の後に追加される
  • 複数回 include された場合には, 後に宣言されたモジュールのメソッドから探索される

図示すると以下のような感じになる.

f:id:inokara:20180519094351p:plain

さらに, 以下のような場合にどうなるか.

module Mod1
end

module Mod2
end

module Mod3
end

class Cls1
  include Mod3
end

class Cls2 < Cls1
  include Mod1
  include Mod2
end

p Cls1.ancestors
p Cls2.ancestors

[Cls1, Mod3, Object, Kernel, BasicObject] [Cls2, Mod2, Mod1, Cls1, Mod3, Object, Kernel, BasicObject]

以下, irb による確認.

irb(main):019:0> p Cls1.ancestors
[Cls1, Mod3, Object, Kernel, BasicObject]
=> [Cls1, Mod3, Object, Kernel, BasicObject]
irb(main):020:0> p Cls2.ancestors
[Cls2, Mod2, Mod1, Cls1, Mod3, Object, Kernel, BasicObject]
=> [Cls2, Mod2, Mod1, Cls1, Mod3, Object, Kernel, BasicObject

以下, 解説.

  • include が実行されると, インタプリタは指定されたモジュールに対する無名クラスを作成して, スーパークラスとの間に組み入れる
  • 上記だと, Cls2 と Cls1 の間に Mod2 と Mod1 は挿入される

図示すると以下のような感じになる.

f:id:inokara:20180519094337p:plain

フムフム.

2018 年 05 月 17 日(木)

ジョギング

  • 自宅からの香椎宮からの香椎浜で 30 min くら
  • 気温のせいかとてもキツかった...

日課

  • お休み

ここ数日

  • 暑い...
  • 頭痛が続く

竹乃屋

  • 香椎駅近くに出来たので, 夕飯を食べてきた
  • 出来立てホヤホヤのお店で, 店員さんも施設も初々しかった

今日のるびぃ ~ REx - Ruby Examination にチャレンジ (4) ~

REx - Ruby Examination の問題を自分なりにアレンジした上で 1 〜 3 問くらいずつ解いていく. 正直言ってかなり難しい. 尚, irb に動作確認環境は以下の通り.

$ ruby --version
ruby 2.1.10p492 (2016-04-01 revision 54464) [x86_64-linux]
$ irb --version
irb 0.9.6(09/06/30)

クラスメソッド

以下のコードを実行するとどうなるか.

class Cls
  class << Cls
    def foo
      'Cls.foo'
    end
  end

  def foo
    'Cls#foo'
  end
end

p Cls.new.foo

'Cls#foo' が表示される

以下, irb で動作確認.

irb(main):013:0* p Cls.new.foo
"Cls#foo"
=> "Cls#foo"

以下, 解説より抜粋.

  • 特異クラス内 class << Cls; end で宣言されたメソッドは特異メソッドになる
  • 特異メソッドは def Cls.foo; end でも宣言することができる
  • 設問では, インスタンスメソッドを呼び出している為, "Cls#foo" が出力される
irb(main):001:0> class Cls
irb(main):002:1>   class << Cls
irb(main):003:2>     def foo
irb(main):004:3>       'foo'
irb(main):005:3>     end
irb(main):006:2>   end
irb(main):007:1> end
=> :foo
irb(main):008:0> def Cls.bar
irb(main):009:1>   'bar'
irb(main):010:1> end
=> :bar
irb(main):011:0> Cls.foo
=> "foo"
irb(main):012:0> Cls.bar
=> "bar"

コマンドラインオプション

Ruby使用可能なオプションではないものを選択する.

-t -f

その他の選択肢について (ドキュメントから引用).

オプション 用途
-l 行末の自動処理を行う. $\$/ と同じ値に設定し, print での出力時に改行を付加する. また, -n フラグまたは -p フラグが指定されていると gets で読み込まれた各行の最後に対して String#chop! を行う.
-p -n フラグとほぼ同じあるが, 各ループの最後に変数 $_ の値を出力するようになる.

全てのコマンドラインオプションについては, 以下のドキュメントを確認すること.

フムフム