#stapy #glnagano 第15回みんなのPython勉強会リモート中継 in GEEKLAB.NAGANO に参加しました

3/8にギークラボ長野で開かれた「【音質・画質UP】みんなのPython勉強会リモート中継 in GEEKLAB.NAGANO #15」に参加しました。
【音質・画質UP】みんなのPython勉強会リモート中継 in GEEKLAB.NAGANO #15 - connpass

「みんなのPython勉強会 #22」の東京会場を中継する形での勉強会でした。

資料などは以下のページにまとまっています。
みんなのPython勉強会#22 - connpass

今回もいろいろな分野のお話があって盛り沢山でした。

 

私のPython学習奮闘記#7 〜Webアプリケーション編〜

阿久津剛史さん(Start Python Club)

Bottleを使って作成したWebアプリケーションについて、公開したソースコードに対する丁寧な解説がありました。

ソースコードが公開されているので、Bottleで何か作りたいときは参考にしようと思いました。

また、参考書籍もいろいろと紹介され、初学者に優しい構成でした。

 

WindowsPython

drillerさん

WindowsPython環境について、いろいろと詳しく解説されていました。

Windowsではvirtualenvで切り替えるくらいしかやっていなかったため、PowerShellプロファイルを利用した環境切り替えが参考になりました。

また、Pythonディストリビューションのうち、Intelのものがあると初めて知り、印象に残りました。

あと、WindowsにおけるPythonのつらいところには共感しました。

 

アジャイルってなにが美味しいの?

やっとむ(安井力)さん

何をするために取り入れるのかなど、アジャイルについてのそもそもなところから解説していただけたTalkでした。

印象に残ったのは、

  • 変更容易性という難しいことを実現する仕組みを作ることは困難だが、それを実現する人は作れる
  • チームで学習すると、より多くのことを学べる

というあたりです。

また、Pythonとは関係ないTalkでもOKというところに、勉強会の懐の深さを感じました。

 
最後になりましたが、開催・運営をされたみなさま、ありがとうございました。

特に、Python勉強会の中継のおかげで長野にいても学べるのが、本当にありがたいです。

 
なお、3/18にはみんなのPython勉強会が長野市にやってきます。
みんなのPython勉強会 in 長野 #1 - connpass

Pythonで、MagicMockのreturn_valueを使って、モックから別のモックを返してみた

Pythonにて、「モックから別のモックを返す」テストコードを作成する機会がありました。

そこで、以下を参考に作成した時のメモを残します。

 
目次

 

環境

  • Python 3.6.0
    • unittest.mock.MagicMockを使用
  • pytest 3.0.6
    • テストランナーとして使用

 

状況

Targetクラスのrun()メソッドについて、戻り値の辞書に正しく値が設定されているかを確認します。

target.py

from cook import Cook

class Target:
    def run(self, material):
        # 引数materialは作るのに手間がかかるオブジェクト
        cook = Cook(material)
        cuisine = cook.bake()
        return {
            'name': 'maindish',
            'cuisine': cuisine.get_name(),
        }

 
Cookクラスのbake()メソッドでは、さらに別のクラス(Ham, Spam, Egg)のインスタンスを返します。

cook.py

from cuisine import Ham, Spam, Egg

class Cook:
    def __init__(self, material):
        self.__material = material

    def bake(self):
        try:
            # 実際には、materialの中身によって複雑な処理がある
            if material:
                return Ham()
            else:
                return Spam()
        except:
            # なかなかEggを出すデータを作れないとする
            return Egg()

 
Ham, Spam, Eggの各クラスは、それぞれget_name()メソッドを持っています。

cuisine.py

class Ham:
    def get_name(self):
        return 'ham'

class Spam:
    def get_name(self):
        return 'spam'

class Egg:
    def get_name(self):
        return 'egg'

 
そんな中で、Target.run()の戻り値を検証しようとしましたが、

  • get_name()で取得するオブジェクトは、run()の引数materialに依存する
  • 引数material用のオブジェクトを生成するのは難しい

と、このままではテスト作成に手間がかかりそうでした。

 

対応

そこで今回は、unittest.mock.MagicMockを使って2つモックを作ることで、引数material用のオブジェクトを生成しなくても済むようにします*1

 
1つは、戻り値としてeggを返すget_name()メソッドを持つモックです。

<MagicMockオブジェクト>.<差し替えたいメソッド名>.return_valueに、戻り値eggをセットします。

from unittest.mock import MagicMock

def test_bake():
    # cuisineのモックを作る
    mock_cuisine = MagicMock()
    # get_name()メソッドは、'egg'を返すように指定
    mock_cuisine.get_name.return_value = 'egg'

 
もう1つは、bake()メソッドを持ち、上記で作成したモックmock_cuisineを返すモックです。このモックはCookクラスと差し替えるのに使います。

import cook

def test_bake():
    ...
    # bake()メソッドでcuisineのモックを返す、モックを作る
    mock_cook = MagicMock()
    # bake()メソッドは、上記で作ったモック'mock_cuisine'を返すように指定
    mock_cook.bake.return_value = mock_cuisine
    # Cookクラスはモックを返すモックに差し替える
    cook.Cook = mock_cook

 
準備ができたため、あとはテストコードを書きます。

from target import Target

def test_bake():
    ...
    sut = Target()
    # runの引数materialは、本来は作るのが面倒
    # 今回はモック向けなので、何かあれば良い
    actual = sut.run('dummy')
    assert actual['cuisine'] == 'egg'

 
実行してみます。

$ python -m pytest
==== test session starts ====
platform darwin -- Python 3.6.0, pytest-3.0.6, py-1.4.32, pluggy-0.4.0
rootdir: /Users/kamijoshinya/thinkami/try/python_mock_sample, inifile: pytest.ini
collected 1 items 

test_get_mock_object.py .

==== 1 passed in 0.07 seconds ====

Target.run()メソッドの戻り値が差し替わり、テストがpassしました。

 

ソースコード

GitHubにあげました。e.g._get_mock_objectディレクトリの中が今回のファイルです。
thinkAmi-sandbox/python_mock-sample

*1:他の解決方法があるかもしれませんが、今回はモックから別のモックを返すための例なので…

Python + pytestで、monkeypatch.setattr()を使ってみた

pytestでは、monkeypatchを使ってmockを作成できます。

 
今回は、monkeypatch.setattr()を使って、

  • プロダクションコードのメソッドや関数
  • Python標準モジュールのメソッド

を差し替え(mock化)してみた時のメモです。

 
目次

 

環境

 

使い方

