ようへいの日々精進XP

よかろうもん

Amazon Elasticsearch Service クラスタを作成するだけの CloudFormation テンプレートを作った #ただそれだけ

追記 (2018/06/09)

初めて VPC 内に Amazon Elasticsearch Service クラスタを作成する際, AWSServiceRoleForAmazonElasticsearchService という IAM ロールを作成する必要があります. この IAM ロールを CloudFormation で作成する方法を見つけることが出来ませんでした.

ということで, 一度, 手動で VPC 内に Amazon Elasticsearch Service クラスタを作っておくことを今のところはおすすめ致します... 手動で作っておくことで, AWSServiceRoleForAmazonElasticsearchService が作成されます.

尚, ポリシーについては, AmazonElasticsearchServiceRolePolicy というポリシーが付与され, ポリシーのドキュメントは以下のような内容となります.

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "Stmt1480452973134",
            "Action": [
                "ec2:CreateNetworkInterface",
                "ec2:DeleteNetworkInterface",
                "ec2:DescribeNetworkInterfaces",
                "ec2:ModifyNetworkInterfaceAttribute",
                "ec2:DescribeSecurityGroups",
                "ec2:DescribeSubnets",
                "ec2:DescribeVpcs"
            ],
            "Effect": "Allow",
            "Resource": "*"
        }
    ]
}

どうも

#ただそれだけ の CloudFormation 初心者, 川原です.

タイトルの通り, Amazon Elasticsearch Service クラスタVPC 内にサクっと作成する CloudFormation テンプレートを作成しました. 作成したテンプレートは, 以下のリポジトリにアップしています.

github.com

以上

簡単ですが, 共有させて頂きます...で終わるのも寂しいので, CloudFormation 職人の皆さんから見ると, 「なんだ今更」と言われてしまうかもしれませんが, 作成にあたって学んだことなどをメモしていきます.

パラメータのタイプ

CloudFormation テンプレート内にParameters で指定する値のタイプについて.

docs.aws.amazon.com

文字列や数値, 配列等の各種タイプについて, 以下のようなタイプがあります. (上記のドキュメントより引用, 抜粋させていただきました.)

タイプ 内容 parameters セクションで指定するサンプル
String リテラル文字列 "FooBarBaz"
Number 整数又は浮動小数 "12345678"
List カンマで区切られた, 整数又は浮動小数 "123, 456"
CommaDelimitedList カンマで区切られたリテラル文字列 "foo, bar, baz"
AWS 固有のパラメーター型 EC2 キーペア名, VPC ID 等の AWS の値 AWS::EC2::Image::Id
SSM パラメータータイプ Systems Manager パラメーターストア内の既存のパラメーターに対応するパラメーター AWS::SSM::Parameter::Name

これらのパラメータを指定する際に個人的に注意したのが, Number タイプを指定する際に値を指定する場合.

  {
    "ParameterKey": "EsInstanceCount",
    "ParameterValue": 1
  },

数値なので, 1 と書いてしまうと, 以下のようなエラーとなってしまいます.

$ bundle exec rake es:create:demo
./deploy.sh ${AWS_PROFILE} ${STACK_NAME_PREFIX} demo create

Parameter validation failed:
Invalid type for parameter Parameters[3].ParameterValue, value: 1, type: <type 'int'>, valid types: <type 'basestring'>

パラメータで指定する場合には数値であっても "" で括って文字列として指定する必要があります. ドキュメントにも以下のように記述されています.

整数または浮動小数点値。AWS CloudFormation は、この型のパラメーターを数値として検証しますが、テンプレート内の他の場所で使用した場合には (Ref 組み込み関数を使用した場合など) 文字列として扱います。

ということで, 以下のように書く必要があります.

  {
    "ParameterKey": "EsInstanceCount",
    "ParameterValue": "1"
  },

うむ.

「文字列値のリスト」タイプをパラメータとして付与したい場合

テンプレートの VPCOptions.SecurityGroupIdsVPCOptions.SubnetIds に指定する値のタイプは, 「文字列値のリスト」で定義する必要があります.

Parameters:
...
  EsSubnetIds:
    Type: CommaDelimitedList
Resources:
  ElasticsearchDomain:
    Type: AWS::Elasticsearch::Domain
...
      VPCOptions:
        SubnetIds: !Ref EsSubnetIds
...

「文字列値のリスト」を分かりやすく YAML で書くと以下のような感じになると思います.

foo:
  - bar
  - baz

foo の要素である barbaz をパラメータでどのように渡すかという話になりますが, 結論から言うと, CommaDelimitedList タイプを利用して書きました. CommaDelimitedList タイプは先述の通り, パラメータにおいては「カンマで区切られた文字列」を指定します. これを !Ref で展開すると, ["foo", "bar"] という配列として解釈されます.

これを CloudFormation テンプレートに落とし込むと以下のようになると思います.

Parameters:
  FooBarBaz:
    Default: "bar, baz"
    Type: CommaDelimitedList
Resources:
    foo: !Ref FooBarBaz 

また, JSON のテンプレートは以下のようになると思います.

  {
    "ParameterKey": "FooBarBaz",
    "ParameterValue": "bar, baz"
  }

フムフム.

テンプレートのチェックはあくまでもテンプレートのみのチェック

テンプレートを検証する為に, AWS CLI では validate-template というサブコマンドが用意されています.

aws --region=${_AWS_REGION} cloudformation validate-template --template-body file://template.yml

自分のようなうっかり者には, このサブコマンドは非常にありがたいのですが, 検証の対象はあくまでもテンプレートのみが対象となっています. 例えば, 先述の Number パラメータを数値で定義したようなパラメータに異常な値があったとしても, validate-template コマンドは検証成功を返します.

$ aws cloudformation validate-template --template-body file://template.yml; echo $?
{
    "Parameters": [
        {
            "NoEcho": false,
            "ParameterKey": "EsZoneAwareness"
        },
        {
            "NoEcho": false,
            "ParameterKey": "EsSubnetIds"
        },
        {
            "NoEcho": false,
            "ParameterKey": "EsDomainEnvironment"
        },
        {
            "NoEcho": false,
            "ParameterKey": "EsSecurityId"
        },
        {
            "NoEcho": false,
            "ParameterKey": "EsDomainNamePrefix"
        },
        {
            "NoEcho": false,
            "ParameterKey": "EsInstanceType"
        },
        {
            "NoEcho": false,
            "ParameterKey": "EsInstanceCount"
        },
        {
            "NoEcho": false,
            "ParameterKey": "EsVersion"
        },
        {
            "NoEcho": false,
            "ParameterKey": "EsStorageVolumeSize"
        }
    ]
}
0

パラメータを含めた状態で検証して頂けると嬉しいかぎりですが, とりあえずはこんなものだと思って利用したいと思います.

テンプレートの汎用化

異なる環境に対して, 同じような構成でクラスタを構築するにあたって, テンプレートを環境毎に用意して良いのは小学生までという自負はあるものの, どのようにすれば良いのか試行錯誤しました. AWS CLI を利用する場合, スタックの作成や, スタックの更新の際にパラメータをコマンドラインオプションや JSON ファイルで渡すことができるので, 以下のように, 環境毎のパラメータ情報を持った JSON ファイルを用意することにしました.

