Kuzunoha-NEのブログ

プログラミングなどの勉強をしてます

【Docker】docker-compose "exec" について

こんばんは、葛の葉です。

さて、DockerComposeを使ってコンテナを立ち上げたり、あるいはDockerNetworkを構築したりすることがあると思います。DockerComposeで立ち上げたコンテナの中に侵入するためにdocker exec -it コンテナ名 /bin/bashと打つ方もいらっしゃるかと思いますが、docker-compose.ymlにコンテナ名を確認しに行ったり、docker ps -a等で動いている(あるいは死んでる)コンテナを見に行ったりしてコンテナ名を確認したりしているのかなと思慮します。

前置きが長くなってしまいましたが、今回は上記よりもう少し簡単にDockerComposeで起動したコンテナ内に侵入できるdocker-compose execについてお話します。

docker-composeの記載内容の例

version: "3"
services:
    py:
        build: .
        image: python:3.6.5
        container_name: test_python
        volumes:
          - "./program/:/var/program/"
        tty: true
        networks:
            test_net:
                ipv4_address: 172.20.0.2
    rdy:
        image: redis:4.0.10
        container_name: test_redis
        volumes:
          - "./data:/data"
        networks:
            test_net:
                ipv4_address: 172.20.0.3
networks:
    test_net:
        driver: bridge
        ipam:
            driver: default 
            config: 
            - subnet: 172.20.0.0/24 

上記のようなdocker-compose.ymlがあったとします。今回の方法ではサービスを用います。DockerComposeにおけるサービスとはコンテナにオプションを施したものの単位という認識でよいと思います。今回、docker-compose.ymlに記載されているサービスはpyrdyとになります。pyというサービスはpython:3.6.5のイメージを使ってコンテナを作成し、volumesというオプションを使ってホストとゲスト間のディレクトリを共有しています。

まず、docker-compose.ymlがあるディレクトリ上でdocker-compose up -dコマンドを実行し、2つのサービスをデタッチドモード起動しましょう。2つのサービスの起動が完了するまで待ちます。

上記のサービスに侵入するには以下のコマンドを使用します。

docker-compose exec (service名) /bin/bash

今回のケースだとdocker-compose exec py /bin/bashになります。

root@50947e77690d:/#

こんな感じで出力されたかと思います。50947e77690dの部分は人やタイミングによって変わるかな、と思います。

docker-compose exec (service名) (command)

例えばdocker-compose exec py pythonとすることで、Pythonコマンドラインを出力することできます。

Python 3.6.5 (default, Jun 27 2018, 08:15:56)
[GCC 6.3.0 20170516] on linux
Type "help", "copyright", "credits" or "license" for more information.

今回は起動したサービスの元となっているImageがpython:3.6.5であり、pythonがインストールされているイメージであるため、上記のコマンドラインが出現しています。

では、もう一つのサービスであるrdyのコンテナに入ってみましょう。こちらはRedis:4.0.1がインストールされているサービスなります。

> docker-compose exec rdy redis-cli set testkey hoge
OK
> docker-compose exec rdy redis-cli get testkey
"hoge"

docker-compose exec rdy redis-cli set testkey hogeを分解すると

docker-compose exec はDockerComposeのコマンド

rdydocker-compose.ymlで作成したサービスの名前

redis-cli set testkey hogeはサービスに実行してもらうコマンド部分になります。

余談ですが、redis-cliコマンドはRedis用のコマンドです。set (KEY) (VALUE)とすると、KEYに値をセットでき、get (KEY)とするとKEYにセットされた値を出力しています。

今回はtestkeyというKEYにhogeというVALUEを与えています。

/bin/bashもコマンドです

ご存知の方が多いと思いますが、/bin/bashbashというshellを呼び出すコマンドです。docker-compose exec (service名) /bin/bashとは、サービス内のbashを呼び出しているので、サービス内に侵入できているわけです。

【Python】byte型だけが入ったlistを全てstr型に変換したlistとして返す + アノテーションを少し