プロダクションコードのメソッドを差し替え

プロダクションコード

class Target(object):
    CONST_VALUE = 'foo'

    def get_const(self):
        return self.CONST_VALUE

get_const()メソッドを差し替えるには、

  • ダミーメソッドを用意して差し替え
  • lambdaを使って差し替え

のどちらかを使います。

# ダミーメソッドを用意して差し替える場合
# pytestのテストコードの引数に、`monkeypatch`を追加
def test_patch_by_dummy_function(self, monkeypatch):
    # run_dummyというダミーメソッドを用意し、この中で差し替える戻り値を設定
    # Target.get_constは引数selfがあるため、差し替えメソッドでも引数(arg)を用意
    def run_dummy(arg):
        return 'ham'
    
    # monkeypatch.setattr()を使って差し替え
    # 第一引数:importしたプロダクションコードのクラス
    # 第二引数:差し替え対象のメソッド
    # 第三引数:差し替えたダミーメソッド
    monkeypatch.setattr(Target, 'get_const', run_dummy)
    sut = Target()
    actual = sut.get_const()
    assert actual == 'ham'


# lambdaを使って差し替える場合
def test_patch_by_lambda(self, monkeypatch):
    expected = 'ham'
    # ダミーメソッドを用意する手間を省くために、lambdaを使っても良い
    # lambdaでも引数は必要(今回の場合、`x`)
    monkeypatch.setattr(Target, 'get_const', lambda x: expected)
    sut = Target()
    actual = sut.get_const()
    assert actual == expected

 

標準ライブラリを差し替え

プロダクションコード

import platform

class Target(object):
    def get_platform(self):
        return platform.system()

で使われている標準モジュールplatform.system()の戻り値をhamという文字列に差し替えるには、

import platform
class Test_target:
    def test_patch_standard_library(self, monkeypatch):
        monkeypatch.setattr(platform, 'system', lambda: 'ham')

とします。

 

差し替え対象モジュールをimportせずに差し替え

モジュールをimportせず、モジュール名を文字列で渡しても差し替えが可能です。

例えば、テストコードでimport osせずに、os.sysmtem()を差し替える場合は、

def test_patch_no_import(self, monkeypatch):
    monkeypatch.setattr('os.system', lambda: expected)

OKです。

 

定数の差し替え

プロダクションコード

class Target(object):
    CONST_VALUE = 'foo'

の定数CONST_VALUEを差し替えます。

定数なので、lambdaを使う必要はないです。

def test_const(self, monkeypatch):
    monkeypatch.setattr(Target, 'CONST_VALUE', expected)

 

複数の引数を持つメソッドを差し替え

プロダクションコード

class Target(object):
    def get_multi_args(self, foo, bar):
        return ''.join([foo, bar])

の複数の引数を持つget_multi_args()メソッドを差し替えます。

同じ数の引数か可変長引数をlambdaに渡します。

def test_function_multi_args1(self, monkeypatch):
    # 同じ数の引数を渡す
    monkeypatch.setattr(Target, 'get_multi_args', lambda x, y, z: expected)

def test_function_multi_args2(self, monkeypatch):
    # 可変長引数を渡す
    monkeypatch.setattr(Target, 'get_multi_args', lambda *_: expected)

 

プライベートメソッドの差し替え

プロダクションコードに面倒なプライベートメソッド__run_complex()があり、テストではこのプライベートメソッドで何も処理してほしくないとします。

class Target(object):
    def call_private_function(self):
        self.__run_complex()
        return self.CONST_VALUE

    def __run_complex(self):
        # 何行も続いて、最後にうまくいってないときはraiseする
        # さらに戻り値は返さないという、面倒なプライベートメソッド
        raise Exception

 
この場合、プライベートメソッドをマングリングした表現で指定し、Noneを返すように差し替えます。

def test_patch_private_function(self, monkeypatch):
    monkeypatch.setattr(Target, '_Target__run_complex', lambda x: None)

 

複数の戻り値を持つメソッドの差し替え

プロダクションコード

class Target(object):
    def get_multi_return_values(self):
        return ('foo', 'bar')

の複数の戻り値を持つget_multi_return_values()メソッドを差し替えるには、戻り値をタプルなどで返します。

def test_patch_multi_return_values(self, monkeypatch):
    monkeypatch.setattr(Target, 'get_multi_return_values', lambda x: ('ham', 'spam'))

 

例外を送出するように差し替え

プロダクションコード

class Target(object):
    def raise_exception(self):
        raise RuntimeError('foo')

で例外RuntimeErrorを送出するraise_exception()メソッドを、別の例外AssertionErrorを送出するように差し替えます。

この場合、ジェネレータ式のthrow()を使ったlambdaのワンライナーを使います。

def test_patch_exception(self, monkeypatch):
    monkeypatch.setattr(Target, 'raise_exception', lambda x: (_ for _ in ()).throw(AssertionError('ham')))
    sut = Target()
    # 差し替えた例外AssertionErrorが発生したかをチェック
    with pytest.raises(AssertionError) as excinfo:
        sut.raise_exception()
    assert 'ham' == str(excinfo.value)

 

プロダクションコードがimportしているモジュールの属性を差し替え

プロダクションコード(target.py)で

# target.py
import outer_import
class Target(object):
    def get_outer_import(self):
        return outer_import.FOO

# outer_import.py
FOO = 'baz'

のように、outer_import.pyをimportしているouter_import.FOOを差し替えます。

方法は、ここまで見てきたものと同じです。  

def test_patch_import(self, monkeypatch):
    expected = 'ham'
    monkeypatch.setattr(outer_import, 'FOO', expected)
    sut = Target()
    actual = sut.get_outer_import()
    assert actual == expected

 

プロダクションコードがfrom … importしているモジュールの属性を差し替え

前項と異なり、プロダクションコード(target.py)で

# target.py
from outer_from_import import BAR
class Target(object):
    def get_outer_from_import(self):
        return BAR

# outer_from_import.py
BAR = 'baz'

のように、outer_from_import.pyをfrom ... importしているouter_from_import.BARを差し替えます。

今まで通り、

def test_patch_from_import(self, monkeypatch):
    expected = 'ham'
    monkeypatch.setattr(outer_from_import, 'BAR', expected)
    sut = Target()
    actual = sut.get_outer_from_import()
    assert actual == expected

としても差し替わらず、以下のエラーとなります。

E       assert 'baz' == 'ham'
E         - baz
E         + ham

 
from ... importした場合には、モジュール名前空間が変わるためです。
参考:そんなpatchで大丈夫か? (mockについてのメモ〜後編〜) - Qiita