[
  {
    "ParameterKey": "EsDomainEnvironment",
    "ParameterValue": "demo"
  },
  {
    "ParameterKey": "EsDomainNamePrefix",
    "ParameterValue": "oreno-elasticsearch"
  },
  {
    "ParameterKey": "EsInstanceType",
    "ParameterValue": "t2.small.elasticsearch"
  },
  {
    "ParameterKey": "EsInstanceCount",
    "ParameterValue": "1"
  },
  {
    "ParameterKey": "EsVersion",
    "ParameterValue": "6.2"
  },
  {
    "ParameterKey": "EsZoneAwareness",
    "ParameterValue": "false"
  },
  {
    "ParameterKey": "EsStorageVolumeSize",
    "ParameterValue": "10"
  },
  {
    "ParameterKey": "EsSecurityId",
    "ParameterValue": "sg-xxxxxxxx"
  },
  {
    "ParameterKey": "EsSubnetIds",
    "ParameterValue": "subnet-xxxxxxx1, subnet-xxxxxxx2"
  }
]

上記のようなパラメータを記載した JSON ファイルを例えば demo.json というファイル名で保存して, create-stack や update-stack の際に, 以下のように --parameters オプションの引数に渡して利用します.

aws --profile=${_AWS_PROFILE} --region=${_AWS_REGION} \
  cloudformation create-stack \
    --stack-name ${_ENV}-${_STACK_NAME_PREFIX} \
    --parameters file://parameters/demo.json \
    --template-body file://template.yml \
    --capabilities CAPABILITY_IAM

wait コマンドを利用する

AWS CLI の wait コマンド, とても便利だと思います. 例えば, 以下のように EC2 を起動する run-instances した後に利用すると, EC2 のステータス (instance status) が OK になるまで待機してくれるので, 正常に起動することを確認する為の処理を自前で実装する必要が無いという利点があります.

# EC2 を起動 (※このコードはサンプルです)
aws ec2 run-instances --instance-ids i-xxxxxxxxxxxxx

# 起動させた EC2 のインスタンスステータスが OK になるまで待機 (※このコードはサンプルです)
aws ec2 wait instance-status-ok --instance-ids i-xxxxxxxxxxxxx

cloudformation でも wait コマンドが用意されていて, 以下のようなコマンドをサポートしています.

  • change-set-create-complete
  • stack-create-complete
  • stack-delete-complete
  • stack-exists
  • stack-update-complete

例えば, stack-create-complete コマンドの場合, スタックのステータスが CREATE_COMPLETE になるまで待機してくれるので, スタックが正常に作成されたかをわざわざチェックするような実装が不要となります. ただし, Amazon Elasticsearch Service クラスタを作成する場合, EC2 等を作成するスタックと比べると待機時間が長く, 一瞬, 通信が切れてしまったのかなと思うほどでした.

終わり

以上, メモでした.

素敵な CloudFormation と Elasticsearch 生活をお送りください.

2018 年 05 月 31 日 (木)

ジョギング

日課

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

AWS Summit

話題の EFS リリースよりも, 以下のブログ記事のサービスアップデートが気になった.

dev.classmethod.jp

夕飯

  • パスタを作る

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

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)

定数

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

m = Module.new

m.module_eval do
  EVAL_CONST = 100
end

puts "CONST is defined? #{m.const_defined?(:CONST)}"
puts "_CONST is defined? #{Object.const_defined?(:CONST)}"

CONST is defined? true CONST is defined? true

以下, irb にて確認.

irb(main):001:0> m = Module.new
=> #<Module:0x0055719c3d4f88>
irb(main):002:0> 
irb(main):003:0* m.module_eval do
irb(main):004:1*   CONST = 100
irb(main):005:1> end
=> 100
irb(main):006:0> 
irb(main):007:0* puts "CONST is defined? #{m.const_defined?(:CONST)}"
EVAL_CONST is defined? true
=> nil
irb(main):008:0> puts "CONST is defined? #{Object.const_defined?(:CONST)}"
EVAL_CONST is defined? true
=> nil

以下, 解説より抜粋.

  • 設問において, module_eval のブロックで定義した定数はこの問題ではトップレベルで定義したことになる
  • 定数 EVAL_CONST はトップレベルで定義していることになる為, Object クラスの定数あることであることを確認出来る
  • Module クラスのインスタンスには直接, 定数は定義されていないが継承関係を探索して参照することが可能
  • const_defined? メソッドは第二引数に継承関係を探索するか指定が可能である為, 継承関係を探索するかによって結果は異なる
irb(main):001:0> m = Module.new
=> #<Module:0x0055f166098fa0>
irb(main):002:0> m.module_eval do
irb(main):003:1*   CONST = 'foo'
irb(main):004:1> end
=> "foo"
irb(main):005:0> puts Object.const_defined?(:CONST)
true
=> nil
irb(main):006:0> puts m.const_defined?(:CONST)
true
=> nil
irb(main):007:0> puts m.const_defined?(:CONST, false)
false
=> nil

ちなみに, 設問において, module_eval で文字列でメソッドを定義した場合には以下のような挙動になる.