こんばんは、葛の葉です。

さて、私は最近、Redisを使っているのですが、Pythonを通して取得した場合、byte型で受け取ることになると思います。Listとして受け取った場合、List内全てがbyte型になってしまいます。今回はbyteが入ったリストを一括で変化するための関数を作りました。

環境

Python 3.6.5

コード

def byte_to_str(byte_list: list) -> list:
    return [i.decode('utf8') for i in byte_list]

少し変わった書き方が見えると思います。引数(byte_list: list) -> list: list-> listアノテーションと言って、注釈の意味になります。(byte_list: list)の個所のアノテーションは「引数に入れるのはリストで入れてください」と注釈しています。また、 -> listの個所は返り値の型がlistであることを注釈しています。ただ、あくまで注釈なので拘束力はありません。

もう一つ、返り値の[i.decode('utf8') for i in byte_list]の書き方はリスト内包表記となっております。

実行

test_list = [b'python',b'ruby', b'node', b'golang']
byte_to_str(test_list)
['python', 'ruby', 'node', 'golang']

さもありなん

アノテーションあれこれ

文字列を入れることもできるので説明しやすくていいですね。

def byte_to_str(byte_list: "byteだけが入ったlistを入れてね") -> "listで返します。":
    return [i.decode('utf8') for i in byte_list]

また、アノテーションには拘束力はないので、引数に文字列を入れることが出来てしまいます。以下は引数を文字列で渡しました。結果、strオブジェクトのAttributeErrorになっています。引数として読み込まれたtest変数が関数内で.decode('utf8')を呼び出そうとした際に起きたErrorです。str.decode('utf8')インスタンスが無いのでAttributeErrorとなります。

test = 'insert str'
byte_to_str(test)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 2, in byte_to_str
  File "<stdin>", line 2, in <listcomp>
AttributeError: 'str' object has no attribute 'decode'

その他、アノテーションについて、詳しくはPEPの以下のリンクをチェック

www.python.org

【Python】 インスタンス変数を辞書型で出力 + JSON形式で文字列にする。

こんばんは、葛の葉です。

Pythonインスタンス変数を辞書型で出力する方法とそれをJSONに変換する方法を記載します。

環境

Python 3.6.5

こんな感じ

class MyClass(object):
    def __init__(self, name):
        self.name = name
        self.gender = 'male'
        self._address = '192-1999'
        self.interest = ['Kemono Friends', 'DoDonpachi', 'Python']
        self.age = 111

myclass = MyClass('kuzunoha')

このようにクラスとインスタンスを作成する。

print(myclass.__dict__)

myclass.__dict__インスタンス変数を辞書型として持っている。

{'name': 'kuzunoha', 'gender': 'male', '_address': '192-1999', 'interest': ['Kemono Friends', 'DoDonpachi', 'Python'], 'age': 111}

JSON形式文字列にする。

内部modulejsonjson.dumpsを使ってJSON型にする。

import json # ここ追加になっている点に注意

class MyClass(object):
    def __init__(self, name):
        self.name = name
        self.gender = 'male'
        self._address = '192-1999'
        self.interest = ['Kemono Friends', 'DoDonpachi', 'Python']
        self.age = 111

    def to_json(self):
        return json.dumps(self.__dict__, ensure_ascii=False, indent=4) 

myclass = MyClass('kuzunoha')

json.dumps(self.__dict__, ensure_ascii=False, indent=4)jsonモジュールを使って辞書型をjsonに変更しています。

オプションのensure_ascii=Falseはこれを打たないとバイト文字になってしまうので、そのようにしています。

indent=4はインデントを空白4つで自動で行ってくれます。自動ってすごい。かしこい。

print(myclass.to_json())で出力してみましょう。

{
    "name": "kuzunoha",
    "gender": "male",
    "_address": "192-1999",
    "interest": [
        "Kemono Friends",
        "DoDonpachi",
        "Python"
    ],
    "age": 111
}