そこで、monkeypatch.setattr('target.BAR', expected)のように差し替えます。

def test_patch_from_import(self, monkeypatch):
    expected = 'ham'
    monkeypatch.setattr('target.BAR', expected)
    sut = Target()
    actual = sut.get_outer_from_import()
    assert actual == expected

 

標準出力の差し替え

プロダクションコード

class Target(object):
    def write_stdout(self):
        print('foo')

write_stdout()で標準出力へ出力している内容を差し替えます。

この場合、pytestのcapsysを併用して差し替えと検証をします。
Capturing of the stdout/stderr output ? pytest documentation

def test_patch_stdout(self, monkeypatch, capsys):
    # pytestのcapsysを併用した例
    # http://doc.pytest.org/en/latest/capture.html
    def print_dummy(arg):
        print('ham')
    # python3ではprintは式なので、lambdaに指定可能
    monkeypatch.setattr(Target, 'write_stdout', lambda x: print('ham'))
    # python2ではprintは文なので、lamdbaに指定できないことから、ダミー関数を使う
    # monkeypatch.setattr(Target, 'write_stdout', print_dummy)
    sut = Target()
    actual = sut.write_stdout()
    actual, _ = capsys.readouterr()
    assert actual == 'ham\n'

 
なお、プロダクションコードが標準出力へ文字コードcp932などで出していた場合、

class Target(object):
    def write_cp932_stdout(self):
        print('ほげ'.encode('cp932'))

差し替えてもうまく動作しません。

def test_stdout_cp932(self, capsys):
    sut = Target()
    actual = sut.write_cp932_stdout()
    actual, _ = capsys.readouterr()
    assert actual == 'ほげ\n'.encode('cp932')
    # E assert "b'\\x82\\xd9\\x82\\xb0'\n" == b'\x82\xd9\x82\xb0\n'
    # E  +  where b'\x82\xd9\x82\xb0\n' = <built-in method encode of str object at 0x101bb4990>('cp932')
    # E  +    where <built-in method encode of str object at 0x101bb4990> = 'ほげ\n'.encode

 

関数の差し替え

プロダクションコード

def standard_function():
    return 'standard'

の関数standard_functionを差し替えます。

import functions
def test_standard_function(self, monkeypatch):
    expected = 'ham'
    monkeypatch.setattr(functions, 'standard_function', lambda: expected)
    actual = functions.standard_function()
    assert actual == expected

 

プライベート関数の差し替え

プロダクションコードの

def __private_function():
    return 'private'

プライベート関数(先頭にアンダースコアが2つある関数)である__private_function()を差し替えます。

この場合、以下を組み合わせて差し替えます。

from functions import __private_function as pf
def test_private_function(self, monkeypatch):
    expected = 'ham'
    monkeypatch.setattr(sys.modules[__name__], 'pf', lambda: expected)
    actual = pf()
    assert actual == expected

 

sys.exc_infoの差し替え

pytestが内部で使っているようで、

def test_do_not_patch_exc_info(self, monkeypatch):
    expected = ('ham', 'spam', 'egg')
    def patch_exc_info(arg):
        return expected
    monkeypatch.setattr(sys, 'exc_info', patch_exc_info)
    sut = Target()
    actual = get_sys_exc_info()

とすると、実行ログで

  • テストは通る
  • 以下のエラーが出て、pytestの結果が乱れる

となります。そのため、差し替えないほうがいいようです。

test_monkeypatch.py ...............
========================== 15 passed in 0.09 seconds ===========================
...
Traceback (most recent call last):
  TypeError: patch_exc_info() missing 1 required positional argument: 'arg'
  During handling of the above exception, another exception occurred:

 

datetimeの差し替え

datetimeの差し替えはfreezegunを使うと便利です。
spulec/freezegun: Let your Python tests travel through time

 
ただ、pytestだけで差し替えたい場合は、以下が参考にして差し替えます。

def test_patch_datetime(self, monkeypatch):
    expected = datetime.datetime.now()
    class PatchedDatetime(datetime.datetime):
        @classmethod
        def now(cls):
            return expected
    monkeypatch.setattr('datetime.datetime', PatchedDatetime)
    sut = Target()
    actual = sut.get_current_datetime()
    assert actual == expected

 
なお、datetime.datetimeはC拡張の組込型です。

そのため、

def test_cannot_patch_datetime(self, monkeypatch):
    expected = datetime.datetime.now()
    monkeypatch.setattr('datetime.datetime.now', lambda: expected)
    sut = Target()
    actual = sut.get_current_datetime()
    assert actual == expected

としても、エラーとなります。
built-in object を拡張する禁断の果実を齧ろう - Qiita

E TypeError: can't set attributes of built-in/extension type 'datetime.datetime'

 

ソースコード

GitHubに上げました。e.g._monkeypatchディレクトリの中が今回のファイルです。
thinkAmi-sandbox/python_pytest-sample

 
なお、プロダクションコードとテストコードは、同じディレクトリに入れてあります。

そのため、実行時PYTHONPATHを通すために、python -m pytestで実行します。
python - PATH issue with pytest ‘ImportError: No module named YadaYadaYada’ - Stack Overflow

Macにrabbitをインストールし、プレゼンテーマを自作してみた

Macにプレゼンツールのrabbitをインストールし、プレゼンテーマを自作した時のメモを残します。
公式サイト:Rabbit - はじめに

 
目次

 

環境

 

rabbitの動作環境を構築

rbenvのインストール

以下を参考に、システムのRubyではなくrbenvのRubyを使う環境を構築します。

$ brew install rbenv ruby-build
...
🍺  /usr/local/Cellar/ruby-build/20170201: 334 files, 178.8K, built in 13 seconds
...
🍺  /usr/local/Cellar/rbenv/1.1.0: 36 files, 63.2K

 
rbenvの設定をします。

# rbenv initを実行
$ rbenv init
# Load rbenv automatically by appending
# the following to ~/.bash_profile:

eval "$(rbenv init -)"


# 上記メッセージに従って、.bash_profileを編集する
$ vi ~/.bash_profile
export PATH="$HOME/.rbenv/bin:$PATH"
eval "$(rbenv init -)"

# .bash_profileを再読込
$ source .bash_profile

 

rbenvを使ったRubyのインストール

一番新しい2.4.0をインストールします。

# インストール可能なバージョンの確認
$ rbenv install -l
...
2.4.0
...