irb(main):001:0> m = Module.new
=> #<Module:0x0056220dff5138>
irb(main):002:0> m.module_eval %Q{
irb(main):003:0"   CONST = 'foo'
irb(main):004:0" } 
=> "foo"
irb(main):005:0> puts Object.const_defined?(:CONST)
false
=> nil
irb(main):006:0> puts m.const_defined?(:CONST)
true
=> nil
irb(main):007:0> puts m.const_defined?(:CONST, false)
true
=> nil

module_evalcalss_eval において, メソッドを文字列で渡すかブロックで渡すかによって定数とクラス変数のスコープは異なるので注意が必要となる. 上記の例だと, 定数は module m 内に定義されていることになる.

ところで, Module.new って出来るんだ...ということで, 以下, ドキュメントより抜粋.

  • Module.new
    • 名前の付いていないモジュールを新しく生成して返す
    • ブロックが与えられると生成したモジュールをブロックに渡し, モジュールのコンテキストでブロックを実行する
mod = Module.new
mod.module_eval {|m| ... }
mod

また, このメソッドで生成されたモジュールは, 最初に名前が必要になった時に名前が決定する

mod = Module.new
mod.module_eval {|m| ... }
mod

m = Module.new
p m               # => #<Module 0lx40198a54>
p m.name          # => nil   # まだ名前は未定
Foo = m
# m.name          # ここで m.name を呼べば m の名前は "Foo" に確定する
Bar = m
m.name            # "Foo" か "Bar" のどちらかに決まる

フムフム.

2018 年 05 月 30 日 (水)

ジョギング

日課

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

蒸し暑くなってきた...

  • 走った後とか辛い

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

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 クラス, Enumerable クラス, String クラス, Object クラス

以下のプログラムを実行すると, ruby が一文字ずつ表示される. each_char を置き換えても同じ結果になるメソッドを選択肢から選ぶ.

enum = 'ruby'.each_char
p enum.next
p enum.next
p enum.next
p enum.next

とりあえず, 上記を irb で試す.

irb(main):001:0> enum = 'ruby'.each_char
=> #<Enumerator: "ruby":each_char>
irb(main):002:0> p enum.next
"r"
=> "r"
irb(main):003:0> p enum.next
"u"
=> "u"
irb(main):004:0> p enum.next
"b"
=> "b"
irb(main):005:0> p enum.next
"y"
=> "y"
irb(main):001:0> enum = 'ruby'.enum_for(:each_char)
=> #<Enumerator: "ruby":each_char>
irb(main):002:0> p enum.next
"r"
=> "r"
irb(main):003:0> p enum.next
"u"
=> "u"
irb(main):004:0> p enum.next
"b"
=> "b"
irb(main):005:0> p enum.next
"y"
=> "y"

ちなみに, String#nextInteger#next は, それぞれ succ メソッドをエイリアスとして利用可能であるが, Enumerator#next には succ は存在しないので注意する.

ということで, 解答.

to_enum(:each_char) enum_for(:each_char)

以下, irb で確認.

irb(main):006:0> enum = 'ruby'.to_enum(:each_char)
=> #<Enumerator: "ruby":each_char>
irb(main):007:0> p enum.next
"r"
=> "r"
irb(main):008:0> p enum.next
"u"
=> "u"
irb(main):009:0> p enum.next
"b"
=> "b"
irb(main):010:0> p enum.next
"y"
=> "y"

以下, 解説より抜粋.

  • each_char にブロックを渡さない場合は, Enumerator オブジェクトが作成される
  • Enumerator オブジェクトを作成するには, enum_for または, to_enum を利用する
  • 引数にはレシーバーに送信したいメッセージ (メソッドの呼び出し) を定義する
  • 引数を指定しない場合は, each がデフォルト値になる

次の例では、eachを使って1つずつアクセスして要素を表示しています。

irb(main):001:0> enum = [1, 2, 3, 4, 5].to_enum
=> #<Enumerator: [1, 2, 3, 4, 5]:each>
irb(main):002:0> p enum.next
1
=> 1
irb(main):003:0> p enum.next
2
=> 2
irb(main):004:0> p enum.next
3
=> 3
irb(main):005:0> p enum.next
4
=> 4
irb(main):006:0> p enum.next
5
=> 5
irb(main):001:0> enum = [1, 2, 3, 4, 5].enum_for(:reverse_each)
=> #<Enumerator: [1, 2, 3, 4, 5]:reverse_each>
irb(main):002:0> p enum.next
5
=> 5
irb(main):003:0> p enum.next
4
=> 4
irb(main):004:0> p enum.next
3
=> 3
irb(main):005:0> p enum.next
2
=> 2
irb(main):006:0> p enum.next
1
=> 1

例外

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

begin
  print "foo" + :bar
rescue TypeError
  print "TypeError."
rescue
  print "Error."
else
  print "Else."
end

TypeError. が表示される

以下, irb による確認.

irb(main):001:0> begin
irb(main):002:1*   print "foo" + :bar
irb(main):003:1> rescue TypeError
irb(main):004:1>   print "TypeError."
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> end
TypeError.=> nil

以下, 解説より抜粋.

  • String#+String クラスのオブジェクトを期待する
  • 引数に Symbol クラスを渡している為, 例外 TypeError が発生する
  • else ブロックは例外が発生しない場合に実行される

ちなみに, 以下のように書くとどうなるか.

begin
  print "foo" + :bar
rescue TypeError
  print "TypeError."
rescue
  print "Error."
else
  print "Else."
ensure
  print "Ensure."
end

Type Error. と Ensure. が出力される

以下, irb による実行例.

irb(main):001:0> begin
irb(main):002:1*   print "foo" + :bar
irb(main):003:1> rescue TypeError
irb(main):004:1>   print "TypeError."
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
TypeError.Ensure.=> nil

ensure ブロックは例外の発生関わらず, 必ず実行されるので, このコードにおいて例外が発生しない場合には, else 及び ensure ブロックまで含まれる出力となる.

irb(main):001:0> begin
irb(main):002:1*   print "foo" + 'bar'
irb(main):003:1> rescue TypeError
irb(main):004:1>   print "TypeError."
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
foobarElse.Ensure.=> nil

フムフム.

2018 年 05 月 29 日 (火)

天気

  • 曇り後晴れ

ジョギング

日課

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

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

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_eval

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

module Mod
  B = 100

  def method
    1000
  end
end

Mod.module_eval do
  def self.method
    p B
  end
end

B = 10000

Mod.method

10000 が出力される

以下, irb にて確認.

irb(main):001:0> module Mod
irb(main):002:1>   B = 100
irb(main):003:1> 
irb(main):004:1*   def method
irb(main):005:2>     1000
irb(main):006:2>   end
irb(main):007:1> end
=> :method
irb(main):008:0> 
irb(main):009:0* Mod.module_eval do
irb(main):010:1*   def self.method
irb(main):011:2>     p B
irb(main):012:2>   end
irb(main):013:1> end
=> :method
irb(main):014:0> 
irb(main):015:0* B = 10000
=> 10000
irb(main):016:0> 
irb(main):017:0* Mod.method
10000
=> 10000

以下, 解説より抜粋.

  • module_eval にブロックを渡した際のネスト状態は [] となり, メソッドはトップレベルに定義されることになる
  • トップレベルで定数を定義した場合は Object クラスの定数になる
  • 設問では, self.method はブロックで渡されている為, トップレベルの定数を参照することになり Mod.method10000 を返す
irb(main):001:0> module A; end
=> nil
irb(main):002:0> A.module_eval do
irb(main):003:1*   p Module.nesting
irb(main):004:1> end
[]
=> []
irb(main):005:0> A.module_eval %Q{
irb(main):006:0"   p Module.nesting
irb(main):007:0" }
[A]
=> [A]
irb(main):008:0> A.module_eval do
irb(main):009:1*   p Module.nesting
irb(main):010:1> end
irb(main):011:0> B = "Hello, world"
=> "Hello, world"
irb(main):012:0> p Object.const_get(:B)
"Hello, world"
=> "Hello, world"

なるほどー, ブロックで渡した時と文字列で渡した時の違いってこれかなー.

ちなみに, class_eval でも同様の事が言える.

irb(main):001:0> class C; end
=> nil
irb(main):002:0> C.class_eval do
irb(main):003:1*   p Module.nesting
irb(main):004:1> end
[]
=> []
irb(main):009:0> C.class_eval %Q{
irb(main):010:0"   p Module.nesting
irb(main):011:0" }
[C]
=> [C]

ということで, 以下のドキュメントの意味がシュッと頭に入ってくるような気がする.

  • 文字列で定義した場合には, クラス又はモジュール定義式内の定数, クラス変数を参照する
  • ブロックで定義した場合には, クラス又はモジュール定義外の定数, クラス変数を参照する

一応, 以下, ドキュメントより抜粋.

  • module_eval に文字列を渡した場合の定数及びクラス変数のスコープはモジュール定義式内と同じスコープとなる
  • 対して, module_eval にブロックを渡した場合は, 定数とクラス変数のスコープはブロックの外側のスコープとなる
  • 尚, ローカル変数に関しては, class_eval 及び module_eval の外側と共有する
irb(main):001:0> class C
irb(main):002:1> end
=> nil
irb(main):003:0> a = 1
=> 1
irb(main):004:0> C.class_eval %Q{
irb(main):005:0"   def m
irb(main):006:0"     return :m, #{a}
irb(main):007:0"   end
irb(main):008:0" }
=> :m
irb(main):009:0> 
irb(main):010:0* p C.new.m
[:m, 1]
=> [:m, 1]

フムフム...

リトライ処理が適切に行われるかをテストする方法を考えた Python 編

tl;dr

  • Python の retry モジュール超便利
  • で, ちゃんと意図した通りにリトライされているか心配になった
  • リトライされているかユニットテストのレベルでテストしたい

さあ, どーしよう.

尚, 本記事で利用している環境は以下の通り.

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

$ python --version
Python 3.6.4

retry モジュール

retry モジュール

retry モジュールは, まさにその名前の通り, 指定した関数のリトライ処理を簡単に実現するモジュール.

pypi.org

retry モジュールのシンプルな使い方

以下のようにデコレータを付与することで, 指定した回数, 失敗時の遅延時間等で対象となる関数において再処理を実施してくれる.

import logging
from retry import retry

@retry(tries=3, delay=2, backoff=2)
def zero_division():
    try:
        return 10 / 0
    except ZeroDivisionError as ex:
        logging.error(ex)
        raise

上記のコードの場合, zero_division() を最大 3 回試行し, エラーが発生した場合には 1 回目の試行は 2 秒後, backoff が定義されているので, 2 回目の試行は 4 秒後に行う. 実際にコードを動かしてみると, 以下のように, 初回を含めると 3 回試行されていることが判る.

>>> import logging
>>> from retry import retry
>>>
>>> @retry(tries=3, delay=2, backoff=2)
... def zero_division():
...   try:
...       return 10 / 0
...   except ZeroDivisionError as ex:
...       logging.error(ex)
...       raise
...
>>> zero_division()
ERROR:root:division by zero
WARNING:retry.api:division by zero, retrying in 2 seconds...
ERROR:root:division by zero
WARNING:retry.api:division by zero, retrying in 4 seconds...
ERROR:root:division by zero

わざわざ, 自分でリトライ処理を書く必要が無いのが嬉しい. もし, retry モジュールを使わずにリトライ処理を実装するとしたら, 以下のようになるかもしれない.

import logging
from time import sleep
TRY = 3

def zero_division_no_retry_module():
    for r in range(1, TRY):
        try:
            return 10 / 0
        except ZeroDivisionError as ex:
            logging.warn('%s, %d 回目, %d 秒待機します.' % (ex, r, (r * 2)))
            sleep (r * 2)
    logging.error('%d 回目, エラーが発生しました. 処理を終了します.' % TRY)
    return False

試しに実行してみる.

>>> import logging
>>> from time import sleep
>>> TRY = 3
>>>
>>> def zero_division_no_retry_module():
...     for r in range(1, TRY):
...         try:
...             return 10 / 0
...         except ZeroDivisionError as ex:
...             logging.warn('%s, %d 回目, %d 秒待機します.' % (ex, r, (r * 2)))
...             sleep (r * 2)
...     logging.error('%d 回目, エラーが発生しました. 処理を終了します.' % TRY)
...     return False
...
>>> zero_division_no_retry_module()
WARNING:root:division by zero, 1 回目, 2 秒待機します.
WARNING:root:division by zero, 2 回目, 4 秒待機します.
ERROR:root:3 回目, エラーが発生しました. 処理を終了します.
False

んー, retry を使うよりは複雑な感じ.

ということで, そのリトライ, ちゃんとリトライしてますか?

まずは...

先程からちょいちょい出てくる, 0 除算のコードを再掲.

# file name: retry_sample.py
import logging

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

def zero_division():
    try:
        return 10 / 0
    except ZeroDivisionError as ex:
        logger.error(ex)
        raise

以下のようなテストを書いた.

# file name: test_retry_sample.py
import unittest
import retry_sample

class TestRetrySample(unittest.TestCase):
    def test_retry_sample_exception(self):
        with self.assertRaises(ZeroDivisionError):
            retry_sample.zero_division()

例外が起きることを期待する.

$ python -m unittest tests.test_retry_sample -v
test_retry_sample (tests.test_retry_sample.TestRetrySample) ... division by zero
ok

----------------------------------------------------------------------
Ran 1 test in 0.001s

OK

例外が起きることはテストで確認出来た. しかも, logging が設定されているので, ログも出力されている.

リトライ

先程の 0 除算コードを以下のように修正し, retry 処理を追加した.

import logging
from retry import retry

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

@retry(tries=3, delay=2, backoff=2)
def zero_division():
    try:
        return 10 / 0
    except ZeroDivisionError as ex:
        logger.error(ex)
        raise

retry モジュールにより, 初回を含めて合計 3 回, この関数が実行されることを意図している.

これを, 先程書いたテストで確認してみる.

$ python -m unittest tests.test_retry_sample -v
test_retry_sample (tests.test_retry_sample.TestRetrySample) ... division by zero
division by zero
division by zero
ok

----------------------------------------------------------------------
Ran 1 test in 6.012s

OK

例外メッセージが 3 回出力されているし, retry モジュールの delay = 2backoff = 2 が効いているので, 初回から 2 秒後, 4 秒後にリトライされてテスト自体の処理時間は 6 秒 (くらい) 掛かっていることが確認できた. 間違いなくリトライしているんだと思う.

で, リトライされていることをテストする

先程の通り, 例外メッセージが 3 回出力されていることから, retry モジュールによって再処理が行われていることが確認出来たが...やはり, ちゃんと処理が 3 回行われることをテストで確認したい.

ということで, testfixtures モジュールを利用して, 例外メッセージの出力を捕捉することで, retry による再処理が実施されていることをテストする.

pypi.org

testfixtures は,

TestFixtures is a collection of helpers and mock objects that are useful when writing unit tests or doc tests.

上記の通り, ユニットテストなどで利用する mock オブジェクトをお手軽に作成できるモジュール. 今回は, 標準出力や標準エラーへの出力をキャプチャするクラスを利用して, 以下のようにテストを書いた.

import unittest
from testfixtures import OutputCapture

import retry_sample

class TestRetrySample(unittest.TestCase):
    def test_retry_sample_exception(self):
        with self.assertRaises(ZeroDivisionError):
            retry_sample.zero_division()

    def test_retry_sample_exception_retry(self):
        with OutputCapture() as output:
            with self.assertRaises(ZeroDivisionError):
                retry_sample.zero_division()
            output.compare('\n'.join([
                'division by zero',
                'division by zero',
                'division by zero',
                ''
            ]))

division by zero という例外メッセージ (エラーログ) が 3 回出力されることを期待するテスト. retry モジュールによる試行回数は 3 回なので, ログ自体も 3 回出力されるはず.

テストを実行すると, 以下のように出力される.

$ python -m unittest tests.test_retry_sample -v
test_retry_sample_exception (tests.test_retry_sample.TestRetrySample) ... division by zero
division by zero
division by zero
ok
test_retry_sample_exception_retry (tests.test_retry_sample.TestRetrySample) ... ok

----------------------------------------------------------------------
Ran 2 tests in 12.013s

OK

なんか, ログ出力の表示が若干ウザいけど, 意図した結果となった.

また, LogCapture クラスを利用することで, 上記のようにテスト結果出力にログが交じることもなくテストをすることを出来たので, LogCapture クラスを利用したほうが良いと思う.

import unittest
from testfixtures import LogCapture
import logging

import retry_sample

class TestRetrySample(unittest.TestCase):
    def test_retry_sample_exception_retry_log_output(self):
        with LogCapture(level=logging.ERROR) as output:
            with self.assertRaises(ZeroDivisionError):
                retry_sample.zero_division()
            output.check(
                ('root', 'ERROR', 'division by zero'),
                ('root', 'ERROR', 'division by zero'),
                ('root', 'ERROR', 'division by zero'))

初回の試行と, retry モジュールによる再試行の合計 3 回分のログが出力されることを期待している.

テストを走らせると, 以下のように出力される.

$ python -m unittest tests.test_retry_sample.TestRetrySample.test_retry_sample_exception_retry_log_output -v
test_retry_sample_exception_retry_log_output (tests.test_retry_sample.TestRetrySample) ... ok

----------------------------------------------------------------------
Ran 1 test in 6.010s

OK

テストが通ることを確認した. つまり, retry モジュールによるリトライが実施されることがテストコードにより確認された. また, 実行時間を見る限り, retry モジュールによる試行が行われていることが判る. 念の為, 以下のように 1 行のみログが出力されることを期待するテストコードを変更して実行してみると...

$ diff -u test_retry_sample.py.bk test_retry_sample.py
--- test_retry_sample.py.bk     2018-05-27 23:35:47.000000000 +0900
+++ test_retry_sample.py        2018-05-27 23:35:56.000000000 +0900
@@ -27,6 +27,4 @@
             with self.assertRaises(ZeroDivisionError):
                 retry_sample.zero_division()
             output.check(
-                ('root', 'ERROR', 'division by zero'),
-                ('root', 'ERROR', 'division by zero'),
                 ('root', 'ERROR', 'division by zero'))

実際のログ出力は 3 行となる為, 以下のように, テストの結果は FAIL となる.

$ python -m unittest tests.test_retry_sample.TestRetrySample.test_retry_sample_exception_retry_log_output -v
test_retry_sample_exception_retry_log_output (tests.test_retry_sample.TestRetrySample) ... FAIL

======================================================================
FAIL: test_retry_sample_exception_retry_log_output (tests.test_retry_sample.TestRetrySample)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/path/to/python-unittest/tests/test_retry_sample.py", line 30, in test_retry_sample_exception_retry_log_output
    ('root', 'ERROR', 'division by zero'))
  File "/path/to/.pyenv/versions/3.6.4/lib/python3.6/site-packages/testfixtures/logcapture.py", line 180, in check
    recursive=self.recursive_check
  File "/path/to/.pyenv/versions/3.6.4/lib/python3.6/site-packages/testfixtures/comparison.py", line 563, in compare
    raise AssertionError(message)
AssertionError: sequence not as expected:

same:
(('root', 'ERROR', 'division by zero'),)

expected:
()

actual:
(('root', 'ERROR', 'division by zero'), ('root', 'ERROR', 'division by zero'))

----------------------------------------------------------------------
Ran 1 test in 6.041s

FAILED (failures=1)

意図した通りにリトライは行われているようで安心. ただ, より厳密 (delaybackoff から算出される時間等) に対してテストを行いたい場合には, retry モジュール自体のテストコードを見てみると, 以下のようにテストを行っているので, これを真似てテストを書くことが出来そう.

...
def test_retry(monkeypatch):
    mock_sleep_time = [0]

    def mock_sleep(seconds):
        mock_sleep_time[0] += seconds

    monkeypatch.setattr(time, 'sleep', mock_sleep)

    hit = [0]

    tries = 5
    delay = 1
    backoff = 2

    @retry(tries=tries, delay=delay, backoff=backoff)
    def f():
        hit[0] += 1
        1 / 0

    with pytest.raises(ZeroDivisionError):
        f()
    assert hit[0] == tries
    assert mock_sleep_time[0] == sum(
        delay * backoff ** i for i in range(tries - 1))
...

以上

retry モジュールを利用した際のリトライ処理が意図した回数行われているかをテストする方法を考えてみた. 今回は, エラー表示やログメッセージを捕捉して, その件数をカウントすることでテストするという手法を実装してみたが, もっと良い方法があるかもしれない. ということで, 改めて思いついたら続編を書いてみたいと思う.

2018 年 05 月 28 日 (月)

天気

  • 小雨からの曇
  • 梅雨入り

ジョギング

日課

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

夕飯

  • 手羽中の磯辺揚げ風
  • ピーマンと新玉ねぎの天ぷらっぽいやつ

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

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)

mix-in

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

module Mod1
end

module Mod1::Mod2
  p Module.nesting
end

Mod1::Mod2

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

irb(main):001:0> module Mod1
irb(main):002:1> end
=> nil
irb(main):003:0> 
irb(main):004:0* module Mod1::Mod2
irb(main):005:1>   p Module.nesting
irb(main):006:1> end
[Mod1::Mod2]
=> [Mod1::Mod2]

以下, 解説より抜粋.

  • Module.nesting はネストの状態を表示する
  • モジュールがネストされている場合には, ネストの状態をすべて表示する
  • Mod1::Mod2 のようにプログラムを書くとモジュール Mod1 の内側にモジュール Mod2 があることを表現することが出来る
  • インデントして個別に書いた場合と比較して, プレフィックス (::) がある場合は内側にあるモジュールしかネストの状態は表示されない
irb(main):001:0> module Mod1
irb(main):002:1>   p Module.nesting
irb(main):003:1> end
[Mod1]
=> [Mod1]
irb(main):004:0> module Mod1::Mod2
irb(main):005:1>   p Module.nesting
irb(main):006:1> end
[Mod1::Mod2]
=> [Mod1::Mod2]
irb(main):007:0> module Mod1
irb(main):008:1>   module Mod2
irb(main):009:2>     p Module.nesting
irb(main):010:2>   end
irb(main):011:1> end
[Mod1::Mod2, Mod1]
=> [Mod1::Mod2, Mod1]
irb(main):012:0> 
irb(main):012:0> module Foo
irb(main):013:1>   module Bar
irb(main):014:2>     module Baz
irb(main):015:3>       p Module.nesting
irb(main):016:3>     end
irb(main):017:2>   end
irb(main):018:1> end
[Foo::Bar::Baz, Foo::Bar, Foo]
=> [Foo::Bar::Baz, Foo::Bar, Foo
irb(main):019:0> class Cls1
irb(main):020:1>   class Cls2
irb(main):021:2>     class Cls3
irb(main):022:3>       Module.nesting
irb(main):023:3>     end
irb(main):024:2>   end 
irb(main):025:1> end
=> [Cls1::Cls2::Cls3, Cls1::Cls2, Cls1

尚, Module.nesting はモジュールだけでなく, クラスについても同様にメソッドを呼び出した時点のネスト情報を配列に入れて返す.

定数

以下のプログラムを実行するとどうなるか.

class Ca
  CONST = "A"
end

class Cb
  CONST = "B"
end

class Cc
  CONST = "C"
end

class Cd
  CONST = "D"
end

module M1
  class C0 < Ca
    class C1 < Cc
      class C2 < Cd
        p CONST

        class C2 < Cb
        end
      end
    end
  end
end

D が出力される

以下, irb で確認.

... 略
irb(main):017:0* module M1
irb(main):018:1>   class C0 < Ca
irb(main):019:2>     class C1 < Cc
irb(main):020:3>       class C2 < Cd
irb(main):021:4>         p CONST
irb(main):022:4> 
irb(main):023:4*         class C2 < Cb
irb(main):024:5>         end
irb(main):025:4>       end
irb(main):026:3>     end
irb(main):027:2>   end
irb(main):028:1> end
"D"
=> nil

以下, 解説より抜粋.

  • Rubyは定数参照はレキシカル (≒静的に) に決定されるが, 設問ではレキシカルスコープに定数は定義されていない
  • レキシカルスコープに定数がない場合は, スーパークラスの探索を行う
  • 設問では, クラス C2 のスコープで定数を参照する
  • クラス C2スーパークラスはクラス Cd となるので, "D" が正解となる

レキシカル又は,レキシカルスコープについては, フワッとしか認識しかないので機会があれば掘り下げたい. 今のところは, スコープ外の同名変数に影響を与えないというくらいのフワッとした認識しかない...

フムフム.

2018 年 05 月 27 日(日)

ジョギング

日課

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

放送大学レポート提出

このレポートを出さないと単位認定試験を受けることが出来ないということで頑張った. そして, LaTeX で作成したった.

inokara.hateblo.jp

LaTeX 楽しかった.

担々麺

ランチは近所の担々麺. 自分が食べた担々麺の中でかなり上位に食い込んでくる美味しさ.

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

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 Object
  CONST = "10"
  def const_succ
    CONST.succ!
  end
end

class Cls1
  const_succ
  class << self
    const_succ
  end
end

class Cls2
  const_succ
  def initialize
    const_succ
  end
end

Cls1.new
Cls2.new

p Object::CONST

15 が出力される

以下, irb にて動作確認.

irb(main):023:0> p Object::CONST
"15"
=> "15"

以下, 解説より抜粋.

  • Object クラスにメソッドを定義すると, 特異クラスでもそのメソッドを利用することが出来る
  • 設問のプログラムを順に実行すると, 答えは 15 になる
  • String#succ メソッドは self の「次の」文字列を返す
  • 定数 CONST に対して, 破壊的メソッド succ! を呼び出している為, CONST の値はカウントアップされる

以下, 諸々確認.

# Object クラスにメソッドを定義
irb(main):001:0> class Object
irb(main):002:1>   CONST = "10"
irb(main):003:1>   def const_succ
irb(main):004:2>     CONST.succ!
irb(main):005:2>   end
irb(main):006:1> end
=> :const_succ
irb(main):007:0> const_succ
=> "11"
irb(main):008:0> const_succ
=> "12"
# 特異メソッド (クラスメソッド) で const_succ が利用可能
irb(main):009:0> class Cls1
irb(main):010:1>   class << self
irb(main):011:2>     const_succ
irb(main):012:2>   end
irb(main):013:1> end
=> "13"
irb(main):014:0> Cls1.const_succ
=> "14"

定数に対する succ メソッドの呼び出し例.

irb(main):001:0> CONST = '1'
=> "1"
irb(main):002:0> CONST = '2'
(irb):2: warning: already initialized constant CONST
(irb):1: warning: previous definition of CONST was here
=> "2"
irb(main):003:0> CONST.succ
=> "3"
irb(main):004:0> CONST.succ
=> "3"
irb(main):005:0> CONST.succ
=> "3"
irb(main):006:0> CONST.succ!
=> "3"
irb(main):007:0> CONST.succ!
=> "4"

通常, 定数への再代入はワーニングが出てしまう. succ メソッドを呼び出すと, 最初に呼び出された際にはカウントアップ (3 になる) されるが, その後, 何度も succ メソッドを呼び出してもカウントアップされない. 破壊的メソッド succ! を呼び出すとカウントアップされる.

正規表現

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

p "Nut rage incident"[/[J-P]\w+[^ ]/]

Nut と表示される

以下, irb による確認.

irb(main):001:0> p "Nut rage incident"[/[J-P]\w+[^ ]/]
"Nut"
=> "Nut"

以下, 解説より抜粋.

  • 正規表現[J-P] は囲まれた文字の 1 つ 1 つにマッチする
  • J-P は大文字の J から P の 1 文字にマッチする
  • \w は大文字, 小文字, 数字とアンダーバー(_) にマッチする
  • + は直前の文字が, 1回以上の繰り返しにマッチする
  • [^ ] は空白以外にマッチする

ということで, 設問では Nut という文字列がマッチする.

以下, よく使われる文字クラスには, 以下のような省略記法がある.

省略記法 文字クラス
\w 単語構成文字 [a-zA-Z0-9_]
\W 非単語構成文字 [^a-zA-Z0-9_]
\s 空白文字 [ \t\r\n\f\v]
\S 非空白文字 [^ \t\r\n\f\v]
\d 10進数字 [0-9]
\D 非10進数字 [^0-9]
\h 16進数字 [0-9a-fA-F]
\H 非16進数字 [^0-9a-fA-F]

フムフム.

放送大学のレポートを LaTeX で作成して提出したのでメモ ~ Cloud LaTeX でしたためた 20 年越しの思い ~

[:toc]

tl;dr

f:id:inokara:20180527215529p:plain

一応, 放送大学の学生をやっているので, この時期になると通信指導と呼ばれるレポートを提出ことが求められている. このレポートを提出しなければ, そもそも単位認定試験を受ける権利が無くなってしまうので, この土日でなんとかレポートを書いた. そして, 提出はワープロ (!!) でも可能ということだったので, Markdown で書いて, Cloud LaTex を使って仕上げてみた.

LaTeX について

LaTeX とは

今更ながらに LaTeX とは.

www.latex-project.org

LaTeX is a high-quality typesetting system; it includes features designed for the production of technical and scientific documentation. LaTeX is the de facto standard for the communication and publication of scientific documents. LaTeX is available as free software.

LaTeX は高品質な組版システムで技術や科学技術文書を作成する為に必要な機能が揃っている. Wikipedia によると, もともとは TeX という数式の処理に優れていた組版ソフトウェアがあり, TeX を使ってもっと簡単に論文やレポートを作成したいという要望に応えて開発されたとある. また, その歴史は古くて 1985 年にリリースされとあるので, リリースされて 30 年以上経っていることになる. すごいなあ.

なぜ LaTeX を使ったのか

今回, レポートは作文用紙が同梱された封書にて提出するこになっている. そのレポートは手書きが前提となっていたが, ワープロでの提出も可能ということだったので, 20 年くらい前にセットアップで挫折した LaTeX を改めて使ってみようという思いに至った. たしか, Vine Linux (又は Slackware) をインストールした東芝のラップトップパソコンだったと思う. 結局, その時は手書きのレポートを提出したんだけど...

で, 時は流れて, 技術は進歩して LaTex はブラウザでも利用することが出来るようになっていて, 今回は Cloud LaTeX というサービスを利用することにした.

cloudlatex.io

改めて, LaTeX に触れる機会に巡りあって嬉しかったし, この技術の進歩に本当に感謝している. また, TeX コマンドに触れるのはほぼ初めてだったので, コマンドと文書を書いて, プレビューで表示して確認という流れが, 初めて HTML に触れて, ブラウザでプレビューしながら試行錯誤した時の感動が蘇ってきた.

Cloud LaTeX で認めた 20 年越しの思い

Cloud LaTeX について

cloudlatex.io

Cloud LaTeX については, 上記サイトの特徴に書かれているが, 個人的には以下の点について気に入った.

  • LaTeX 環境のセットアップ不要で, PDF の生成, ダウンロードまで行える!
  • シンプルなユーザーインターフェース (左ペインはファイルツリー, 真ん中がエディタ, 右が PDF のプレビュー)
  • TeX のコマンドを自動的に補完してくれる

始め方はとても簡単で, 幾つかの登録を行うだけですぐに文書作成を始めることが出来た.

Hello LaTeX

新規にプロジェクトを作成すると, 下図のようにサンプルの TeX ファイルが作成されているので, このサンプルを改変するだけで, それらしい文書を作ることが出来る. 実は, 今回のレポートはこのサンプルを少し拡張して文書を作成した.

ということで, 以下のような文書を作成して Hello world ならぬ Hello LaTex を作成してみた.

\documentclass[a4paper]{jsarticle}
\usepackage[top=20truemm,bottom=20truemm,left=25truemm,right=25truemm]{geometry}
\usepackage[dvipdfmx]{graphicx}
\title{初めての \LaTeX}

\author{かっぱ}
\date{\today}
\begin{document}
\maketitle
\section{こんにちわ LaTeX}

こんにちわ,\LaTeX さん. 私について, 以下の図\ref{fig:watashi}に示します.

\begin{figure}[htbp]
  \begin{center}
    \includegraphics[scale=0.5]{figures/watashi.png}
    \caption{私です}
    \label{fig:watashi}
  \end{center}
\end{figure}

ということで, 楽しく \LaTeX を使っていきましょう.
 
\end{document}

下図のようにコンパイルボタンをクリックすると, 右ペインに PDF のプレビューが表示される.

f:id:inokara:20180527214433p:plain

いい感じ.

今回のレポートで使ったコマンド

先述の通り, 今回提出したレポートについては, Cloud LaTex が用意してくれたサンプルをほぼそのまま利用させて頂いたが, せっかくなので, TeX コマンド等をまとめてみる.

LaTeX の基本構造

LaTeX 文章の基本構造は以下の通り.

\documentclass[a4paper]{jsarticle}
プリアンプル
\begin
本文
\end

プリアンプルとは, ページのレイアウトやパッケージの読み込みについて指定するセクションとなる. 今回のレポートでは, 用紙のマージンや DVI 形式のファイルを PDF 形式に変換するパッケージを指定している.

\usepackage[top=20truemm,bottom=20truemm,left=30truemm,right=30truemm]{geometry}
\usepackage[dvipdfmx]{graphicx}

\documentclass

\documentclass[a4paper]{jsarticle}

\documentclass コマンドでは文書クラスを指定する. 文書クラスは, 「論文」, 「本」, 「報告書」 等の文書の種類を指定する. 上記の例では, 用紙サイズ A4 で和文の文書クラスを指定している. LaTeX 文書では基本的にはこのコマンドから始める.

\autor

\author{かっぱ}

\author は読んで字の如く, 著者名を定義する.

\begin ~ \end

\begin{document}
本文
\end{document}

\begin から \end の中に文書を作成することなる. \begin から \end は「環境」と呼ばれる区切りを表現しており, 上記の場合 document という環境を示している. 尚, \begin から \end はネストすることが出来る.

\maketitle

\maketitle はタイトルを出力する.

\title{初めての \LaTeX}

\author{かっぱ}
\date{\today}
\begin{document}
\maketitle

\title, \author 及び, \date で定義したタイトルを出力する. これらは \maketitle より前に定義しておく必要がある.

下図のように生成される.

f:id:inokara:20180527215000p:plain

\section, \subsection, \subsubsection

節, 小節等の見出しを出力する.

\section{ヘックション}
\subsection{サブヘックション}
\subsubsection{サブサブヘックション}

下図のように生成される.

f:id:inokara:20180527214706p:plain

\includegraphics

LaTeX で図を出力する為には, graphicx 環境の figure 環境と合わせて, includegraphics パッケージを利用する.

\begin{figure}[htbp]
  \begin{center}
    \includegraphics[scale=0.5]{figures/watashi.png}
    \caption{私です}
    \label{fig:watashi}
  \end{center}
\end{figure}

[htbp] は画像が表示される位置を指定している. 詳細については, こちらの記事がとても参考になった. また, \includegraphics[scale=0.5]{figures/watashi.png} で画像ファイルのパスや大きな (scale=0.5) を指定している.

下図のように生成される.

f:id:inokara:20180527214839p:plain

プリアンプル

プリアンプルとは, \begin{document} より前の行を呼ぶ. HTML で言うところの <body> タグの前の <header> から </header> タグに囲まれた部分という理解. この部分で文書内で利用するパッケージの読み込みや文書のマージン等を定義している.

% 以下, プリアンプルです
\usepackage[top=20truemm,bottom=20truemm,left=25truemm,right=25truemm]{geometry}
\usepackage[dvipdfmx]{graphicx}
\title{初めての \LaTeX}

\author{かっぱ}
\date{\today}
% ここまでがプリアンプルです
\begin{document

ちなみに, コメントは % コメント と記載する.

ということで

LaTeX で文書を作成する面白さが 20 年の時を経てジワジワ解ってきた. 今後も, 機会があれば LaTeX を使っていきたいと考えている.

参考サイト

本記事の記載にあたって, 以下のサイトの記事を参考にさせて頂いた. とても解りやすく LaTeX の情報が整理されていてとても勉強になった.

www.latex-cmd.com

medemanabu.net

管理者の方にはこの場を借りてお礼を申し上げる. 本当に有難うございます.

2018 年 05 月 26 日(土)

ジョギング

  • 自宅→香椎宮香椎浜
  • 何気に足に負担がきているなーという感じ

日課

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

奥さんと天神デート

天神で所用を済ませた後, ブラブラと博多まで夫婦で歩く. その途中で櫛田神社に立ち寄ってお参り.

hakatanomiryoku.com

初の櫛田神社だった.

博多駅の地下街にある焼肉屋さんで夕食. お腹一杯食べた.

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

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 クラス

以下のプログラムの __(1)__ に適切なコードを入力して実行すると, [98, 97, 110, 97, 110, 97] と表示される. 期待した結果を得られるように正しい選択肢を選ぶ.

enum_char = Enumerator.new do |yielder|
  "banana".each_char do |chr|
    __(1)__
  end
end

array = enum_char.map do |chr|
  chr.ord
end

p array

yielder << chr

以下, irb による確認.

irb(main):001:0> enum_char = Enumerator.new do |yielder|
irb(main):002:1*   "banana".each_char do |chr|
irb(main):003:2*     yielder << chr
irb(main):004:2>   end
irb(main):005:1> end
=> #<Enumerator: #<Enumerator::Generator:0x0056404d2ee058>:each>
irb(main):006:0> 
irb(main):007:0* array = enum_char.map do |chr|
irb(main):008:1*   chr.ord
irb(main):009:1> end
=> [98, 97, 110, 97, 110, 97]
irb(main):010:0> 
irb(main):011:0* p array
[98, 97, 110, 97, 110, 97]
=> [98, 97, 110, 97, 110, 97]

以下, 解説より抜粋.

  • map メソッドのブロックは Enumerator オブジェクトをレシーバーとした場合に, Enumerator::Yielder オブジェクトとなり, 設問上のプログラムでは変数 yielder を指す
  • Enumerator::Yielder を評価するには, << を呼び出す

フムフム.

ちょっと理解に苦しんだので, コードを自分なり解析していくことにする.

  • Enumerator クラスについて

ドキュメントより引用.

each 以外のメソッドにも Enumerable の機能を提供するためのラッパークラスです。

Enumerator.new することで, Enumerable なオブジェクトを生成することが出来るという理解. また, Enumerator を生成する場合,

  • Enumerator.new
  • Object#to_enum
  • Object#enum_for

を利用する.

  • そして, Enumerator.new について

さらに, ドキュメントより引用.

Enumerator オブジェクトを生成して返します。与えられたブロックは Enumerator::Yielder オブジェクトを 引数として実行されます。 生成された Enumerator オブジェクトに対して each を呼ぶと、この生成時に指定されたブロックを 実行し、Yielder オブジェクトに対して << メソッドが呼ばれるたびに、 each に渡されたブロックが繰り返されます。 new に渡されたブロックが終了した時点で each の繰り返しが終わります。 このときのブロックの返り値が each の返り値となります。

以下, サンプルコード.

enum = Enumerator.new{|y|
  (1..10).each{|i|
    y << i if i % 5 == 0
  }
}
enum.each{|i| p i }

上記のサンプルを irb で実行する.

irb(main):001:0> enum = Enumerator.new{|y|
irb(main):002:1*   (1..10).each{|i|
irb(main):003:2*     y << i if i % 5 == 0
irb(main):004:2>   }
irb(main):005:1> }
=> #<Enumerator: #<Enumerator::Generator:0x0055b00eac7a60>:each>
irb(main):006:0> enum.each{|i| p i }
5
10
=> 1..10
  • 上記のサンプルだと, y が Enumerator::Yielder オブジェクトになる
  • Enumerator::Yielder オブジェクトに対して, << を呼ぶと {|i| p i } が実行される

という理解しか今のところは出来ない...

他にも Enumerator の実行例.

irb(main):003:0* enum = Enumerator.new(str, :scan, /\w+/)
(irb):3: warning: Enumerator.new without a block is deprecated; use Object#to_enum
=> #<Enumerator: "Yet Another Ruby Hacker":scan(/\w+/)>
irb(main):004:0> enum.each {|word| p word }
"Yet"
"Another"
"Ruby"
"Hacker"
=> "Yet Another Ruby Hacker"
irb(main):005:0> str.scan(/\w+/) {|word| p word }
"Yet"
"Another"
"Ruby"
"Hacker"
=> "Yet Another Ruby Hacker"
irb(main):006:0> "Hello, world!".scan(/\w+/)
=> ["Hello", "world"]
irb(main):007:0> "Hello, world!".to_enum(:scan, /\w+/).to_a
=> ["Hello", "world"]
irb(main):008:0> "Hello, world!".to_enum(:scan).each(/\w+/).to_a
=> ["Hello", "world"]

フムフム...

2018 年 05 月 25 日(金)

ジョギング

日課

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

奥さん

  • 体調が悪いなりになんとか乗り切ったようだ
  • 明日はゆっくりして欲しい

夕飯

  • 俺達の「よしもと」
  • コスパも良いし, 天ぷらは間違い無い

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

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)

文法

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

p [1,2,3,4].map(&self.method(:*))

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

irb(main):001:0> p [1,2,3,4].map(&self.method(:*))
NameError: undefined method `*' for class `Object'

以下, 解説より抜粋.

  • 設問の selfObject クラスのインスタンスになる
  • Object クラスには * メソッドは未定義となる為, NameError: undefine method の例外となる

他の選択肢を実現する為には...

irb(main):014:0> p [1,2,3,4].map {|n| n * 2 }
[2, 4, 6, 8]
=> [2, 4, 6, 8]
irb(main):015:0> p [1,2,3,4].map {|n| n * n }
[1, 4, 9, 16]
=> [1, 4, 9, 16]
irb(main):016:0> p [1,2,3,4].each_with_object(2).map(&:*)
[2, 4, 6, 8]
=> [2, 4, 6, 8]
irb(main):017:0> p [1,2,3,4].each_with_object(2).map(&:**)
[1, 4, 9, 16]
=> [1, 4, 9, 16]

尚, (&:) 記法の場合, 引数を取るようなメソッドは利用出来ないので, Enumerable#each_with_object を利用した.

Enumerable#each_with_object について, ドキュメントより引用.

与えられた任意のオブジェクトと要素をブロックに渡し繰り返し、最初に与えられたオブジェクトを返します。 ブロックを省略した場合は、上の繰り返しをして、最初に与えたオブジェクトを 最後に返す Enumerator を返します。

以下, サンプル.

irb(main):018:0> (1..10).each_with_object([]) {|i, a| a << i*2 }
=> [2, 4, 6, 8, 10, 12, 14, 16, 18, 20]

サンプルの解釈.

  • 与えられた任意のオブジェクト = []
  • 要素 = (1..10)
  • ブロック = {|i, a| a << i*2 }
  • 1 〜 10 の数値がブロックでは i に入ってきて, i * 2 の結果を [] に append していく
  • 10 まで同様の処理が行われたら, [2, 4, 6, 8, 10, 12, 14, 16, 18, 20] を返す

文法

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

def foo(arg:)
  puts arg
end

foo 100

エラーになる

以下, irb にて確認.

irb(main):001:0> def foo(arg:)
irb(main):002:1>   puts arg
irb(main):003:1> end
=> :foo
irb(main):004:0> 
irb(main):005:0* foo 100
ArgumentError: missing keyword: arg

以下, 解説より抜粋.

  • 設問の arg: はキーワード引数となる為, キー (arg:) 省略することは出来ない

エラーを回避する為の方法は, 以下の二つの書き方があると思う.

irb(main):001:0> def foo(arg)
irb(main):002:1>   puts arg
irb(main):003:1> end
=> :foo
irb(main):004:0> foo 100
100
=> nil
irb(main):005:0> foo(100)
100
=> nil
irb(main):006:0> def foo(arg:)
irb(main):007:1>   puts arg
irb(main):008:1> end
=> :foo
irb(main):009:0> 
irb(main):010:0* foo arg: 100
100
=> nil
irb(main):011:0> foo(arg: 100)
100
=> nil

フムフム.