上記の通り出ている。けれども、少し問題なのがアンダースコア_で定義したインスタンス変数もそのまま出力されている点かも?

それが嫌なら、一度、インスタンス変数の辞書をコピーして、そこから_addressを抜き取ってみる?

    def to_json(self):
        dicts = self.__dict__.copy()
        # 以下の要素はjsonには要らないので削除
        dicts.pop('_address')

        return json.dumps(dicts, ensure_ascii=False, indent=4)
myclass = MyClass('kuzunoha')

print(myclass.to_json())

{
    "name": "kuzunoha",
    "gender": "male",
    "interest": [
        "Kemono Friends",
        "DoDonpachi",
        "Python"
    ],
    "age": 111
}

【Docker】Windowsで「curl」コマンド使いたい件について[byrnedo/alpine-curl]

お世話になっております。葛の葉です。

WindowsPowerShellにはInvoke-WebRequestというLinuxBASHでいうところのcurlみたいなコマンドがついていて、何かHTTP系に色んなテストをしてみたいときなんかは有効です。しかし、curlに慣れていたりするとコマンドが打ちにくい、結果もなんだかわかりにくい等、curlがやっぱり使いたい、ってときがあります。

そんなときに便利なDockerImageがbyrnedo/alpine-curlです。

hub.docker.com

使い方は書いてある通り

先のURLにも書いてありますが…まぁ書いておきます。

docker pull byrnedo/alpine-curl

先ず、DockerImageをダウンロードするため、上記のコマンドを打ちます。-tオプションでタグを指定するのもよいと思いますが、まぁ気にしなくてもいいかなぁと思います。ダウンロードが終われば使用可能です。

docker run --rm byrnedo/alpine-curl (url)

このコマンドでHTTPのrequestを確認することができます。

なお、dockerの--rmオプションは、コンテナを通してコマンドを実行し、そのコマンドが完了したときにそのコンテナを消す、というオプションになります。curlコマンドは何回も使うはずなので、そのたびにコンテナのカスが溜まっていくことを防いでいるわけですね。

docker run --rm byrnedo/alpine-curl https://www.google.co.jp

Googleのサイトをcurlで受け取ればソースコードを取得できます。あとはcurlの操作そのまま使えるので調べて使ってみてみてくださいな。

なお、Win版のcurlもある模様

下記URLにあります。一番下のほうにWindows64bit用のリンクがあるので、それを落としてbinフォルダ以下に環境変数PATHを通してあげましょう。PowerShellではcurlコマンドがInvoke-WebRequestにaliasされているので、curl.exeってコマンドになるか、あれこれ設定をしてあげなきゃあいけません。詳しくはググってもらったほうがいいかな。

curl.haxx.se

参考

qiita.com

【Python】Scikitlearnの内LinearRegressionを使った値予測を行う

こんばんは、葛の葉です。機械学習ライブラリであるsklearnを使ってデータの学習と予測…と言いたいところなんだけど、なんだかいいデータがなくて、全く無造作なデータを学習させて予測するという意味のないことやってました。

環境

Anaconda 5.3.0

Python 3.7.0

numpy 1.15.1

pandas 0.23.4

jupyter 1.0.0

scikit-learn 0.19.2

適当なデータを使う。

tekito.csv

data1,data2,data3
22,62,73
22,66,61
76,58,26
35,60,43
35,30,100
61,35,97
50,62,48
87,94,86
95,33,34
35,58,32
79,84,20
82,15,92
50,66,37
62,89,74
53,51,65
74,80,75
89,27,80
100,62,78
97,77,79
88,37,54

data1,data2,data3は共に15から100の数値を適当出力する設定にしているだけなので、なんも意味を持ちません。

重回帰分析を使う

回帰分析のうち、目的変数に対して説明変数が2つ以上のものを重回帰分析といい、説明変数が1つの場合は単回帰分析と言うみたいです。

目的変数とは予測したいデータで今回はdata3です。

説明変数とは目的変数を予測するために使うデータのことdata1,data2です。