# Rubyのインストール(時間かかる)
$ rbenv install 2.4.0
...
Installed ruby-2.4.0 to ~/.rbenv/versions/2.4.0

# バージョン確認
$ rbenv versions
  system
* 2.4.0 (set by ~/.rbenv/version)

# rehashする 
$ rbenv rehash

# バージョンを指定
$ rbenv global 2.4.0
$ ruby --version
ruby 2.4.0p0 (2016-12-24 revision 57164) [x86_64-darwin15]

# 念のためどこにインストールされているかを確認
$ rbenv which irb
~/.rbenv/versions/2.4.0/bin/irb

 

Bundlerを使ってrabbitをインストール

rabbitはGemfileでインストールしたいため、以下を参考にBundlerまわりをインストールします。
Bundlerの使い方 - Qiita

 

Bundlerのインストール

no-documentオプションを付け、ドキュメントを除いてインストールします。
gemrcの–no-riと–no-rdoc、deprecatedなoptionなのでみなおしたほうがいいかもですよ - Qiita

# Bundlerのインストール
$ gem install bundler -​-no-document
...
Successfully installed bundler-1.14.4
1 gem installed

 

Gemfileの作成とrabbitのインストール

rabbit入れたいディレクトリへと移動して、Gemfileを作成します。

# ディレクトリを作って移動
$ mkdir rabbit_theme_sample
$ cd rabbit_theme_sample/

# Gemfileのひな形を作る
$ bundle init
Writing new Gemfile to /path/to/rabbit_theme_sample/Gemfile

 
作成されたGemfileにrabbitをインストールするように記載します。

Gemfile

# frozen_string_literal: true
source "https://rubygems.org"

gem "rabbit"

 
準備ができたため、bundlerを使ってインストールします。

--path vendor/bundleでインストール先を指定します。

# インストール(時間がかかる)
$ bundle install --path vendor/bundle
Fetching gem metadata from https://rubygems.org/........
Fetching version metadata from https://rubygems.org/.
Resolving dependencies...
Installing pkg-config 1.1.7
Installing coderay 1.1.1
Installing multipart-post 2.0.0
Installing locale 2.1.2
Installing text 1.3.1
Installing hikidoc 0.1.0
Installing kramdown 1.13.2
Installing mini_portile2 2.1.0
Installing rdtool 0.6.38
Installing rttool 1.0.3.0
Using bundler 1.14.4
Installing glib2 3.1.1 with native extensions
Installing cairo 1.15.5 with native extensions
Installing faraday 0.11.0
Installing gettext 3.2.2
Installing nokogiri 1.7.0.1 with native extensions
Installing atk 3.1.1 with native extensions
Installing gobject-introspection 3.1.1 with native extensions
Installing pango 3.1.1 with native extensions
Installing gio2 3.1.1 with native extensions
Installing gdk_pixbuf2 3.1.1
Installing gtk2 3.1.1 with native extensions
Installing poppler 3.1.1 with native extensions
Installing rsvg2 3.1.1 with native extensions
Installing rabbit 2.2.0
Bundle complete! 1 Gemfile dependency, 25 gems now installed.
Bundled gems are installed into ./vendor/bundle.

 

rabbit-themeコマンドによるひな形を作成

rabbitには、既存のテーマがいくつかあります。

 
ただ、今回は以下を参考にして、defaultテーマを拡張する形で作成します。

 
既存のテーマのソースコードを読むと、defaultテーマのincludeに対し、

  • include前に、インスタンス変数へ値をセット
  • include後に、オブジェクトに対して、prop_set()を実行

などを行えば、defaultテーマを拡張できそうでした。

   
まずは、rabbit-themeコマンドを使って、テーマのひな形を作成します。

コマンドではidの指定が必要なので、今回はsample-themeとします。

# rabbit-themeコマンドで生成
$ bundle exec rabbit-theme new --id sample_theme
[情報]
ディレクトリを作成中: sample_theme
[情報]
ディレクトリを作成中: sample_theme/data
[情報]
ファイルを作成中:     sample_theme/.gitignore
[情報]
ファイルを作成中:     sample_theme/config.yaml
[情報]
ファイルを作成中:     sample_theme/README.rd
[情報]
ファイルを作成中:     sample_theme/Rakefile
[情報]
ファイルを作成中:     sample_theme/property.rb
[情報]
ファイルを作成中:     sample_theme/theme.rb
[情報]
ディレクトリを作成中: ~/.rabbit
[情報]
ファイルを作成中:     ~/.rabbit/author.yaml


# ディレクトリへと移動
$ cd sample_theme/

# テーマファイルの中身を確認
## defaultテーマのincludeだけがされている
$ cat theme.rb 
include_theme("default")

 

確認用スライドを準備

スライドがないとテーマを確認しづらいため、markdown形式のスライドを用意しました。

slide.md

# スライドタイトル

subtitle
:   サブタイトル

date
:   2017/2/28

author
:   著者

allotted-time
:   10s

# リスト

* リスト1段目
  * リスト2段目
    * リスト3段目


# 画像サイズはそのまま表示
![](images/shinanogold.png){:height='80' :width='80'}

# 画像サイズは調整して表示
![](images/shinanogold.png){:relative_height='80' :relative_width='80'}

 
この時点のスライドを表示してみます。

$ bundle exec rabbit slide.md -t theme

スライドタイトル

f:id:thinkAmi:20170301081648p:plain

スライド

f:id:thinkAmi:20170301081701p:plain

 

タイマー(カメ)について

スライドに

allotted-time
:   10s

と、allotted-timeを設定しているため、、タイマー(カメ)が出ています。

f:id:thinkAmi:20170301124052p:plain

 

テーマの編集

スライド全体のフォントを変更

デフォルト値は、base.rbテーマにあります。
https://github.com/rabbit-shocker/rabbit/blob/master/lib/rabbit/theme/base/base.rb

 
今回は、以前も使用したことのあるやさしさゴシックボールドへ変更します。
フリーフォントやさしさゴシックボールドのダウンロード | フォントな

フォントは公式サイトに従ってインストールします。
Mac OS Xのフォントのインストール | フォントな

 
スライドで使うには、find_font_family()にフォント名を渡し、インスタンス変数font_familyへセットすれば良さそうでした。

ただ、やさしさゴシックボールドの英字表記のフォント名が分かりませんでした。

そこで、以下を参考に調べたところ、07YasashisaGothicBoldということが分かりました。
Mac ハンドブック:Font Book - Apple サポート

そのため、

@font_family = find_font_family("07YasashisaGothicBold")

include_theme("default")

と実装します。

