【文法系】【go】基本20:パッケージ

本noteの概要

golang(以下、go)の基本的な文法と出力内容について確認する。

本noteの対象者

・goをインストール済みの方
※ 筆者は仮想環境上でgoを実行していますが、ローカル環境でも基本的に挙動は変わらないと思います、goがインストールされていれば問題ないかと。

▽ 仮想環境上でgoを動かしたい方は以下参考までに ▽
【手順系】【go】仮想環境上でのWebアプリケーション開発①:go環境構築 - This is My note

本noteの環境

PC環境(ホスト)

# OSのバージョン
(base) $ sw_vers
ProductName:    Mac OS X
ProductVersion: 10.14.6
BuildVersion:   18G2022

# Virtualboxのバージョン
(base) $ VBoxManage -v
6.1.2r135662

# vagrantのバージョン
(base) $ vagrant -v
Vagrant 2.2.7

仮想環境(ゲスト)

# Linuxのバージョンが記載されているファイルを検索
vagrant@:~$ ls /etc/*-release
/etc/lsb-release  /etc/os-release

# ゲストOSのバージョンを出力
vagrant@:~$ cat /etc/lsb-release
DISTRIB_ID=Ubuntu
DISTRIB_RELEASE=16.04
DISTRIB_CODENAME=xenial
DISTRIB_DESCRIPTION="Ubuntu 16.04.6 LTS"

仮想環境上のディレクトリ構成

workspace
  - src
    - test
      - lesson.go

現在のディレクト

vagrant@vagrant-ubuntu-trusty-64:~/workspace/src/myapp$ pwd
/home/vagrant/workspace/src/myapp

【注】
 以降、特段の記述がない限り、コマンドの実行は現在のディレクトリ(test)で行われるものとし、文字数削減のため、表記を以下に省略して記述する。

 省略前:vagrant@vagrant-ubuntu-trusty-64:~/workspace/src/myapp$
  ↓
 省略後:$

Today's Thema:パッケージ

1.

使用例

ディレクトリ構成

.
├── main.go
└── mylib
    └── math.go

<main.go>

package main

import (
    "./mylib"
    "fmt"
)

func main() {
    s := []int{1, 2, 3, 4, 5}
    fmt.Println(mylib.Average(s))
}

<mylib/math.go>

package mylib

func Average(s []int)int{
    total := 0
    for _, i := range s{
        total += i
    }
    return int(total/len(s))
}
▼ 実行
$ go run lesson.go

3

《解説》

使用例

ディレクトリ構成

.
├── main.go
└── mylib
    └── math.go
    └── human.go

<mylib/human.go>

package mylib

import "fmt"

func Say(){
      fmt.Println("Human!")
}
▼ 実行
$ go run lesson.go

3
Human!
⑶ さらに下に階層をつくる
使用例

ディレクトリ構成

.
├── main.go
└── mylib
    └── math.go
    └── human.go
    └── under
        └── sub.go

<under/sub.go>

package main

import (
        "fmt"
        "./mylib"
        "./mylib/under"
)

func main() {
    s := []int{1, 2, 3, 4, 5}
        fmt.Println(mylib.Average(s))
     
        mylib.Say()
        under.Hello()
}
▼ 実行
$ go run lesson.go

3
Human!
Hello!
⑷ structをmainから呼び出す
使用例

ディレクトリ構成

.
├── main.go
└── mylib
    └── math.go
    └── human.go
    └── under
        └── sub.go

<mylib/human.go>

package main

import (
        "fmt"

        "./mylib"
        "./mylib/under"
)

func main() {
    s := []int{1, 2, 3, 4, 5}
        fmt.Println(mylib.Average(s))
     
        mylib.Say()
        under.Hello()
        person := mylib.Person{Name: "Mike", Age: 20}
        fmt.Println(person)
}

<main.go>

package main

import (
        "fmt"

        "./mylib"
        "./mylib/under"
)

func main() {
    s := []int{1, 2, 3, 4, 5}
        fmt.Println(mylib.Average(s))
     
        mylib.Say()
        under.Hello()
        person := mylib.Person{Name: "Mike", Age: 20}
        fmt.Println(person)
}
▼ 実行
$ go run lesson.go

3
Human!
Hello!
{Mike 20}

《解説》
structの変数を小文字で書くと、同一パッケージからはアクセスできるが、別のパッケージ(今回であればmain)からはアクセスできなくなってしまうため注意。

<mylib/human.go>

package mylib

import "fmt"

type person struct{ // 小文字に
    name string // 小文字に
    age int // 小文字に
}

func Say(){
      fmt.Println("Human!")
}

<main.go>

package main

import (
        "fmt"

        "./mylib"
        "./mylib/under"
)

func main() {
    s := []int{1, 2, 3, 4, 5}
        fmt.Println(mylib.Average(s))
     
        mylib.Say()
        under.Hello()
        person := mylib.person{name: "Mike", age: 20} // 小文字に
        fmt.Println(person)
}
▼ 出力
# command-line-arguments
./main.go:16:13: cannot refer to unexported name mylib.person
./main.go:16:13: undefined: mylib.person
⑸ testing
単体テスト

テストをしたいプログラム(goファイル)と同階層に「テストをしたいgoファイルの名前_test.go」というファイルを作成する

ディレクトリ構成

.
├── main.go
└── mylib
    └── math.go
    └── math_test.go // 追加
    └── human.go
    └── under
        └── sub.go

<mylib/math_test.go>

package mylib

import "testing"

func TestAverage(t *testing.T) {
    v := Average([]int{1, 2, 3, 4, 5})
    if v != 3{
        t.Error("Expected 3, got", v)
    }
}
▼ 実行
$ go test ./...

?       _/home/vagrant/workspace/src/myapp  [no test files]
ok      _/home/vagrant/workspace/src/myapp/mylib    0.003s
?       _/home/vagrant/workspace/src/myapp/mylib/under  [no test files]

$ go test -v ./...

?       _/home/vagrant/workspace/src/myapp  [no test files]
=== RUN   TestAverage
--- PASS: TestAverage (0.00s)
PASS
ok      _/home/vagrant/workspace/src/myapp/mylib    0.003s
?       _/home/vagrant/workspace/src/myapp/mylib/under  [no test files]

《解説》
go test ./...コマンドでカレントディレクトリ下にあるテストファイルを探して実行。テストファイルがない場合には「[no test files]」を返す。
go test -v ./...コマンドはテスト内容の詳細表示。
goには基本的なテストしかないため、しっかりとしたテストを実行したい場合などは、Ginkgoなどがおすすめ。

https://qiita.com/SYZ/items/373b1150f3f060103730

⑹ gofmt(goフォーマット)

gofmtを使用すると、goの書き方に合わせてコードを修正してくれる。

①:gofmt goファイル名

指定したファイルの中身をgoの書き方に修正してターミナルに出力。

<mylib/math.go>

package mylib




func Average(s      []int)int      {
    total := 0
    for _, i :=     range s{
        total += i
    }
    return int(total      /len(s      ))
}
▼ 実行
mylib$ gofmt math.go

package mylib

func Average(s []int) int {
    total := 0
    for _, i := range s {
        total += i
    }
    return int(total / len(s))
}

《解説》

②:gofmt -w goファイル名

指定したファイルの中身をgoの書き方に合わせて修正し、ファイルの中身自体を書き換えてくれる。

<mylib/math.go>

package mylib




func Average(s      []int)int      {
    total := 0
    for _, i :=     range s{
        total += i
    }
    return int(total      /len(s      ))
}
▼ 実行
mylib$ gofmt -w math.go

<mylib/math.go>

package mylib

func Average(s []int) int {
    total := 0
    for _, i := range s {
        total += i
    }
    return int(total / len(s))
}

《解説》

サードパーティーのパッケージのインストール

$ go get インストールするパッケージ
今回インストールするパッケージ
GitHub - markcheno/go-talib: A pure Go port of TA-Lib (http://ta-lib.org)

その他のサードパーティーパッケージは以下参照
GoDoc

パッケージのインストール

$ go get github.com/markcheno/go-talib

goのパッケージがインストールされている場所の確認

$ go env | grep GOPATH
GOPATH="/home/vagrant/go"

インストールしたパッケージの確認

$ cd /home/vagrant/go
go$ ls
pkg  src

go$ cd src/
go/src$ ls
github.com

go/src$ cd github.com
go/src/github.com$ ls
markcheno

パッケージのインストール

$ go get github.com/markcheno/go-quote

コードの記載 インストールしたパッケージのGithubにあるExampleをそのまま記載。
<main.go>

package main

import (
    "fmt"
    "github.com/markcheno/go-quote"
    "github.com/markcheno/go-talib"
)

func main() {
    spy, _ := quote.NewQuoteFromYahoo("spy", "2016-01-01", "2016-04-01", quote.Daily, true)
    fmt.Print(spy.CSV())
    rsi2 := talib.Rsi(spy.Close, 2)
    fmt.Println(rsi2)
}

main.goがあるディレクトリに戻って以下を実行

▼ 実行
$ go run main.go

datetime,open,high,low,close,volume
2016-01-04 00:00,200.49,201.03,198.59,185.92,222353500.00
2016-01-05 00:00,201.40,201.90,200.05,186.24,110845800.00
2016-01-06 00:00,198.34,200.06,197.60,183.89,152112600.00
2016-01-07 00:00,195.33,197.44,193.59,179.48,213436100.00
2016-01-08 00:00,195.19,195.85,191.58,177.51,209817200.00
2016-01-11 00:00,193.01,193.41,189.82,177.68,187941300.00
2016-01-12 00:00,193.82,194.55,191.14,179.11,172330500.00
2016-01-13 00:00,194.45,194.86,188.38,174.65,221168900.00
2016-01-14 00:00,189.55,193.26,187.66,177.51,240795600.00
2016-01-15 00:00,186.77,188.76,185.52,173.70,324846400.00
2016-01-19 00:00,189.96,190.11,186.20,173.94,195244400.00
2016-01-20 00:00,185.03,187.50,181.02,171.71,286547800.00
2016-01-21 00:00,186.21,188.87,184.64,172.67,195772900.00
2016-01-22 00:00,189.78,190.76,188.88,176.21,168319600.00
2016-01-25 00:00,189.92,190.15,187.41,173.55,130371700.00
2016-01-26 00:00,188.42,190.53,188.02,175.91,141036800.00
2016-01-27 00:00,189.58,191.56,187.06,174.00,185681700.00
2016-01-28 00:00,189.96,190.20,187.16,174.91,143798800.00
2016-01-29 00:00,190.02,193.88,189.88,179.17,210529300.00
2016-02-01 00:00,192.53,194.58,191.84,179.11,136061600.00
2016-02-02 00:00,191.96,191.97,189.54,175.88,182564900.00

〜中略〜

[0 0 11.805138424204099 2.737417921772375 1.6236355292365552 8.280483916125652 56.41181226441782 13.210013791344194 56.23373812144925 24.262958081960942 29.151499083088687 12.98848538074747 41.15032016304844 82.60831803149993 40.11137682547193 68.71920147811743 38.769619274868674 56.65640833450013 88.43649560302671 86.50927232770997 27.27345009263249 49.75170865128946 56.781626138262695 12.891615447548046 6.223179254179022 6.606276618939291 5.844427039641772 1.3065838466637951 71.21984520208864 86.82078611920139 93.62385848231581 74.12207060089115 70.76443355887687 92.30467860170151 40.06362037915057 57.33776517520462 83.15578831123342 67.45567533286915 29.534847273534464 83.77699194472726 87.53180346410304 91.13000160360744 94.0107627029488 94.83644854861915 19.866664331860793 53.070147719517315 58.662246515518866 92.86882220427496 81.92004797782417 63.05407838123072 85.96912267237236 94.073858999178 96.55488432539273 97.35709326625738 82.74114996190607 17.661516166180068 15.984625049680359 32.956170574310484 90.85195137929757 94.99230492260554 63.207998184389005]

《解説》

【補足】
以下のようにすることで、importしたパッケージの名前を変更することもできる。

package main

import (
    "fmt"
    "github.com/markcheno/go-quote"
    a"github.com/markcheno/go-talib" // パッケージ名の前に任意の名前を設定(今回はa)
)

func main() {
    spy, _ := quote.NewQuoteFromYahoo("spy", "2016-01-01", "2016-04-01", quote.Daily, true)
    fmt.Print(spy.CSV())
    rsi2 := a.Rsi(spy.Close, 2) // デフォルトのtalibを今回設定したパッケージ名(a)に変更
    fmt.Println(rsi2)
}

また、コード内で使用しないパッケージは以下のようにアンダースコアをつけることで、エラーが発生しないようにすることもできる。

package main

import (
    "fmt"
    "github.com/markcheno/go-quote"
    _ "github.com/markcheno/go-talib"
)

func main() {
    spy, _ := quote.NewQuoteFromYahoo("spy", "2016-01-01", "2016-04-01", quote.Daily, true)
    fmt.Print(spy.CSV())
    // rsi2 := talib.Rsi(spy.Close, 2)
    fmt.Println(rsi2)
}

今後はgo getではなく、vgoを使用することが多くなるかも、、?
vgo - GoDoc

【文法系】【go】基本19:並列処理(Goroutine)

本noteの概要

golang(以下、go)の基本的な文法と出力内容について確認する。

本noteの対象者

・goをインストール済みの方
※ 筆者は仮想環境上でgoを実行していますが、ローカル環境でも基本的に挙動は変わらないと思います、goがインストールされていれば問題ないかと。

▽ 仮想環境上でgoを動かしたい方は以下参考までに ▽
【手順系】【go】仮想環境上でのWebアプリケーション開発①:go環境構築 - This is My note

本noteの環境

PC環境(ホスト)

# OSのバージョン
(base) $ sw_vers
ProductName:    Mac OS X
ProductVersion: 10.14.6
BuildVersion:   18G2022

# Virtualboxのバージョン
(base) $ VBoxManage -v
6.1.2r135662

# vagrantのバージョン
(base) $ vagrant -v
Vagrant 2.2.7

仮想環境(ゲスト)

# Linuxのバージョンが記載されているファイルを検索
vagrant@:~$ ls /etc/*-release
/etc/lsb-release  /etc/os-release

# ゲストOSのバージョンを出力
vagrant@:~$ cat /etc/lsb-release
DISTRIB_ID=Ubuntu
DISTRIB_RELEASE=16.04
DISTRIB_CODENAME=xenial
DISTRIB_DESCRIPTION="Ubuntu 16.04.6 LTS"

仮想環境上のディレクトリ構成

workspace
  - src
    - test
      - lesson.go

現在のディレクト

vagrant@vagrant-ubuntu-trusty-64:~/workspace/src/test$ pwd
/home/vagrant/workspace/src/test

【注】
 以降、特段の記述がない限り、コマンドの実行は現在のディレクトリ(test)で行われるものとし、文字数削減のため、表記を以下に省略して記述する。

 省略前:vagrant@vagrant-ubuntu-trusty-64:~/workspace/src/test$
  ↓
 省略後:$

Today's Thema:並列処理

1.並列処理(Goroutine)

⑴ 基本的な使い方
使用例
package main

import (
    "fmt"
    "time"
)

func goroutine(s string){
    for i := 0; i < 5; i++{
        time.Sleep(100 * time.Millisecond)
        fmt.Println(s)
    }
}

func normal(s string){
    for i := 0; i < 5; i++{
        time.Sleep(100 * time.Millisecond)
        fmt.Println(s)
    }
}

func main() {
    go goroutine("world")
    normal("hello")
}
▼ 実行
$ go run lesson.go

world
hello
hello
world
world
hello
hello
world
world
hello

《解説》

並列処理を使わなかった場合
package main

import (
    "fmt"
    "time"
)

func goroutine(s string){
    for i := 0; i < 5; i++{
        time.Sleep(100 * time.Millisecond)
        fmt.Println(s)
    }
}

func normal(s string){
    for i := 0; i < 5; i++{
        time.Sleep(100 * time.Millisecond)
        fmt.Println(s)
    }
}

func main() {
    goroutine("world")
    normal("hello")
}
▼ 実行
$ go run lesson.go

world
world
world
world
world
hello
hello
hello
hello
hello

《解説》

【補足】
以下のように、time.Sleep()をコメントアウトすると、goroutine()のスレッドは生成されるものの、goroutine()の処理が始まる前に、normal()の処理が完了し、プログラムが終了してしまうためgoroutine()の中身は出力されない。

package main

import (
    "fmt"
    // "time"
)

func goroutine(s string){
    for i := 0; i < 5; i++{
        // time.Sleep(100 * time.Millisecond)
        fmt.Println(s)
    }
}

func normal(s string){
    for i := 0; i < 5; i++{
        // time.Sleep(100 * time.Millisecond)
        fmt.Println(s)
    }
}

func main() {
    go goroutine("world")
    normal("hello")

}
▼ 出力
hello
hello
hello
hello
hello

そのため、main関数でtime.Sleep()を使用し、プログラムが終了するまでの時間を指定すると、goroutineも出力はされる。

package main

import (
    "fmt"
    "time"
)

func goroutine(s string){
    for i := 0; i < 5; i++{
        // time.Sleep(100 * time.Millisecond)
        fmt.Println(s)
    }
}

func normal(s string){
    for i := 0; i < 5; i++{
        // time.Sleep(100 * time.Millisecond)
        fmt.Println(s)
    }
}

func main() {
    go goroutine("world")
    normal("hello")
    time.Sleep(2000 * time.Millisecond)
}
▼ 出力
hello
hello
hello
hello
hello
world
world
world
world
world

2.sync.WaitGroup

並列処理が実行完了するまでプログラムを終了しないようにする。

⑴ 基本的な使い方
使用例
package main

import (
    "fmt"
    "sync"
    "time"
)

func goroutine(s string, wg *sync.WaitGroup){
    for i := 0; i < 5; i++{
        time.Sleep(100 * time.Millisecond)
        fmt.Println(s)
    }
    wg.Done()
}

func normal(s string){
    for i := 0; i < 5; i++{
        time.Sleep(100 * time.Millisecond)
        fmt.Println(s)
    }
}

func main() {
    var wg sync.WaitGroup
    wg.Add(1)
    go goroutine("world", &wg)
    normal("hello")
    wg.Wait()
}
▼ 実行
$ go run lesson.go

world
hello
world
hello
hello
world
world
hello
hello
world

《解説》
並列処理が完了するまで、プログラムが終了しないため、1のときとは異なり、time.Sleep()をコメントアウトしても並列処理の内容は実行される。

package main

import (
    "fmt"
    "sync"
    // "time"
)

func goroutine(s string, wg *sync.WaitGroup){
    for i := 0; i < 5; i++{
        // time.Sleep(100 * time.Millisecond)
        fmt.Println(s)
    }
    wg.Done()
}

func normal(s string){
    for i := 0; i < 5; i++{
        // time.Sleep(100 * time.Millisecond)
        fmt.Println(s)
    }
}

func main() {
    var wg sync.WaitGroup
    wg.Add(1)
    go goroutine("world", &wg)
    normal("hello")
    wg.Wait()
}
▼ 出力
hello
hello
hello
hello
hello
world
world
world
world
world

3.channel

並列処理を行う関数同士はそれぞれ独立しており、そのままではデータのやりとりができないため、channelを使用してデータのやりとりができるようにする。

⑴ main関数とgoroutine
使用例
package main

import "fmt"

func goroutine(s []int, c chan int){ // ④
    sum := 0
    for _, v := range s{
        sum += v
    }
    c <- sum // c(channel)にsumを入れる
}

func main() {
    s := []int{1, 2, 3, 4, 5} // ①
    c := make(chan int) // ②
    go goroutine(s, c) // ③
    x := <-c // ⑤ c(channel)に入ったsumを受け取りxに代入
    fmt.Println(x) // ⑥
}
▼ 実行
$ go run lesson.go

15

《解説》
f:id:otsuba1:20200225224520p:plain

⑵ main関数とgoroutine1、goroutine2
使用例
package main

import "fmt"

func goroutine1(s []int, c chan int){
    sum := 0
    for _, v := range s{
        sum += v
    }
    c <- sum
}

func goroutine2(s []int, c chan int){
    sum := 5
    for _, v := range s{
        sum += v
    }
    c <- sum
}

func main() {
    s := []int{1, 2, 3, 4, 5}
    c := make(chan int)
    go goroutine1(s, c)
    go goroutine2(s, c)
    x := <-c
    fmt.Println(x)
    y := <-c
    fmt.Println(y)
}
▼ 実行
$ go run lesson.go

20
15

《解説》
f:id:otsuba1:20200225224609p:plain

3.Buffered Channels

使用例
package main

import "fmt"

func main() {
    ch := make(chan int, 2) // make(chan データ型, バッファの数)
    ch <- 100
    fmt.Println(len(ch))
    ch <- 200
    fmt.Println(len(ch))

    x:= <-ch
    fmt.Println(x)

    fmt.Println(len(ch))

    ch <- 300
    fmt.Println(len(ch))
}
▼ 実行
$ go run lesson.go

1
2
100
1
2

《解説》
以下のように、x:= <-chでchannelを取り出さず、バッファー(今回は2)を超えるデータを入れようとするとエラーになる。

func main() {
    ch := make(chan int, 2)
    ch <- 100
    fmt.Println(len(ch))
    ch <- 200
    fmt.Println(len(ch))
    
    ch <- 300
    fmt.Println(len(ch))
}
▼ 出力
1
2
fatal error: all goroutines are asleep - deadlock!

goroutine 1 [chan send]:
main.main()
    /home/vagrant/workspace/src/myapp/lesson.go:12 +0x187
exit status 2
⑵ rangeとclose(ch)

channelとセットでrangeを使用する場合には、close(ch)でchannelの終了を明示する。

使用場面
package main

import "fmt"

func goroutine(s []int, c chan int){
    sum := 0
    for _, v := range s{
        sum += v
        c <- sum
    }
    close(c) // channelの終了を明示
}

func main() {
    s := []int{1, 2, 3, 4, 5}
    c := make(chan int, len(s)) // len(s)でchannelのバッファを指定
    go goroutine(s, c)
    for i := range c{
        fmt.Println(i)
    }
}
▼ 実行
$ go run lesson.go

1
3
6
10
15
使い方
package main

import "fmt"

func main() {
    ch := make(chan int, 2)
    ch <- 100
    fmt.Println(len(ch))
    ch <- 200
    fmt.Println(len(ch))
    close(ch) // channelの終了を明示

    for c := range ch{
        fmt.Println(c)
    }
}
▼ 実行
$ go run lesson.go

1
2
100
200

《解説》
close(ch)がないと、バッファで指定した数を超えるchannelを取りにいこうとしてしまうため、以下のようにエラーになる。

func main() {
    ch := make(chan int, 2)
    ch <- 100
    fmt.Println(len(ch))
    ch <- 200
    fmt.Println(len(ch))

    for c := range ch{
        fmt.Println(c)
    }
}
▼ 出力
1
2
100
200
fatal error: all goroutines are asleep - deadlock!

goroutine 1 [chan receive]:
main.main()
    /home/vagrant/workspace/src/myapp/lesson.go:12 +0x200
exit status 2

4.producerとconsumer

ログを集めて、解析をする場面などで使用

使用例
package main

import (
    "fmt"
    "sync"
    "time"
)

func producer(ch chan int, i int) {
    ch <- i * 2
}

func consumer(ch chan int, wg *sync.WaitGroup) {
    for i := range ch {
        func() {
            defer wg.Done()
            fmt.Println("process", i*1000)
        }()
    }
    fmt.Println("###################")
}

func main() {
    var wg sync.WaitGroup
    ch := make(chan int)

    // Producer
    for i := 0; i < 10; i++ {
        wg.Add(1)
        go producer(ch, i)
    }

    // Consumer
    go consumer(ch, &wg)
    wg.Wait()
    close(ch)
    time.Sleep(2 * time.Second)
    fmt.Println("Done")
}
▼ 実行
$ go run lesson.go

process 0
process 2000
process 4000
process 6000
process 8000
process 10000
process 12000
process 14000
process 16000
process 18000
###################
Done

《解説》
f:id:otsuba1:20200225234836p:plain

5.fan-out fan-in

使用例
package main

import "fmt"

func producer(first chan int) {
    defer close(first)
    for i := 0; i < 10; i++ {
        first <- i
    }
}

func multi2(first <-chan int, second chan<- int) { // 「first <-chan」は送信用のchannel、「second chan<-」は受信用のchannelであることを表す。「<-」はなくてもOK。
    defer close(second)
    for i := range first {
        second <- i * 2
    }
}

func multi4(second chan int, third chan int) {
    defer close(third)
    for i := range second {
        third <- i * 4
    }
}

func main() {
    first := make(chan int)
    second := make(chan int)
    third := make(chan int)

    go producer(first)
    go multi2(first, second)
    go multi4(second, third)
    for result := range third {
        fmt.Println(result)
    }
}
▼ 実行
$ go run lesson.go

0
8
16
24
32
40
48
56
64
72

《解説》
producerのfor文でfirst channelが0を受け取り、multi2のfor文でfirst channelが受け取った0×2を行う。さらに、multi2のfor文の結果を受け取ったsecond channelの0を用いて、multi4で0×4を行い、main関数のresultとして0を出力する。その後も同様の流れで、producerのfor文の条件を満たすまで出力を繰り返す。

f:id:otsuba1:20200226020414p:plain

6.selectを用いたchannelの受信

複数のchannelをお互いにブロッキングしないようにしながら実行する

使用例
package main

import (
    "fmt"
    "time"
)

func goroutine1(ch chan string) {
    for {
        ch <- "packet from 1"
        time.Sleep(3 * time.Second)
    }
}

func goroutine2(ch chan string) {
    for {
        ch <- "packet from 2"
        time.Sleep(1 * time.Second)
    }
}

func main() {
    c1 := make(chan string)
    c2 := make(chan string)
    go goroutine1(c1)
    go goroutine2(c2)

    for {
        select {
        case msg1 := <-c1:
            fmt.Println(msg1)
        case msg2 := <-c2:
            fmt.Println(msg2)
        }
    }
}
▼ 実行
$ go run lesson.go

packet from 2
packet from 1
packet from 2
packet from 1
packet from 2
packet from 1
packet from 2
packet from 1
packet from 2
packet from 1
packet from 2
packet from 1
:

※ 強制的に終了させるまで繰り返す

《解説》
f:id:otsuba1:20200226023027p:plain 以下のようにデータ型が異なるものや出力までの待ち時間が異なる場合でもOK。

package main

import (
    "fmt"
    "time"
)

func goroutine1(ch chan string) {
    for {
        ch <- "packet from 1"
        time.Sleep(3 * time.Second)
    }
}

func goroutine2(ch chan int) {
    for {
        ch <- 100
        time.Sleep(1 * time.Second)
    }
}

func main() {
    c1 := make(chan string)
    c2 := make(chan int)
    go goroutine1(c1)
    go goroutine2(c2)

    for {
        select {
        case msg1 := <-c1:
            fmt.Println(msg1)
        case msg2 := <-c2:
            fmt.Println(msg2)
        }
    }
}
▼ 出力
100
packet from 1
100
100
packet from 1
100
100
100
packet from 1
100
:

※ 強制的に終了させるまで繰り返す

7.Default Selection と for break

使用例
package main

import (
    "fmt"
    "time"
)

func main() {
    tick := time.Tick(100 * time.Millisecond) // 設定した時間ごとに実行
    boom := time.After(500 * time.Millisecond) // 設定した時間経過後に実行
    for {
        select {
        case <-tick:
            fmt.Println("tick.")
        case <-boom:
            fmt.Println("BOOM!")
            return
        default:
            fmt.Println("    .")
            time.Sleep(50 * time.Millisecond)
        }
    }
}
▼ 実行
$ go run lesson.go

    .
    .
tick.
    .
    .
tick.
    .
    .
tick.
    .
    .
tick.
    .
    .
BOOM!

《解説》

package main

import (
    "fmt"
    "time"
)

func main() {
    tick := time.Tick(100 * time.Millisecond)
    boom := time.After(500 * time.Millisecond)

    for {
        select {
        case t := <-tick: // tを設定した場合
            fmt.Println("tick.", t)
        case <-boom:
            fmt.Println("BOOM!")
            return
        default:
            fmt.Println("    .")
            time.Sleep(50 * time.Millisecond)
        }
    }
}
▼ 出力
    .
    .
tick. 2020-02-25 17:36:31.852435353 +0000 UTC m=+0.100577828
    .
    .
tick. 2020-02-25 17:36:31.953306907 +0000 UTC m=+0.201449365
    .
    .
tick. 2020-02-25 17:36:32.053527741 +0000 UTC m=+0.301670329
    .
    .
tick. 2020-02-25 17:36:32.153991047 +0000 UTC m=+0.402133577
    .
    .
tick. 2020-02-25 17:36:32.252393581 +0000 UTC m=+0.500536048
BOOM!

《解説》

⑵ for break

for文を抜ける

使用例
package main

import (
    "fmt"
    "time"
)

func main() {
    tick := time.Tick(100 * time.Millisecond)
    boom := time.After(500 * time.Millisecond)
    OuterLoop: // OuterLoopの名称は任意のものでOK
        for {
            select {
            case <-tick:
                fmt.Println("tick.")
            case <-boom:
                fmt.Println("BOOM!")
                break OuterLoop
            default:
                fmt.Println("    .")
                time.Sleep(50 * time.Millisecond)
            }
        }
    fmt.Println("##############")
}

▼ 実行
$ go run lesson.go

    .
    .
tick.
    .
    .
tick.
    .
    .
tick.
    .
    .
tick.
    .
    .
tick.
BOOM!
##############

8.sync.Mutex

使用例
package main

import (
    "fmt"
    "sync"
    "time"
)

type Counter struct {
    v   map[string]int
    mux sync.Mutex
}

func (c *Counter) Inc(key string) {
    c.mux.Lock()
    defer c.mux.Unlock()
    c.v[key]++
}

func (c *Counter) Value(key string) int {
    c.mux.Lock()
    defer c.mux.Unlock()
    return c.v[key]
}

func main() {
    c := Counter{v: make(map[string]int)}
    go func() {
        for i := 0; i < 10; i++ {
            c.Inc("Key")
        }
    }()
    go func() {
        for i := 0; i < 10; i++ {
            c.Inc("Key")
        }
    }()
    time.Sleep(1 * time.Second)
    fmt.Println(c, c.Value("Key"))
}
▼ 実行
$ go run lesson.go

{map[Key:20] {0 0}} 20

《解説》

【文法系】【go】基本15:ロギングとエラーハンドリング

1.log(ロギング)

以下のように、該当するファイルがない場合、エラー文を表示して、終了する際などエラーハンドリングに使用。

主な使用場面
package main

import (
    "io"
    "log"
    "os"
)

func loggingSettings(logFile string){
    logfile, _ := os.OpenFile(logFile, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
    multiLogFile := io.MultiWriter(os.Stdout, logfile)
    log.SetFlags(log.Ldate | log.Ltime | log.Llongfile)
    log.SetOutput(multiLogFile)
}

func main() {
    loggingSettings("test.log") // test.logに以下のエラーハンドリングの結果を出力
    _, err := os.Open("xxxxxx")
    if err != nil{
        log.Fatalln("Exit", err)
    }
}
▼ 出力
2020/02/24 06:53:21 /home/vagrant/workspace/src/myapp/lesson.go:20: Exit open xxxxxx: no such file or directory
exit status 1

<test.log>

2020/02/24 06:53:21 /home/vagrant/workspace/src/myapp/lesson.go:20: Exit open xxxxxx: no such file or directory
⑴ 基本的な書き方
例①
package main

import (
    "log"
    "fmt"
)

func main() {
    log.Println("logging!")
    log.Printf("%T %v", "test", "test")

    log.Fatalln("error!")

    fmt.Println("OK!") // 実行されない
}
▼ 実行
$ go run lesson.go

2020/02/24 06:27:29 logging!
2020/02/24 06:27:29 string test
2020/02/24 06:27:29 error!
exit status 1

《解説》
「log.Fatalln()」と同様に、「log.Fatalf()」もそこでコードは終了する。

func main() {
    log.Println("logging!")
    log.Printf("%T %v", "test", "test")

    log.Fatalf("%T %v", "test2", "test2")

    fmt.Println("OK!") // 実行されない
}
▼ 出力
2020/02/24 06:38:26 logging!
2020/02/24 06:38:26 string test
2020/02/24 06:38:26 string test2
exit status 1

2.エラーハンドリング

説明

⑴ 基本的な書き方
例①
package main

import (
    "fmt"
    "log"
    "os"
)

func main() {
    file, err := os.Open("./lesson.go")
    if err != nil{
        log.Fatalln("Error!")
    }

    defer file.Close()
    data := make([]byte, 100)
    count, err := file.Read(data)
    if err != nil{
        log.Fatalln("Error")
    }
    fmt.Println(count, string(data))

    err = os.Chdir("test") // Chdirはchangeディレクトリの意
    if err != nil{
        log.Fatalln("Error")
    }
}
▼ 実行
$ go run lesson.go

100 package main

import (
    "fmt"
    "log"
    "os"
)

func main() {
    file, err := os.Open("./les

2020/02/24 07:52:42 Error
exit status 1

《解説》
:=(イニシャライズのショートデクレアレーションは)が最低一つイニシャライズする変数があれば良いため、今回のケースでも使用できる。 つまり、「count, err := file.Read(data)」のcountはイニシャライズされ、errはすでに定義されているため、override(上書き)される。
なお、os.Chdir()については、エラーかどうかのみ返すため、変数は一つとなり、errはすでに定義済みのため、=でoverrideする。

2.panicとrecover

panic:自分で例外を投げられるため、そこでプログラムは強制終了される。

⑴ panic
例①
package main

import "fmt"

func thirdPartyConnectDB(){
    panic("Unable to connect database!")
}

func save(){
    thirdPartyConnectDB()
}

func main() {
    save()
    fmt.Println("OK?")
}
▼ 実行
$ go run lesson.go

panic: Unable to connect database!

goroutine 1 [running]:
main.thirdPartyConnectDB()
    /home/vagrant/workspace/src/myapp/lesson.go:6 +0x39
main.save()
    /home/vagrant/workspace/src/myapp/lesson.go:10 +0x20
main.main()
    /home/vagrant/workspace/src/myapp/lesson.go:14 +0x22
exit status 2

《解説》
save関数でthirdPartyConnectDB関数を呼び出し、panicが実行されるため、そこでプログラムが終了し、main関数にあるfmt.Println("OK?")は実行されない。

⑵ recover
例①
package main

import "fmt"

func thirdPartyConnectDB(){
    panic("Unable to connect database!")
}

func save(){
    defer func(){
        s := recover()
        fmt.Println(s)
    }()
    thirdPartyConnectDB()
}

func main() {
    save()
    fmt.Println("OK?")
}
▼ 実行
$ go run lesson.go

Unable to connect database!
OK?

《解説》
save関数でthirdPartyConnectDB関数を呼び出し、panicが実行されるが、save関数では、thirdPartyConnectDB関数よりも先にdefer func()が記述されているため、panicで発生した例外の内容のみrecover()が受け取り、Printlnで出力する(プログラムの終了はさせない)。プログラムは終了しないため、main関数のfmt.Println("OK?")も実行される。

【補足】
以下のように、panicを呼び出すthirdPartyConnectDB関数をdefer func()よりも先に書くと、panicでプログラムが終了してしまうため注意。

func save(){
    thirdPartyConnectDB()
    defer func(){
        s := recover()
        fmt.Println(s)
    }()
}
▼ 出力
panic: Unable to connect database!

goroutine 1 [running]:
main.thirdPartyConnectDB()
    /home/vagrant/workspace/src/myapp/lesson.go:6 +0x39
main.save()
    /home/vagrant/workspace/src/myapp/lesson.go:10 +0x22
main.main()
    /home/vagrant/workspace/src/myapp/lesson.go:18 +0x22
exit status 2

学習メモ

今日学んだこと、調べた事項のURLをここに記します。

2020/5/23

Reactディレクトリ構成試行記 - hokan公式アカウント - Medium

SPAでのログイン処理のやりかた - Qiita

React.js ライブラリ「react-toastify」を使用してアラート機能を実装する | mebee

state とライフサイクル – React

build-web-application-with-golang/14.2.md at master · astaxie/build-web-application-with-golang · GitHub

2020/5/22

[webpackレシピ] その1: TypeScriptに対応する - sansaisoba's tech blog

2020/5/20

webpack 4 入門 - Qiita

最新版TypeScript+webpack 4の環境構築まとめ(React, Vue.js, Three.jsのサンプル付き) - ICS MEDIA

webpack-dev-serverの導入、設定 - Qiita

webpack-dev-serverの基本的な使い方と設定方法の詳しい解説 | オリジナルゲーム.com

Jest · 🃏快適なJavaScriptのテスト

Babelとwebpackを使ってES6でReactを動かすまでのチュートリアル - Qiita

webpack - ERROR in Entry module not found: Error: Can't resolve './src' が解決できない|teratail

2020/5/19

【Python】絶対値の計算方法について(abs, math.fabs, numpy.abs) | Hbk project

interface{} な変数を型が決まっている関数の引数にする - Qiita

2020/5/18

React HooksのuseCallbackを正しく理解する - Qiita

JavaScriptの「コールバック関数」とは一体なんなのか

JavaScript中級者への道【5. コールバック関数】 - Qiita

React Hooksのルールをよく理解しないとハマるエラー対処法 - Qiita

TypeScriptのEnumのループはObject.entries()で実現可能 - Qiita

TypeScriptに於けるArray.reduceの型推論の種類 - Qiita

2020/5/12

Railsで超簡単API - Qiita

RailsでAPI用のアプリを作成(POST処理編) - 親バカエンジニアのナレッジ帳

CORSがよくわからないので解説してみた&Rails APIでのCORS設定 - Qiita

2020/5/11

TypeScriptの型入門 - Qiita

React開発において便利なTypeScriptの型まとめ - Qiita

JavaScriptで書く「let,var,const」の違い・使い分け | TechAcademyマガジン

イマドキのJavaScriptの書き方2018 - Qiita

React.js - React.FC型の使い所|teratail

async await の使い方 - Qiita

Promiseの使い方、それに代わるasync/awaitの使い方 - Qiita

TypeScriptでReactをやるときは、小さいアプリでもReduxを最初から使ってもいいかもねというお話 | フューチャー技術ブログ

コードで理解するRedux(React使用) - Qiita

クラス · JavaScript Primer #jsprimer

ReactとFirebaseを使ってログインフォームを実装する② | Harkerblog

2020/5/10

【React | Redux】connect()をざっくりと解説してみる | Qrunch(クランチ)

TypeScript+Reduxで全ステートの型を解決するには - Qiita

2020/5/8

TypeScriptで始めるReactプロジェクトのボイラープレート作ってみた | Qrunch(クランチ)

Awesome Go : 素晴らしい Go のフレームワーク・ライブラリ・ソフトウェアの数々 - Qiita

TypeScript で書く React コンポーネントを基礎から理解する - Qiita

React (TypeScript): ベストプラクティス - Qiita

TypeScript+React+Reduxチュートリアル · nametake.info

2020/5/7

【INNER JOIN, LEFT JOIN , RIGHT JOIN】テーブル結合の挙動をまとめてみた【SQL】 - Qiita

Golang x Beego x Docker x CircleCI x npmで開発環境をサクッと作ってみよう - Qiita

自動テストのスタブ・スパイ・モックの違い | gotohayato.com

Go言語でテスト作成 testifyの基本的な使い方 | RE:ENGINES

【GitHub】ソースコード検索したい。 - Qiita

go-sqlmockを使ってGORMで書いたコードをテストする - Qiita

Goでデータベースを簡単にモック化する【sqlmock】 - Qiita

2020/5/6

pythonでそのまま数値を出力すると小数点以下も表示される。 小数点以下を使用しない場合は、出力時にint()を使用する

def games(n):
    num = (n * (n-1))/2
    print(int(num)) # int()を使用しない場合は小数点以下も表示される

if __name__ == "__main__":
    number = int(input())
    games(number)

Pythonで小数点以下を切り捨て・切り上げ: math.floor(), math.ceil() | note.nkmk.me

2020/5/5

【Go】string型からint64型・uint64型へ変換する方法 - Qiita

Go-gorm: JOINS make life easier! · Kaviraj

INNER JOINでエラーメッセージ「Column 'カラム名' in field list is ambiguous」が出た時 - Qiita

2020/5/4

《図解》 SaaS、PaaS、IaaSってどういう意味?そしてその違いとは?

Docker Compose restart の挙動 - 技術備忘記

.gitignoreしたファイルをレポジトリから削除する方法 - Qiita

git addを取り消す方法 - Reasonable Code

基本的なシステム構成図を理解するためのAWS基礎をまとめてみた - Qiita

2020/5/3

create-react-appで作った雛形のコードがService Workerで何をしているのか - Qiita

https://note.com/npaka/n/n6d0e8cd4ebe0?scrollpos=comment

なぜReact+TypeScriptでコンポーネント作成が早くなるのか - Qiita

TypeScriptでredux-formを使ってみる - かずきのBlog@hatena

TypeScriptの型におけるJSXサポートが100%分かる記事 - Qiita

Redux Form V6 typescript definition · GitHub

2020/5/1

軽量プログラミング言語とは?おすすめ言語5選と便利な使い方を解説!軽量言語を使うメリットは?強みを活かした使用例も紹介 | A-STAR(エースター)

GinでBindingが物珍しかったので他のフレームワークも調べてみた - Qiita

2020/4/30

アサーション(アサート)とは - IT用語辞典 e-Words

Goでメソッドを簡単にモック化する【gomock】 - Qiita

GitHub - golang/mock: GoMock is a mocking framework for the Go programming language.

モジュールバンドラーはなぜモダンなフロントエンドの開発に必要なのか?|Kosukeee|note

フロントエンド知らない私のwebpack入門 その1 - Qiita

Testifyでmockを作ってテストを記述してみたメモ - Qiita

Goテストの綺麗な書き方 - Eureka Engineering - Medium

Golangでtestingことはじめ(1)〜testingパッケージを使ったユニットテスト〜 - DeNA Testing Blog

DI・DIコンテナ、ちゃんと理解出来てる・・? - Qiita

0.デザインパターンの基本 1 | TECHSCORE(テックスコア)

GoF デザインパターン チートシート - Qiita

2020/4/29

JSのスプレッド構文を理解する - Qiita

【JavaScript】Spread構文とRestパラメーターを使いこなそう - Qiita

【JavaScript入門】初心者でも分かるreduce()の使い方とサンプル例まとめ | 侍エンジニア塾ブログ(Samurai Blog) - プログラミング入門者向けサイト

2020/4/28

Handling Events – React

UI Events

W3Cとは?Web標準化の重要性とW3Cの勧告プロセス

Widgetとは何? Weblio辞書

Getting Started with React - An Overview and Walkthrough Tutorial – Tania Rascia

TypeScriptでジェネリクス(Generics)を理解するための簡単なチュートリアル | I am mitsuruog

ユーティリティって何ですか? - jQueryを勉強中です。今、こち... - Yahoo!知恵袋

typescriptのuniontypesについて - ウェブエンジニア珍道中

【考えたこと、感じたこと】

・その日学んだことをその場限りにせず、次の学びにつなげるアプリをつくりたい
→その日調べたURLを保存できる。その日にやったことを振り返り、次のアクションを立てられるなど
→学習は反復により定着する。どうやって簡単に反復できるようにするかが課題。
→日次の学習記録には貼り付けたURLが記載されるが、学習記録概要?には、URLのトップページのみが保存される?(URLの/でアクセスできる部分)

【Go】本noteの基本的な環境について

本noteの環境

PC環境(ホスト)

# OSのバージョン
(base) $ sw_vers
ProductName:    Mac OS X
ProductVersion: 10.14.6
BuildVersion:   18G2022

# Virtualboxのバージョン
(base) $ VBoxManage -v
6.1.2r135662

# vagrantのバージョン
(base) $ vagrant -v
Vagrant 2.2.7

仮想環境(ゲスト)

# Linuxのバージョンが記載されているファイルを検索
vagrant@:~$ ls /etc/*-release
/etc/lsb-release  /etc/os-release

# ゲストOSのバージョンを出力
vagrant@:~$ cat /etc/lsb-release
DISTRIB_ID=Ubuntu
DISTRIB_RELEASE=16.04
DISTRIB_CODENAME=xenial
DISTRIB_DESCRIPTION="Ubuntu 16.04.6 LTS"

仮想環境上のディレクトリ構成

workspace
  - src
    - test
      - lesson.go

現在のディレクト

vagrant@vagrant-ubuntu-trusty-64:~/workspace/src/test$ pwd
/home/vagrant/workspace/src/test

【注】
 以降、特段の記述がない限り、コマンドの実行は現在のディレクトリ(test)で行われるものとし、文字数削減のため、表記を以下に省略して記述する。

 省略前:vagrant@vagrant-ubuntu-trusty-64:~/workspace/src/test$
  ↓
 省略後:$

【手順系】【Python】vagrant+dockerを使ったWebアプリケーション開発②:Flaskによる「Hello World!」

本noteの概要

仮想環境上でFlaskを使用したWebアプリケーションを開発する。

本noteの対象者

VirtualBoxおよびVagrantをインストールしている方
docker hubのアカウントを開設している方
・dockerおよびdocker-composeをインストールしている方

▽ dockerのインストール確認
vagrant@ubuntu-xenial:~$ docker version
Client: Docker Engine - Community
 Version:           19.03.6
 API version:       1.40
 Go version:        go1.12.16
 Git commit:        369ce74a3c
 Built:             Thu Feb 13 01:28:06 2020
 OS/Arch:           linux/amd64
 Experimental:      false
Got permission denied while trying to connect to the Docker daemon socket at unix:///var/run/docker.sock: Get http://%2Fvar%2Frun%2Fdocker.sock/v1.40/version: dial unix /var/run/docker.sock: connect: permission denied
▽ docker-composeのインストール確認
vagrant@ubuntu-xenial:~$ docker-compose version
docker-compose version 1.23.1, build b02f1306
docker-py version: 3.5.0
CPython version: 3.6.7
OpenSSL version: OpenSSL 1.1.0f  25 May 2017

※ 筆者は仮想環境上でdockerを実行しているため、ローカルでの操作と少々異なる部分があるかもしれません。仮想環境上でdockerを動かしたい方は以下を参考にしてみてください。
【手順系】vagrant+dockerを使ったWebアプリケーション開発①:開発環境構築と「Hello World!」 - This is My note

本noteの環境

PC環境(ホスト)

# OSのバージョン
(base) $ sw_vers
ProductName:    Mac OS X
ProductVersion: 10.14.6
BuildVersion:   18G2022

# Virtualboxのバージョン
(base) $ VBoxManage -v
6.1.2r135662

# vagrantのバージョン
(base) $ vagrant -v
Vagrant 2.2.7

仮想環境(ゲスト)

# Linuxのバージョンが記載されているファイルを検索
vagrant@:~$ ls /etc/*-release
/etc/lsb-release  /etc/os-release

# ゲストOSのバージョンを出力
vagrant@:~$ cat /etc/lsb-release
DISTRIB_ID=Ubuntu
DISTRIB_RELEASE=16.04
DISTRIB_CODENAME=xenial
DISTRIB_DESCRIPTION="Ubuntu 16.04.6 LTS"

Today's Thema:Flaskを使ったブラウザでの「Hello World!」

完成図

<最終的なディレクトリ構成>
▼ ホスト(ローカル)

- ops
 - testapp   
  - src   
   - app.py   
- Vagrantfile   

※ ローカルのopsディレクトリ下を仮想環境と同期させているため、ローカル上もしくは仮想環境上でディレクトリやファイルを作成した場合には、仮想環境上もしくはローカル上にも同じものが表示されます。
▼ ゲスト(仮想環境)

- srv   
 - ops   
  - testapp   
   - src   
    - app.py   

Today's Thema:Flaskアプリの作成

1.Dockerイメージの作成〜コンテナの起動

⑴ Dockerfileの作成

testapp$ touch Dockerfile

⑵ Dockerfileの編集

FROM ubuntu:latest

RUN apt-get update
RUN apt-get install python3 python3-pip -y

RUN pip3 install flask

⑶ Dockerイメージのビルド

testapp$ sudo docker build . -t flask/app:1.0

Sending build context to Docker daemon  3.584kB
Step 1/4 : FROM ubuntu:latest
latest: Pulling from library/ubuntu
Digest: sha256:8d31dad0c58f552e890d68bbfb735588b6b820a46e459672d96e585871acc110
Status: Downloaded newer image for ubuntu:latest
 ---> ccc6e87d482b

〜中略〜

Installing collected packages: itsdangerous, MarkupSafe, Jinja2, click, Werkzeug, flask
Successfully installed Jinja2-2.11.1 MarkupSafe-1.1.1 Werkzeug-1.0.0 click-7.0 flask-1.1.1 itsdangerous-1.1.0
Removing intermediate container e0bcc95b9688
 ---> 9d8501c135c1
Successfully built 9d8501c135c1
Successfully tagged flask/app:1.0

⑷ 作成されたイメージの確認

testapp$ sudo docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
flask/app           1.0                 9d8501c135c1        29 seconds ago      474MB
ubuntu              latest              ccc6e87d482b        5 weeks ago         64.2MB

⑸ Dockerコンテナの起動

testapp$ sudo docker run -it flask/app:1.0 /bin/bash
root@726c66c1dc24:/# 

【補足】コンテナ内の環境確認
Dockerコンテナが起動したら、以下のように「python3」と入力し、pythonの対話モードが表示されたらOK。
なお、対話モードを終了するにはexit()と入力するか、control+dを押す。

root@726c66c1dc24:/# python3
Python 3.6.9 (default, Nov  7 2019, 10:44:02) 
[GCC 8.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> 

2.Flaskの作成・起動

pythonファイルの作成

testapp$ mkdir src && cd src

※ 上記のディレクトリ名は何でもOK

testapp/src$ touch app.py

<app.py>

from flask import Flask

app = Flask(__name__)

@app.route('/')
def index():
    return "Hello World!"

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5000)
. # 現在のディレクトリの意味
├── Dockerfile
└── src
    └── app.py

⑵ コンテナの起動

cd ..で一階層上に上がって、testappディレクトリに戻った上で、以下を実行。

testapp$ sudo docker run -it -p 80:5000 -v $(pwd)/src:/home flask/app:1.0 /bin/bash

root@67b6da95b1df:/#

※ 仮想環境を使用していない場合は、sudo docker run -it -p 5000:5000 -v $(pwd)/src:/home flask/app:1.0 /bin/bashでOK。
仮想環境上でコンテナを起動する場合は、仮想環境のポート番号に合わせて80の箇所を変更。今回は、Vagrantfileでconfig.vm.network "forwarded_port", guest: 80, host: 8080と設定しているため、仮想環境のポートは80に設定し、ブラウザにアクセスする際は8080を使用する。

pythonファイルのマウント確認

上記コンテナ起動時に、srcディレクトリにあるファイルをコンテナ内のhomeディレクトリにマウントしたため、コンテナのhomeディレクトリにapp.pyがあるかどうかを確認。

root@67b6da95b1df:/# ls
bin  boot  dev  etc  home  lib  lib64  media  mnt  opt  proc  root  run  sbin  srv  sys  tmp  usr  var
root@67b6da95b1df:/# cd home/
root@67b6da95b1df:/home# ls
app.py

⑷ Flaskの起動

root@67b6da95b1df:/home# python3 app.py
 * Serving Flask app "app" (lazy loading)
 * Environment: production
   WARNING: This is a development server. Do not use it in a production deployment.
   Use a production WSGI server instead.
 * Debug mode: off
 * Running on http://0.0.0.0:5000/ (Press CTRL+C to quit)
10.0.2.2 - - [21/Feb/2020 07:53:00] "GET / HTTP/1.1" 200 -
10.0.2.2 - - [21/Feb/2020 07:55:01] "GET / HTTP/1.1" 200 -
10.0.2.2 - - [21/Feb/2020 07:55:02] "GET / HTTP/1.1" 200 -

⑸ ブラウザ上での表示確認

http://localhost:8080/にアクセスし、「Hello World!」と表示されるかどうか確認。
なお、仮想環境を使用していない場合は、http://localhost:5000/にアクセス。
※ 仮想環境を使っている方で、8080番以外のポートを指定した方はそれに合わせてアクセス。
f:id:otsuba1:20200221183041p:plain 【補足】
app.pyの内容を変更した場合には、control+cで一度Flaskを停止し、再度python3 app.pyでFlaskを起動すると内容が更新される。

【参考】
Docker+Flaskによるお手軽Webアプリ開発 - Qiita

【手順系】vagrant+dockerを使ったWebアプリケーション開発①:開発環境構築と「Hello World!」

本noteの概要

仮想環境上でのWebアプリケーション開発を行うため、Vagrantfileに必要なコードを記載し、vagrant upコマンドで仮想環境を構築する。

本noteの対象者

VirtualBoxおよびVagrantをインストールしている方
docker hubのアカウントを開設している方

本noteの環境

PC環境(ホスト)

# OSのバージョン
(base) $ sw_vers
ProductName:    Mac OS X
ProductVersion: 10.14.6
BuildVersion:   18G2022

# Virtualboxのバージョン
(base) $ VBoxManage -v
6.1.2r135662

# vagrantのバージョン
(base) $ vagrant -v
Vagrant 2.2.7

Today's Thema:vagrant+dockerによる開発環境構築

完成図

<最終的なディレクトリ構成>
▼ ホスト(ローカル)

- ops # 今後のアプリケーション開発でソースを管理するための空のディレクトリ     
- Vagrantfile   

▼ ゲスト(仮想環境)

- srv   
 - ops   

1.仮想環境情報の設定

vagrant initコマンドでVagrantfileを作成し、Vagrantfileに環境構築のために必要な設定を記述します。

⑴ Vagrantfileの作成(vagrant 初期化)

(base) $ mkdir vagrant-docker && cd vagrant-docker # 適当なディレクトリを作成し、作成したディレクトリへ移動
(base) :vagrant-docker $ vagrant init ubuntu/xenial64 # vagrant 初期化

A `Vagrantfile` has been placed in this directory. You are now
ready to `vagrant up` your first virtual environment! Please read
the comments in the Vagrantfile as well as documentation on
`vagrantup.com` for more information on using Vagrant.

⑵ Vagrantfileを使った仮想環境情報設定

<vagrantfile>

$install_docker = <<SCRIPT
sudo apt-get -y install apt-transport-https ca-certificates curl software-properties-common
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable"
sudo apt-get update
sudo apt-get -y install docker-ce
sudo systemctl start docker
sudo curl -L "https://github.com/docker/compose/releases/download/1.23.1/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose
docker-compose --version
SCRIPT

Vagrant.configure("2") do |config|
  config.vm.box = "ubuntu/xenial64"
  config.vm.network "forwarded_port", guest: 80, host: 8080 # localhostと仮想環境をつなげるための設定
  config.vm.synced_folder 'ops/', '/srv/ops'
  config.vm.provision 'shell', inline: $install_docker
end

ソースコードを管理するディレクトリの作成

今後、アプリケーション開発を行う際のソースコードを管理するディレクトリを作成。

(base) :vagrant-docker $ mkdir ops

2.仮想環境の起動・接続

⑴ 仮想環境の起動

(base) :vagrant-docker $ vagrant up

Bringing machine 'default' up with 'virtualbox' provider...
==> default: Box 'centos/7' could not be found. Attempting to find and install...
    default: Box Provider: virtualbox
    default: Box Version: >= 0

〜中略〜

    default: docker-compose version 1.23.1, build b02f1306

⑵ 仮想環境への接続

(base) :vagrant-docker $ vagrant ssh

Welcome to Ubuntu 16.04.6 LTS (GNU/Linux 4.4.0-173-generic x86_64)

 * Documentation:  https://help.ubuntu.com
 * Management:     https://landscape.canonical.com
 * Support:        https://ubuntu.com/advantage


17 packages can be updated.
16 updates are security updates.

New release '18.04.4 LTS' available.
Run 'do-release-upgrade' to upgrade to it.

vagrant@ubuntu-xenial:~$

3.仮想環境のセットアップ状況確認

⑴ ゲストOSのバージョン確認

# Linuxのバージョンが記載されているファイルを検索
vagrant@ubuntu-xenial:~$ ls /etc/*-release
/etc/lsb-release  /etc/os-release

# ゲストOSのバージョンを出力
vagrant@ubuntu-xenial:~$ cat /etc/lsb-release
DISTRIB_ID=Ubuntu
DISTRIB_RELEASE=16.04
DISTRIB_CODENAME=xenial
DISTRIB_DESCRIPTION="Ubuntu 16.04.6 LTS"

⑵ dockerのインストール確認

vagrant@ubuntu-xenial:~$ docker version
Client: Docker Engine - Community
 Version:           19.03.6
 API version:       1.40
 Go version:        go1.12.16
 Git commit:        369ce74a3c
 Built:             Thu Feb 13 01:28:06 2020
 OS/Arch:           linux/amd64
 Experimental:      false
Got permission denied while trying to connect to the Docker daemon socket at unix:///var/run/docker.sock: Get http://%2Fvar%2Frun%2Fdocker.sock/v1.40/version: dial unix /var/run/docker.sock: connect: permission denied

⑶ docker-composeのインストール確認

vagrant@ubuntu-xenial:~$ docker-compose version
docker-compose version 1.23.1, build b02f1306
docker-py version: 3.5.0
CPython version: 3.6.7
OpenSSL version: OpenSSL 1.1.0f  25 May 2017

4.Dockerコンテナを使った「Hello World!」

⑴ Webアプリケーション動作確認用コンテナ(training/webapp)の起動
vagrant@ubuntu-xenial:~$ sudo docker run -d -p 80:5000 training/webapp python app.py
Unable to find image 'training/webapp:latest' locally
latest: Pulling from training/webapp
Image docker.io/training/webapp:latest uses outdated schema1 manifest format. Please upgrade to a schema2 image for better future compatibility. More information at https://docs.docker.com/registry/spec/deprecated-schema-v1/
e190868d63f8: Pull complete 
909cd34c6fd7: Pull complete 
0b9bfabab7c1: Pull complete 
a3ed95caeb02: Pull complete 
10bbbc0fc0ff: Pull complete 
fca59b508e9f: Pull complete 
e7ae2541b15b: Pull complete 
9dd97ef58ce9: Pull complete 
a4c1b0cb7af7: Pull complete 
Digest: sha256:06e9c1983bd6d5db5fba376ccd63bfa529e8d02f23d5079b8f74a616308fb11d
Status: Downloaded newer image for training/webapp:latest
1eb795fd41625fb81974d2a54a66e39329f535fc9a664bcb5e21c2cb9ce4f4ce
⑵ ブラウザ上での表示確認

http://localhost:8080/

f:id:otsuba1:20200213181854p:plain

※起動したコンテナは後述のdocker stopコマンドで停止できます。

5.仮想環境やdocker関連コマンド

○ 起動中コンテナの一覧表示
vagrant@ubuntu-xenial:~$ sudo docker ps
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS                  NAMES
1eb795fd4162        training/webapp     "python app.py"     19 minutes ago      Up 19 minutes       0.0.0.0:80->5000/tcp   gallant_murdock

※ 停止中のコンテナも含める場合はsudo docker ps -aコマンドを使用

○ 起動中コンテナへのログイン
vagrant@ubuntu-xenial:~$ sudo docker exec -it 1eb795fd4162 /bin/bash
root@1eb795fd4162:/opt/webapp# 

【参考】今回使用した動作確認用コンテナの中身について
上記のコマンドで起動中コンテナにログインし、lsコマンドを入力するとコンテナ内のコンテンツを確認することが可能。

root@1eb795fd4162:/opt/webapp# ls
Procfile  app.py  requirements.txt  tests.py

またvi app.pyコマンドでファイルの中身を確認すると、今回のアプリケーションはPython(Flask)で書かれていることがわかります。

<app.py>

import os

from flask import Flask

app = Flask(__name__)

@app.route('/')
def hello():
    provider = str(os.environ.get('PROVIDER', 'world'))
    return 'Hello '+provider+'!'

if __name__ == '__main__':
    # Bind to PORT if defined, otherwise default to 5000.
    port = int(os.environ.get('PORT', 5000))
    app.run(host='0.0.0.0', port=port)
○ 起動しているDockerコンテナの停止
vagrant@ubuntu-xenial:~$ sudo docker stop 1c763df10ac5
○ 仮想環境からのログアウト
vagrant@ubuntu-xenial:~$ exit
○ 仮想環境の停止
(base) :vagrant-docker $ vagrant halt
==> default: Attempting graceful shutdown of VM...
○ 仮想環境の稼働状況確認
(base) $ vagrant global-status
id       name     provider   state    directory                                   
----------------------------------------------------------------------------------
48dd523  default  virtualbox running  /Users/vm/infra                     
6126d9b  main     virtualbox running  /Users/vm/ubuntu                    
2911deb  minikube virtualbox poweroff /Users/vm/Linux/vagrant-minikube               
 
The above shows information about all known Vagrant environments
on this machine. This data is cached and may not be completely
up-to-date (use "vagrant global-status --prune" to prune invalid
entries). To interact with any of the machines, you can go to that
directory and run Vagrant, or you can use the ID directly with
Vagrant commands from any directory. For example:
"vagrant destroy 1a2b3c4d"
○ 仮想環境の削除
(base) $ vagrant destroy 48dd523

【参考】
Vagrant+VirtualBoxによるDocker環境構築 - Qiita
Vagrantの使い方 - Qiita
Dockerインストールメモ - Qiita
【まとめ】Vagrant コマンド一覧 - Qiita
VagrantとDockerで「環境に縛られない」開発環境を構築しよう - RAKUS Developers Blog | ラクス エンジニアブログ