data1data2を使ってdata3を予測するというのが今回の目的です。

回帰分析については以下のWikipediaも参照。

回帰分析 - Wikipedia

説明変数のデータ

test.csv

data1,data2
35,77
60,43
45,53
64,65
81,21
90,33
100,46
50,18
30,32
29,60

このtest.csvdata1,data2を使ってdata3に相当するデータを出力する。

JupyterNoteBookを使う

以下はJupyterNoteBook上でのコマンドになります。

ライブラリのインポート

import pandas as pd
import numpy as np # 要らないかも
from sklearn.linear_model import LinearRegression as LR

上記コードをセルに挿入してからShift + Enterすると次のセルに移ります。

その時に何らかのエラーが出ればそれはライブラリのインポートが失敗していると思います。

from sklearn.linear_model import LinearRegression as LR

これが機械学習のライブラリsklearnです。

LinearRegressionは線形回帰を意味しているようです。

as LRとしてLinearRegressionLRとして略称します。

CSVのインポート

pandasでcsvをロードします。

train = pd.read_csv('tekito.csv')
test = pd.read_csv('test.csv')

pandas.read_csv('***.csv')はpandasのライブラリとしてcsvをpandasのDataFrameとして読み込みます。

Shift + Enterで、次のセルへ。

エラーを吐く場合は事前に説明していたCSVが、同じディレクトリ内にないとか、その辺りだとおもいます。

train.head()

.head()は読み込んだDataFrameの最初の5行分を出力します。

data1    data2   data3
0   22  62  73
1   22  66  61
2   76  58  26
3   35  60  43
4   35  30  100

Shift + Enterで、次のセルへ。

特定のカラムのデータを読み取る

trainX = train[['data1', 'data2']]
y = train['data3']
testX = test[['data1','data2']]

train内の2つのカラムを代入しています。

train[['data1', 'data2']]

yは目的変数としtrain['data3']を代入しています。

testXは最終的に予測するための説明変数をもっています。

trainXyを使ってdata1,data2data3で線形回帰を行い、最終的にはtestX内にあるdata1,data2を元にtestXのdata3に当たるデータを出力します。

Shift + Enterで、次のセルへ。

モデルの作成とモデルへの学習

model = LR()
model.fit(trainX, y)

model.fit(trainX, y).fit(説明変数, 目的変数)となっており、回帰モデルとなっています。

trainXdata1data2をもっているので重回帰モデルとなっています。

Shift + Enterで、次のセルへ。

学習したデータを出力する

pred = model.predict(testX)
pred

以下のデータが学習で出したデータになります。

array([55.80356749, 65.91988259, 62.42089327, 60.75553676, 72.7960059 ,70.4459384 , 67.91714972, 71.41497867, 66.58486472, 59.5950097 ])

さっぱり意味がないデータですけどね。

Shift + Enterで、次のセルへ。

ついでにCSVとして出力しましょう。

tester = pd.DataFrame()
tester[0] = pred
tester.to_csv('cho_tekitou.csv', header=None, index=None)

.to_csv関数にてcho_tekitou.csvという名前で出力します。

ここまできていて言うのもなんだが

data1data2は無造作なデータで、当然、法則性なんてないです。なので、最終的なpredの値はとても意味のないものになるはずです。

またpredが正確にデータを予言できているかどうか、というのも保証できません。

とりあえず、データを出せてよかったねってレベルで今回は終わりです。

f:id:Kuzunoha-NE:20190104222556p:plain

【Python】JupyterNotebook + matplotlibで折れ線グラフを出力する

今回はAnaconda + matplotlibを使って折れ線グラフを出力します。

プログラミングの環境

Anaconda 5.3.0

Python 3.7.0

numpy 1.15.1

pandas 0.23.4

matplotlib 2.2.3

jupyter 1.0.0

使うデータ

日本が統計を開始してから2017年までの交通事故の統計データ

政府統計の総合窓口

www.e-stat.go.jp

前準備