テーマの変更を確認するため、起動中のスライド上でrキーを押します。
Rabbit - rabbit-themeコマンドの使い方

フォントが変わりました。

f:id:thinkAmi:20170301082028p:plain

 

スライドタイトルを変更

rabbitでは、スライドタイトルとそれ以降のスライドでは別設定になっているため、まずはスライドタイトルを変更します。

 

書体を変更

デフォルト値は、default-title-text.rbテーマにあります。
https://github.com/rabbit-shocker/rabbit/blob/master/lib/rabbit/theme/default-title-text/default-title-text.rb

今回は、日付(Date)の書体をイタリック体から通常へと変更します。

...
include_theme("default")

match(TitleSlide, Date) do |dates|
  dates.prop_set("style", "normal")
end

 
Dateの書体が変わりました。

f:id:thinkAmi:20170301123407p:plain

 

表示を右詰めに変更

デフォルト値は、default-title-slide.rbテーマにあります。
https://github.com/rabbit-shocker/rabbit/blob/master/lib/rabbit/theme/default-title-slide/default-title-slide.rb

今回は、著者(Author)を中央揃えから右詰めへと変更します。

なお、horizontal_centering = falseにしてからでないと、alignの値が反映されないことに注意します。

...
include_theme("default")

match(TitleSlide, Author) do |authors|
  authors.horizontal_centering = false
  authors.align = :right
  authors.margin_top = @space * 15
end

 
著者の表示が右詰めになりました。

f:id:thinkAmi:20170301123605p:plain

フォントサイズを変更

デフォルト値は、default-title-text.rbテーマにあります。
https://github.com/rabbit-shocker/rabbit/blob/master/lib/rabbit/theme/default-title-text/default-title-text.rb

今回は

@title_slide_title_font_size = screen_size(1 * Pango::SCALE)
@title_slide_subtitle_font_size = screen_size(2 * Pango::SCALE)
@title_slide_author_font_size = screen_size(3 * Pango::SCALE)
@title_slide_date_font_size = screen_size(4 * Pango::SCALE)

include_theme("default")

としたところ、フォントサイズが変更されました。

f:id:thinkAmi:20170301123645p:plain

 

スライドの設定
フォントサイズを変更

スライドタイトル以外のスライドのフォントサイズを変更します。

設定やデフォルト値は、

にあります。

今回は

# 共通で使われるフォントサイズなので、主に使われるところをコメント
# ヘッドライン
@large_font_size = screen_size(1 * Pango::SCALE)
# スライド内で一般的に使われるサイズ(リストの1段目とか)
@normal_font_size = screen_size(2 * Pango::SCALE)
# リストの2段目
@small_font_size = screen_size(3 * Pango::SCALE)
# リストの3段目
@x_small_font_size = screen_size(4 * Pango::SCALE)
# 旗の大きさ
@xx_small_font_size = screen_size(5 * Pango::SCALE)

include_theme("default")

としたところ、通常スライドのフォントサイズが変更されました。

f:id:thinkAmi:20170301123714p:plain

 

ヘッドラインを変更

デフォルト値から、

  • 太さ
  • センタリング

を変更します。

デフォルト値は、

にあります。

今回は

# 色
@default_headline_line_color = "#0066FF"
# 太さ
@default_headline_line_width = 10

include_theme("default")

# ヘッドラインのセンタリング
match(Slide, HeadLine) do |headlines|
  headlines.each do |headline|
    # trueはセンタリング、falseは左詰め
    headline.horizontal_centering = true
  end
end

としたところ、ヘッドラインの設定が反映されました。

f:id:thinkAmi:20170301123737p:plain

 

箇条書きリストのビュレットを変更

ビュレットのデフォルト値から、

を変更します。

 
デフォルト値はdefault-item-mark.rbテーマにあります。 https://github.com/rabbit-shocker/rabbit/blob/master/lib/rabbit/theme/default-item-mark/default-item-mark.rb

今回は

# 形
@default_item1_mark_type = "rectangle"  # 四角
@default_item2_mark_type = "circle"     # 円
@default_item3_mark_type = "dash"       # 直線

# 色
# 16進数表記のほか、以下のような色名も使える
# http://noz.day-break.net/webcolor/colorname.html
@default_item1_mark_color = "#FF0000"   # red
@default_item2_mark_color = "#009900"   # green
@default_item3_mark_color = "green"

include_theme("default")

としたところ、箇条書きリストでビュレットが変更されました。

f:id:thinkAmi:20170301123750p:plain

 

画像を表示

画像を表示するには、

を行います。

今回は  
theme.rb

# theme.rbと同じ階層にあるimagesディレクトリを画像ディレクトリとする
add_image_path("images")
...
include_theme("default")

 
slide.md

# そのまま表示
![](images/shinanogold.jpg){:height='80' :width='80'}

# いい感じに調整
![](images/shinanogold.jpg){:relative_height='80' :relative_width='80'}

 
としたところ、以下のように表示されました。

そのまま表示する場合

f:id:thinkAmi:20170301123823p:plain

 
いい感じに調整して表示する場合

f:id:thinkAmi:20170301123834p:plain

 
なお、

# 2つの画像は表示できるか?
![](images/shinanogold.png){:height='80' :width='80'}
![](images/shinanogold.png){:height='80' :width='80'}

のように、一つのスライドに複数の画像を指定した場合、以下のログが出て、スライドが表示されません。

[警告]
ひとつの段落中で複数の「![alt]{image}」を使うことはできません。

複数の画像を1ページの中に入れるのは無理そうです。

 

その他

その他いろいろ機能があるようですが、とりあえずはこのへんにします。
Rabbit - テーマの作り方

 

スライドをpdfへ変換

以下を参考に、作成したスライド(slide.md)をpdf(slide.pdf)へ変換します。
Debianでプレゼンテーションツールrabbitを使用する – ぺけみさお

$ bundle exec rabbit slide.md -t theme -p -o slide.pdf

 

ソースコード

GitHubに上げました。
GitHub - thinkAmi-sandbox/rabbit_theme-sample

  • slide.md
  • theme.rb
  • slide.pdf

などが含まれます。

デブサミ2017の二日目に参加しました #devsumi

2/17にデブサミ2017に参加しました。
Developers Summit 2017 エンジニアとして生きる、技術の先にある現実に踏み出す

セッション資料は以下で公開されています。
デブサミ2017、講演関連資料まとめ:CodeZine(コードジン)

ここでは自分の感想やメモを残しておきます。誤りがあったらすみません。

 
目次

 

会場まわり

DevBooks

今年は書籍の他、技術系同人誌も販売されました。今までと異なり、通路ではなく一つの部屋を使っていたため、ゆっくり見られて良かったです。

また、気になった同人誌、

があったため、購入しました。

 

Ask the Speakerコーナー

今まで部屋にあったものが、今年は会場出てすぐに設営されていました。

気軽に質問できる距離感で、スピーカーの方への質問が初めてできました。

 

資料の公開について

タイムテーブルに資料が公開されるかが書かれていたため、資料の公開があるセッションは話を聞くことに集中できました。

 

【17-A-1】HoloLens とMixed reality がもたらす新しい世界

高橋 忍 氏 [日本マイクロソフト]

AR・VR・MRにガンガン手を出している同僚がいることもあり、自分もその雰囲気を知ろうと思って参加しました。

冒頭、

  • 外部イベントでの公開は初
  • 時間がないので詳細は毎月やっている別のセミナーで

とのことでしたが、HoloLensとMixed realityの雰囲気をつかむのには十分でした。

デモでは

  • 現実世界の床を認識して、仮想世界のボールが転がる
  • 現実世界の机を認識して、仮想世界のオブジェクトが机に隠れる
  • 空間にExcelを投影して、内容を確認する
  • 出張先で色々なオブジェクトを置けば、殺風景でなくなる

など、現実との境目がなくなりつつあるんだなと感じました。

 
HoloLensのスペックなどで印象に残ったのは、

  • ケーブルレスなコンピュータ, 単体で動く
  • シースルーレンズに投影
  • 電源は2時間くらい
  • ファンレスなので、静かに動作
  • OSはWindows10
    • Windows Updateもあるので、気をつけないと実行されてしまう
  • 光と光を重ね合わせて表現する
    • そのため、減光ができず、黒の表現ができない(黒は透明になる)
    • 炎天下では使用しづらい
  • 開発者用と商用では、HoloLensのハードは同じ
  • 2DはUWPアプリ、3Dは C++ & DirectX、もしくはVS2015 & Unity
  • アプリはWindowsストアアプリに出す

などです。

 
また、Ask the Speakerコーナーで、

を聞いたり、人数が集まればデモなどもとのことでした。

HoloLens、実機で見てみたいです。

 

【17-A-2】Re: ゼロから文化を創り、技術を伝承する ~客先常駐エンジニアと「社内勉強会」で築いた価値と変化

須賀 俊介 氏 [VSN]

社内勉強会がない会社から活発に行う会社への変化に関するセッションでした。

印象に残ったのは、

  • デブサミやコミュニティへの参加したのを受けて、社内でもやろうとした姿
  • 社内勉強会を支援する予算を用意すること
  • いわゆるIT系以外、エレクトロニクス系でも勉強会が発生

でした。

過去の諸先輩方の努力のおかげで勉強会があるのは、本当にありがたいと改めて感じました。

 

【17-B-L】エンジニアのキャリア形成における、フリーランスの位置付けとは

水野 竜与志氏、小山 哲志 氏 [ほげ技研]、林 英司 氏 [レバレジーズ]

フリーランスのお二方を交えた座談会形式のセッションでした。

印象に残ったのは、

  • 税金などの手続きは、色々なセミナーに参加して勉強
  • 発信する人に情報が集まる

でした。

話を聞く中で、一人でやるのは大変そうだと感じました。

 

【17-E-3】コンテナをフル活用した現場

坂部 広大 氏 [Wantedly,Inc]

Dockerをさわり始める中で、どのようにコンテナを活用しているのか気になり、参加しました。

前半は「The Twelve-Factor App」に関する話、後半は社内の課題解決のためにKubernetesを導入し、その後に起きた変化の話でした。

印象に残ったのは、

  • 2人でインフラを支えている
  • インフラ担当を権威化しない姿
    • リリースまで時間がかかっていた原因を一つずつ潰す
    • アプリ担当者でも簡単にリリースできるようにする
  • 必要なツールは自作し、OSSとして公開
  • 「インフラエンジニアは、運用保守ではなく、積極的にコードを書いて全体を最適化する」という意識

でした。

また、The Twelve-Factor AppやKubernetesについては、あとで調べてみようと思いました。

 

【17-E-4】【GitHub Enterpriseユーザ企業登壇!】企業文化にイノベーションを起こすモダンなソフトウェア開発環境とは?

根本 竜也 氏[マクニカネットワークス]、近藤 圭太 氏[レコチョク]、伊澤 好也 氏[フォーク]

レコチョク・フォークの2社について、

  • 導入前の状況
  • 導入で期待したこと
  • 導入後に変わったこと

に関するセッションでした。

印象に残ったのは、

  • お二方ともソースコードレビューがやりやすいとおっしゃっていた
  • フォーク社のトップが一番ノリノリだったおかげで、導入決定が速やかだった
  • G Suite(旧Google Apps) + GitHub EnterpriseでSAML認証している

でした。

 

【17-C-5】コミュニティとエンジニアの生き方(仮)

コミュニティには色々とお世話になっているので、気になって参加しました。

小林 由憲 氏[TickleCode]

いろんな勉強会の開催に関するセッションでした。

印象に残ったのは

  • 自分の望む勉強会がなかったという動機から勉強会を始めた
  • 勉強会の信条(クレド)をしっかり作成し、それに従って勉強会を開催
  • 参加者にスポットライトを当てる
    • 自己紹介の時間あり
    • 初学者や登壇経験のない人の発表を優先

でした。

 

阪田 浩一 氏

今回のデブサミで一番印象に残ったセッションでした。

特に、

  • 勉強会の運営に関わろう、なければ作ろう
    • 関西Javaエンジニアの会
  • いいなと思った勉強会も、何もしないと止まってしまう
    • 止まった後にやってほしかったなと思っても遅い
    • 人にはステージがあるので、交代できるようにしないと続かない
  • コミュニティは貢献で成り立つ
    • 好きな技術を搾り取って捨てるだけで良いのか
  • 「こうなりたい人」との時間と距離を近づけると、自分の普通が変わる
    • すごい人がどう考えどう準備しているのかを見て、自分に取り込む

などが印象に残りました。

また、声も大きくしっかりしていて、とても聞き取りやすいプレゼンでした。

 

【17-D-6】『もしもスクラムマスターがテストエンジニアだったら』(もしテス)

太田 健一郎 氏[SHIFT]、米沢 毅 氏[SHIFT]

テストエンジニアの観点から社内ツールを作成した時の話でした。

印象に残ったのは

でした。

 