データ分析によく使われるpandas numpy matplotlibをライブラリとしてインストールしておく必要あります。

pandasライブラリはpythonの標準の配列機能より強力なものを使えます。

numpyライブラリは計算機能が強い。

matplotlibライブラリは図を表示するもの。

以上のライブラリが必要になります。

なお、今回はAnaconda5.3.0環境でのVersionになります。

Anacondaなら最初から3つのライブラリは入っています。

データを加工する

もともと入っているデータではちょっと表示がやりづらそうだったのでこんな感じで変更

和暦 =>(西暦に変換して) => AD
発生件数(件) =>  Incidents
負傷者数(人)=>  Injured_person
死者数(人)=>  Casualties

それ以外のものは削除しました。

これのデータをCSVとして変更しました。これらは

名前はh29_jiko.csvです。

AD,Incidents,Injured_person,Casualties
1948,21341,17609,3848
1949,25113,20242,3790
1950,33212,25450,4202
。。。。

こんな感じ

jupyter notebookコマンドにてjupyter notebookを起動しましょう。

(JupyterNotebookについてはまた今度)

最初のライン

import pandas as pd
import numpy as np
from matplotlib import pyplot as plt
%matplotlib inline

%matplotlib inline はJupyterNote上でmatplotlibを表示するための設定です。

上記のコードを入力した状態でShift + Enterを押します。

ライブラリがうまくはいっていれば、問題なく次の行に進むはず。

2つ目のライン

jiko29 = pd.read_csv('h29_jiko.csv')

pd.read_csv()pandascsvファイルを読み込む関数です。

h29_jiko.csvという名前でCSVファイルを同じディレクトリに保管しているので、このデータ呼び出し方で呼べます。

Shift + Enterでそのコードを実行。

3つ目のライン

plt.plot(jiko29['AD'],jiko29['Incidents'])

Shift + Enterでコード

f:id:Kuzunoha-NE:20190102205248p:plain
事故件数の折れ線グラフ

と、このように簡単に折れ線グラフを出力できます。

2000年頭くらいから2010年くらいまでの事故数が増えて、それから右肩下がり?のようです。

3つ目のラインを編集

plt.plot(jiko29['AD'],jiko29['Injured_person'])をグラフ内に追加します。(負傷者数)

plt.plot(jiko29['AD'],jiko29['Incidents'])
plt.plot(jiko29['AD'],jiko29['Injured_person'])

f:id:Kuzunoha-NE:20190102210735p:plain
事故件数+負傷者数の折れ線グラフ

このように、データを重ねて表示することも可能です。

一回の事故に対して、負傷者が増えたってこと…であってるのかな?

4つ目にやりたかったけど出来なかったこと

死亡者数のデータを出力しようとすると…

plt.plot(jiko29['AD'],jiko29['Incidents'])
plt.plot(jiko29['AD'],jiko29['Injured_person'])
plt.plot(jiko29['AD'],jiko29['Casualties'])

f:id:Kuzunoha-NE:20190102220340p:plain
事故件数+負傷者数+死者数

緑色のグラフが死者数…だけど、さっぱりデータがわからない。

f:id:Kuzunoha-NE:20190102220438p:plain
死者数

このデータだけ見てみると経緯がわかりやすい。

死者数はだんだんと減っていっている傾向にある、ということ。

二つ軸グラフが使えればデータわかりやすかったんだと思うけど、ちょっと今はわからない。

また調べてやってみよう。

【Python】FlaskのJinja2と別でimportしたJinja2を使って文字の置換を二回行う

こんばんは、葛の葉です。

Flaskの文字置換を二段階にわけて行いたい場合の方法を書きます。

環境

python 3.6.5

flask 1.0.2

jinja2 2.10

やり方の説明

元のhtml -> custom_jinja -> flaskのjinja2 できれいにする。

custom_jinjaでは::で挟んだ変数を読み込む

flaskのjinja2では{{}}で挟んだ変数を読み込む

jinja2とjinja2.Environmentを使う