【17-A-7】執筆を支える技術と技術書のトレンド

日高 正博 氏、高橋 征義 氏

タイトルが気になって参加しました。

落ち着いた雰囲気の中、Twitterに寄せられた会場の質問にも答えたり、お二人の掛け合いがうまかったりと、会場をうまく巻き込んでいました。

印象に残ったのは

  • 紙と電子書籍の違い
    • 紙は保守的、1-2年持ちそうなのを選ぶ
    • 電子書籍は、あとで更新すればいいかという意識で書ける
  • 同人誌の良いところ
    • 今ある技術を伝えられる
    • 「すごいですね」と直接言われる機会がある
  • 執筆を支える技術としてGit, CI, Dockerを使う
    • 人手だと作業がすぐに止まり、校正などの回転を素早く回せない
  • 書き手は届けたい人を想定する、間違うとお互いに不幸

でした。

 
また、Ask the Speakerコーナーで「著者のサインを電子書籍に頂く方法」を質問しました。

残念ながら今は良い方法がなく、色紙などで代替するしかなさそうでした。そのため、本当にお世話になった本は物理本として用意しておこうかなとも感じました。

 

 
最後になりましたが、今年もいろいろな話を聞くことができました。デブサミの関係者の皆様、ありがとうございました。

Python2で、type()関数を使うと<type 'instance'>が返ってきた

Python2でprintデバッグをした際、インスタンスの型名が知りたくなりました。

type()関数を使ったところ、<type 'instance'>が返ってきたので、これは何だろうと思って調べた時のメモです。
type() | 2. 組み込み関数 — Python 2.7.x ドキュメント

 

環境

 

結果

Python2で古い形式でクラス定義をしていると、type()関数では<type 'instance'>が返ってくるようでした。
python - Why does type(myField) return <type 'instance'> and not <type 'Field'>? - Stack Overflow

 
そのため、<インスタンス>.__class__のように__class__属性を使うのが良いとのことです。
instance.class | 5. 組み込み型 — Python 2.7.x ドキュメント

 
ためしてみます。

type_instance.py

# -*- coding:utf-8 -*-

class Py2OldStyle1:
    pass

class Py2OldStyle2():
    pass
    
class Py2NewStyle(object):
    pass


if __name__ == "__main__":
    print('Python2の古い形式1: {}'.format(type(Py2OldStyle1())))
    print('Python2の古い形式2: {}'.format(type(Py2OldStyle2())))
    print('Python2の新しい形式: {}'.format(type(Py2NewStyle())))
    print('Python2の古い形式1で__class__を使う: {}'.format(Py2OldStyle1().__class__))
    print('Python2の古い形式2で__class__を使う: {}'.format(Py2OldStyle2().__class__))
    print('Python2の新しい形式で__class__を使う: {}'.format(Py2NewStyle().__class__))

 
実行してみます。

# Python 2.xの確認
$ python --version
Python 2.7.13

# 実行
$ python type_instance.py 
Python2の古い形式1: <type 'instance'>
Python2の古い形式2: <type 'instance'>
Python2の新しい形式: <class '__main__.Py2NewStyle'>
Python2の古い形式1で__class__を使う: __main__.Py2OldStyle1
Python2の古い形式2で__class__を使う: __main__.Py2OldStyle2
Python2の新しい形式で__class__を使う: <class '__main__.Py2NewStyle'>

__class__でクラス名を取得できました。

 
ちなみに、Python3.x系でも試してみたところ、

$ python --version
Python 3.6.0

$ python type_instance.py 
Python2の古い形式1: <class '__main__.Py2OldStyle1'>
Python2の古い形式2: <class '__main__.Py2OldStyle2'>
Python2の新しい形式: <class '__main__.Py2NewStyle'>
Python2の古い形式1で__class__を使う: <class '__main__.Py2OldStyle1'>
Python2の古い形式2で__class__を使う: <class '__main__.Py2OldStyle2'>
Python2の新しい形式で__class__を使う: <class '__main__.Py2NewStyle'>

となりました。

 
Python2.xの旧形式の表記は、Python3.xでは新形式の省略形として扱えるので、上記のような結果となりました。
Python class inherits object - Stack Overflow

Docker for Macにて、httpd:alpineのApacheを使ってみた

初めてDockerをインストールし、Alpine LinuxApacheイメージを使ってみた時のメモです。

今回は以下の記事が参考になりました。ありがとうございました。
Dockerコマンドメモ - Qiita

目次

 

環境

 

Docker for Macのインストール

公式サイトから、stableチャンネルのDocker for Macをダウンロード・インストールします。
Get started with Docker for Mac - Docker

 

Visual Studio CodeでDockerfileを書く準備

Dockerfileのシンタックスハイライトなどを使いたいため、拡張Docker Supportを入れます。
Working with Dockerfiles in Visual Studio Code

 

Alpine LinuxApacheイメージをダウンロード

Docker Hubに行くとDocker Storeが案内されますので、そちらの内容に従って作業します。
httpd - Docker Store

 
今回はAlpine Linux版のApacheイメージを使うため、docker pullします。

# ダウンロード
$ docker pull httpd:2.4.25-alpine

2.4.25-alpine: Pulling from library/httpd
...
Status: Downloaded newer image for httpd:2.4.25-alpine


# 確認
$ docker images

REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
httpd               2.4.25-alpine       4fa77c368fa6        2 weeks ago         94.8 MB

 

It Worksを確認

httpd:2.4.25-alpineイメージを使って、Apacheの「It Works」を確認します。

以下のオプションを付けて起動します。

  • pオプション:<ホストのポート>:<dockerのポート>で、ホストとDockerのポートをつなぐ
  • nameオプション:Dockerコンテナに名前をつける
# 起動
$ docker run -p 8081:80 --name it_works httpd:2.4.25-alpine

AH00558: httpd: Could not reliably determine the server's fully qualified domain name, using 172.17.0.2. Set the 'ServerName' directive globally to suppress this message
AH00558: httpd: Could not reliably determine the server's fully qualified domain name, using 172.17.0.2. Set the 'ServerName' directive globally to suppress this message
[xxxx] [mpm_event:notice] [pid 1:tid 140039857982280] AH00489: Apache/2.4.25 (Unix) configured -- resuming normal operations
[xxxx] [core:notice] [pid 1:tid 140039857982280] AH00094: Command line: 'httpd -D FOREGROUND'


# 確認
$ curl http://localhost:8081

<html><body><h1>It works!</h1></body></html>

 
なお、docker runのオプション位置を間違えるとエラーになり起動しません。

$ docker run -p 8081:80 httpd:2.4.25-alpine --name it_works

container_linux.go:247: starting container process caused "exec: \"--name\": executable file not found in $PATH"
docker: Error response from daemon: oci runtime error: container_linux.go:247: starting container process caused "exec: \"--name\": executable file not found in $PATH".

   

ローカルのファイルをコピーして起動

次は、DockerfileのCOPYコマンドを使って、Apacheのドキュメントルートへローカルのファイルをコピーして起動してみます。

Docker storeの記載を見ると、Alpine LinuxApacheのドキュメントルートは/usr/local/apache2/htdocs/でした。

./html/hello.html

<!DOCTYPE html>
<html>
<head>
    <meta lang="ja" />
    <meta charset="utf-8" />
    <title>hello</title>
</head>
<body>
    <p>Hello World</p>
</body>
</html>

 
Dockerfile

FROM httpd:2.4.25-alpine

COPY html /usr/local/apache2/htdocs/

 
ホスト側のディレクトリ構成

path/to/root
├── Dockerfile
└── html\
     └── hello.html

 
準備ができたので、Dockerイメージをビルドし、起動します。

# Dockerfileのあるディレクトリにいることを確認
$ ls -l

-rw-r--r--  1 you  staff   68  xxxx Dockerfile
drwxr-xr-x  3 you  staff  102  xxxx html


# Dockerイメージのビルド
$ docker build -t alpine:hello .

Sending build context to Docker daemon  5.12 kB
Step 1/1 : FROM httpd:2.4.25-alpine
 ---> 4fa77c368fa6
Successfully built 4fa77c368fa6


# Dockerイメージができたことを確認
$ docker images

REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
alpine              hello               0118b8a13aeb        18 hours ago        94.8 MB
httpd               2.4.25-alpine       4fa77c368fa6        2 weeks ago         94.8 MB


# 起動
docker run -p 8082:80 --name hello_world alpine:hello

 
別のコンソールでドキュメントルートにhello.htmlが存在すること、レスポンスが返ってくることを確認します。

# ドキュメントルートを確認
$ docker exec hello_world ls -al /usr/local/apache2/htdocs

total 16
drwxr-xr-x    1 root     root          4096 xxxx .
drwxr-xr-x    1 www-data www-data      4096 xxxx ..
-rw-r--r--    1 root     root           160 xxxx hello.html
-rw-r--r--    1 501      dialout         45 xxxx index.html


# レスポンスを確認
$ curl http://localhost:8082/hello.html

<!DOCTYPE html>
<html>
<head>
    <meta lang="ja" />
    <meta charset="utf-8" />
    <title>hello</title>
</head>
<body>
    <p>Hello World</p>
</body>
</html>

 

ホストとコンテナでディレクトリを共有

docker runのvオプションを使うことで、ホストとコンテナでディレクトリの共有ができるようです。
DockerのVolume機能について実験してみたことをまとめます - Qiita

そこで、新しくhtmlファイルを作成し、そのディレクトリをコンテナのドキュメントルートで共有してみます。

<!DOCTYPE html>
<html>
<head>
    <meta lang="ja" />
    <meta charset="utf-8" />
    <title>shared</title>
</head>
<body>
    <p>Shared!</p>
</body>
</html>

 
ホストのディレクトリ構成

path/to/root
├── Dockerfile
├── html\
│   └── hello.html
└── htdocs\
    └── host.html

 
vオプションで、<ホストのディレクトリ>:<コンテナのディレクトリ>と指定して起動します。

また、$(pwd)を使って、ホストのカレントディレクトリを取得しています。

$ docker run -p 8083:80 -v $(pwd)/htdocs:/usr/local/apache2/htdocs --name share_file alpine:hello

AH00558: httpd: Could not reliably determine the server's fully qualified domain name, using 172.17.0.2. Set the 'ServerName' directive globally to suppress this message
AH00558: httpd: Could not reliably determine the server's fully qualified domain name, using 172.17.0.2. Set the 'ServerName' directive globally to suppress this message
[xxxx] [mpm_event:notice] [pid 1:tid 139810871626568] AH00489: Apache/2.4.25 (Unix) configured -- resuming normal operations
[xxxx] [core:notice] [pid 1:tid 139810871626568] AH00094: Command line: 'httpd -D FOREGROUND'

 
別のターミナルで確認します。

# ドキュメントルートの確認
$ docker exec share ls -al /usr/local/apache2/htdocs

total 8
drwxr-xr-x    3 root     root           102 xxxx .
drwxr-xr-x    1 www-data www-data      4096 xxxx ..
-rw-r--r--    1 root     root           161 xxxx host.html


# 動作確認
$ curl http://localhost:8083/host.html

<!DOCTYPE html>
<html>
<head>
    <meta lang="ja" />
    <meta charset="utf-8" />
    <title>shared</title>
</head>
<body>
    <p>Shared!</p>
</body>
</html>

 
試しにホスト側でhost.htmlファイルを修正してみます。

<!--<p>Share file!</p>-->
<p>Update!</p>

 
Apacheを再起動することなく、再度アクセスしてみます。

$ curl http://localhost:8083/host.html

<!DOCTYPE html>
<html>
<head>
    <meta lang="ja" />
    <meta charset="utf-8" />
    <title>shared</title>
</head>
<body>
    <!--<p>Share file!</p>-->
    <p>Update!</p>
</body>
</html>

 
更新が反映されていました。

 

不要なものを削除

色々と試したので、コンテナやイメージを削除します。
Dockerイメージとコンテナの削除方法 - Qiita

# コンテナの全削除
$ docker rm $(docker ps -a -q)

7adf86edc245
70b091a4b7c6


# 削除されたことを確認
$ docker ps -al

CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES


# イメージの削除
## 削除前の確認
$ docker images

REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
alpine              hello               0118b8a13aeb        18 hours ago        94.8 MB
httpd               2.4.25-alpine       4fa77c368fa6        3 weeks ago         94.8 MB
ubuntu              16.04               104bec311bcd        6 weeks ago         129 MB


## 削除
$ docker rmi 0118b8a13aeb

Untagged: alpine:hello
Deleted: sha256:0118b8a13aebcc3572c7ade1fbffbb3b4f23240327b988bd5e9ef88e280b249d
Deleted: sha256:35611424853777d0ec0de35ef64c8fceb90c60d373f64d4775d301605b04d939


## 削除されたか確認
$ docker images

REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
httpd               2.4.25-alpine       4fa77c368fa6        3 weeks ago         94.8 MB