flask内のjinja2とはのjinja2を使用する。

その別のjinja2を便宜的にカスタムjinjaと呼ぶことにする。

jinja2.Environment()内の引数にある以下の値を変更、それをカスタムjinjaというインスタンスとして扱う。

variable_start_stringは変数を囲う最初の文字

variable_end_stringは変数を囲う最後の文字

custom_jinja = jinja2.Environment(
    loader=jinja2.FileSystemLoader('templates'),
    variable_start_string=':',
    variable_end_string=':'
)

説明が前後したけど、loader=jinja2.FileSystemLoader('templates')はtemplateファイルを読み込むディレクトリ名をtemplatesにしています。

page = custom_jinja.get_template('index.html')

と、このようにすることで、templateファイルを読み込めます。

render_template = pages.render(variable_name='value')

と、することでvariable_nameの変数をtemplateファイルに送ることができます。

こんな感じ

sandbox_flask.py
templates/
    ├index.html
    └layout.html

sandbox_flask.py

from flask import Flask, render_template, render_template_string
import jinja2

app = Flask(__name__)

custom_jinja = jinja2.Environment(
    # 元htmlの保存されているディレクトリ名。flaskと併用するので以下の値。
    loader=jinja2.FileSystemLoader('templates'),

    #デフォルトでは{%にあたるもの。flaskのjinjaと重複しない値にする。
    block_start_string='[{[{[Q',
    #デフォルトでは%}にあたるもの。flaskのjinjaと重複しない値にする。
    block_end_string='Q]}]}]', 
    
    # このvariable...と続くステータスを挟みたい文字にする。
    variable_start_string=':',
    variable_end_string=':',

    # オートエスケープは特殊文字等をエスケープします。
    autoescape=False)


# 定数のようなもの。:Key: -> Value にしてくれる。
SPECIAL_CONVERSION_CHARACTER = {
    'test': '!TesT!'
}
# SPECIAL_CONVERSION_CHARACTERが不要ならここの処理も不要
custom_jinja.globals.update(SPECIAL_CONVERSION_CHARACTER)


@app.route("/")
def hello():

    # index.htmlを元にhtmlを生成します。
    page = custom_jinja.get_template('index.html')

    template = page.render(name='サンプルタイガー')

    return render_template_string(template, name='サンプルドラゴン', title='Titleだよ!!!')

if __name__ == "__main__":
    app.run(debug=True)

layout.html

<!doctype html>
<html>

<head>
    <meta http-equiv="content-type" charset="utf-8">
    <title>{{ title }}</title>
</head>

<body>
    {% block content %}
    {% endblock %}
</body>

<footer>
</footer>

</html>

index.html

{% extends "layout.html" %}
{% block content %}

<p>
    :':name:':=:name:<br>
    {{ '{{ name }}' }}={{ name }}<br>
</p>

:test:

{% endblock %}

表示はこんな感じ

:name:=サンプルタイガー
{{ name }}=サンプルドラゴン
!TesT!

f:id:Kuzunoha-NE:20181228230746p:plain

STR型を変換する

bunsho = ':one:である :two:なのだ'

以下のような辞書を参照してAである Bなのだという文字列にしたい場合

{'one': 'A','two': 'B'}

importやcustom.jinjaなどの記載省略
...

@app.route("/")
def hello():
...省略...

@app.route("/x")
def xstring():

    string = custom_jinja.from_string(':one:である :two:なのだ ')
    strings = string.render(
        {
            'one': 'A',
            'two': 'B'
        }
    )
    return render_template('strings.html', strings=strings)
...

strings.html ( {{ strings }}と書かれているだけのhtml)

{{ strings }}

出力するとこうなる

Aである Bなのだ

出来なかった個所

key名が数字のみ場合は参照されない。

    string = custom_jinja.from_string(':1:である :2:なのだ ')
    strings = string.render(
        {
            '1': 'A',
            '2': 'B'
        }
    )
    return render_template('strings.html', strings=strings)

-> 1である 2なのだ