読者です 読者をやめる 読者になる 読者になる

makefileわりと便利だよという話

makefile memo shell

はじめに

makeはmacにもlinuxにもデフォルトであって便利*1。 とは言え、色々とわかりづらいところがある。 便利なところだけ見繕って使うと便利。

個人的なmakefileの使い方は2種類

個人的には以下のような2つの目的でmakefileを使っている。

  • 複数の目的のためのタスクを記述して手軽に実行できるようにする
  • 1つの目的のためにまじめに依存関係を定義して使う成果物を生成する

後者がたぶん本命だけれど。前者で使うことも多い。

複数の目的のためのタスクを記述して手軽に実行できるようにする

利用するコマンドのオプションを省略したい場合に使う。 脳みそのリソースの削減。細かなオプションの意味など後で見返した時に覚えていないことが多いのでメモがわりにmakefileを書く。

依存関係の書き方は以下の様な感じ。

<タスク名や生成されるファイル>: <依存するタスクや依存するファイル> ...
    <何か実行したい処理>

たいていcleanとdefaultを定義する。記述部分がごちゃっとしてきたら変数にする

SRC = x.txt y.txt

clean:
  rm -f *.out
default:
  python script.py ${SRC} > x.out

デフォルトの設定値(ただし実行時に変えたい)

以下のように実行時に値を渡したい場合がある。

$ make
10!!
# 10ではなく1にしたい
$ FOO=1 make
1!!

このような場合には ?= を使って設定する。実行するコマンド自体のエコー出力を消したい場合には @ をつける。

FOO ?= 10

default:
   @echo ${FOO}!!

phony

makefileにただclean,defaultと書くとそれが生成物としてのファイル名を表しているのか。タスク名を表しているのかわからない。同名のファイルなどがあると困る。なのでphonyを書いておく。

.PHONY clean default

1つの目的のためにまじめに依存関係を定義して使う

一連のタスク依存関係を持ちつつ連動して実行されて欲しい場合にまじめに依存関係を定義する。 対象となるものがファイルの場合には、成果物としてのファイルのutimeよりも依存先のファイルのutimeの方が新しい場合にのみ実行される。便利。

ただしこの目的の為に書く場合には、わりとまじめに使う場合が多いのでその場合はそれぞれの環境のツールを使うという感じでも良い気もする。とは言え、簡単なレベルならmakefileでもわりとどうにかなるので便利なことは便利。

以下のような謎の定義を書いておくと、依存関係にあるファイル拡張子によって実行する処理を記述できる。例えば、.pyの拡張子を持つファイルのpythonを実行した結果をリダイレクトした結果を.py.outに出力するなど。

%.py.out: %.py
  python $< > $@
  
hello.py.out: hello.py

clean:
  rm -f *.py.out

make -p

おもむろに make -p と実行してみると良い。内部で利用できる関係性を見る事ができる。

$ make -p # 一部だけ表示

%.py.out: %.py
#   commands to execute (from `Makefile', line 2):
    python $< > $@

ちなみに 無引数で実行した時に実行されるタスクは DEFAULT_GOALとして表示される。

$ make -p    # 一部だけ表示

# makefile
.DEFAULT_GOAL := hello.py.out

$ make clean
rm -f *.py.out
$ make
python hello.py > hello.py.out
# 2度目の実行はskip
$ make
make: `hello.py.out' is up to date.

シェルの実行結果をつかって定義を記述

シェルの実行結果をつかって定義を記述することもできる。 $(shell ...) を使う。

例えば以下のような状況で。

$ ls
Makefile  bye.py      hello.py

make を実行すると hello.py.outbye.py.out が生成されるようなmakefileは以下。

%.py.out: %.py
  python $< > $@

default: hello.py.out bye.py.out

hello.py.out: hello.py
bye.py.out: bye.py

clean:
  rm -f *.py.out
.PHONY: clean

これが面倒な場合には以下のようにも書ける。関係性が自明なのでhello.py.outなどの記述は省略できる。

%.py.out: %.py
  python $< > $@

default: hello.py.out bye.py.out

clean:
  rm -f *.py.out
.PHONY: clean

さらに依存先部分のhello.py.outなども書きたくない場合には $(shell ...) を使って以下の様にも書ける。 これはシェルスクリプトの実行結果に置き換えるというような意味。

%.py.out: %.py
  python $< > $@

default: $(shell ls *.py | sed 's/$$/.out/g')

clean:
  rm -f *.py.out
.PHONY: clean

注意点として $ は2つ必要(sedの記述は sed 's/$/.out/g' で良いところを)。 markdownで書いたreadmeなどを作ったりするのに便利。

よく分からない自動変数

今まで放置してきた謎の変数について紹介。以下の様になっている。

  • $@ 出力対象(生成されるファイル)の名前
  • $^ 入力対象(依存されるファイル)の名前全部
  • $< 入力対象の内1つ目のものの名前

おそらくよく使うのはこの辺の3つ。詳細を知りたければ このあたり

上の変数などを使って、出力結果をmarkdownでまとめてみたreadmeを作ってみる。

%.py.out: %.py
  python $< > $@

readme.md: $(shell ls *.py | sed 's/$$/.out/g')
  rm -f tmp
  touch tmp
  for i in $^; do echo $$i >> tmp; echo '```' >> tmp; cat $$i >> tmp; echo '```' >> tmp; done
  mv tmp $@

clean:
  rm -f *.py.out
  rm -f readme.md
  
.PHONY: clean

実行結果をまとめたreadmeを生成。

$ make
python bye.py > bye.py.out
python hello.py > hello.py.out
rm -f tmp
touch tmp
for i in bye.py.out hello.py.out; do echo $i >> tmp; echo '```' >> tmp; cat $i >> tmp; echo '```' >> tmp; done
mv tmp readme.md

$ cat readme.md
 bye.py.out
 ```
 bye
 ```
 hello.py.out
 ```
 hello
 ```

makefileわりと便利だよという話。

*1:macもdefaultでgnu makeっぽい

[python][logging] pythonのloggingモジュールの呼び出し元のファイル名:行番号を出力する機能が残念という話

はじめに

pythonの標準ライブラリのloggingモジュールを利用すると、ログ出力の中に呼び出し元の位置に関する情報(ファイル名や関数名など)を含める事ができる。 具体的にはloggerに渡すフォーマットに以下を含めることで、ログ出力時に呼び出した位置の情報が出力される。

  • %(pathname)s ログ出力が行わる実行コードが存在するファイルのパス名。
  • %(funcName)s ログ出力が行われた際のコンテキスト上の関数名。
  • %(lineno)s ログ出力が行われたファイルの行番号

便利で素晴らしい。一方で、ちょっとloggingモジュールのloggerに細工をしようと思った時にこの機能が上手く動作しなくなって悲しかったという話。

loggingモジュールので呼び出し元の位置をログに含める事ができる

例えば以下のようなファイル構造の時に、hello.pyで定義された hello() 上のログ出力にはhello.py中で呼び出されたということが分かる。

$ tree .
.
├── hello.py
└── main.py

hello.py

import logging
logger = logging.getLogger("*")


def hello():
    logger.info("hello")

main.py

import logging
import hello

format = "level:%(levelname)s\tmsg:%(message)s\tlocation:%(pathname)s(%(lineno)s)\tfn:%(funcName)s"
logging.basicConfig(level=logging.DEBUG, format=format)

hello.hello()

具体的には以下のようなログが出力される。

level:INFO   msg:hello   location:~/work/hello.py(6) fn:hello

期待通りhello.py上で実行されたものだということが分かる。一方で、loggerに対してもう少し機能を付加したいとloggerオブジェクトをwrapするようなオブジェクトを定義したときにこの機能が上手く動作しなくなる

元のloggerをwrapしたオブジェクトを作ってみる

例えば、何らかの処理を追加したいと思い。直接的にloggingモジュールのloggerを使うのではなく、loggerを内部に保持したオブジェクトを作りたいと思うことがある。以下のようなもの。

wrap.pyでLoggerWrapperを定義し、hello.pyではこのオブジェクトで包んだオブジェクトを利用してログ出力をしようとするように、コードを変えてみる。

wrap.py

class LoggerWrapper:
    def __init__(self, logger):
        self.logger = logger

    def info(self, *args, **kwargs):
        # do something
        self.logger.info(*args, **kwargs)

hello.py

import logging
import wrap


logger = logging.getLogger(__name__)
wlogger = wrap.LoggerWrapper(logger) 


def hello2():
    wlogger.info("hello")

この時にも、hello2() 上のログ出力の位置としてファイル名がhello.pyになってほしい。ところがそうはならない。LoggerWrapper上のLoggerWrapper.info() の位置がログ出力されてしまう。

level:INFO   msg:hello   location:~/work/wrap.py(7)  fn:info

原因

実行時の位置情報について期待していない結果が出力されてしまう原因はloggerの定義を見てみると分かる。例えばpython3.5.2上のloggingモジュールのコードを見ると以下の様になっている。

if hasattr(sys, '_getframe'):
    currentframe = lambda: sys._getframe(3)
else: #pragma: no cover
    def currentframe():
        """Return the frame object for the caller's stack frame."""
        try:
            raise Exception
        except Exception:
            return sys.exc_info()[2].tb_frame.f_back


## snip..
class Logger(Filterer):

    ## snip..
    def findCaller(self, stack_info=False):
        """
        Find the stack frame of the caller so that we can note the source
        file name, line number and function name.
        """
        f = currentframe()
        #On some versions of IronPython, currentframe() returns None if
        #IronPython isn't run with -X:Frames.
        if f is not None:
            f = f.f_back
        rv = "(unknown file)", 0, "(unknown function)", None
        while hasattr(f, "f_code"):
            co = f.f_code
            filename = os.path.normcase(co.co_filename)
            if filename == _srcfile:
                f = f.f_back
                continue
            sinfo = None
            if stack_info:
                sio = io.StringIO()
                sio.write('Stack (most recent call last):\n')
                traceback.print_stack(f, file=sio)
                sinfo = sio.getvalue()
                if sinfo[-1] == '\n':
                    sinfo = sinfo[:-1]
                sio.close()
            rv = (co.co_filename, f.f_lineno, co.co_name, sinfo)
            break
        return rv

内部の詳細は省略するとして、ログ出力の位置の構造を取得するのに、実行時のstackframeの情報を利用している。 この際に取得するstackframeのレベルが3で固定になってしまっているので、実行時に呼び出される logger.findCaller() の深さが変わってしまうようなコードを書いてしまうと上手く位置が取得できなくなってしまう(loggingモジュールのloggerは、何かのオブジェクトにwrapしたりすることはせずに直接使う必要が有るということ)。

かなしい。

もちろん以下のようにログ出力部分を関数で包んでしまっても上手く行かない。

def log_with_additional_info(message, *args, **kwargs):
    logger.info("additional: {}".format(message), *args, **kwargs)


def hello3():
    log_with_additional_info("hello")

hello3() ではなく log_with_additional_info() 上で使われたということになってしまう。

level:INFO   msg:additional: hello   location:~/work/hello.py(14)    fn:log_with_additional_info

もちろん、両者は共に loggingモジュールのloggerの logger.info() が呼ばれた位置 であることには違いないのだけれど。

以下の様なことができたらなーという感じ。

# 位置情報を取得する際にsys._getframe(3) ではなく sys._getframe(4)が呼ばれるようになる
logger.setFindCallerDepth(4)

gist https://gist.github.com/podhmo/056213a4cd51c2e611b963b36ae9cdbb

pythonのinteractive shellを終了した後 `stty sane` が必要だった問題が解決したという話

mac macports python

はじめに

手元の環境でpythonのinteractive shellを利用した後、コンソールで入力した文字列のエコー表示がされなくなる問題に悩まされていた。

$ python3.5
Python 3.5.2 (default, Sep 19 2016, 02:49:52) 
[GCC 4.2.1 Compatible Apple LLVM 7.3.0 (clang-703.0.31)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> ^D>>> 
$ # ここで入力した文字列が表示されなくなってしまう
$ stty sane #これで復活

readlineを有効にせずにbuildしていた模様

readlineを有効にせずにpythonをinstallしていた模様。

$ port installed python35
The following ports are currently installed:
   python35 @3.5.2_0 (active)
$ port variants python35
python35 has the variants:
   readline: Use readline instead of libedit
   universal: Build for multiple architectures
$ sudo port clean python35
$ sudo port install python35 +readline
The following ports are currently installed:
  python35 @3.5.2_0
  python35 @3.5.2_0+readline (active)

無事は stty sane は不要になった。

手軽にgo1.6を試す方法考える -> 結局dockerを使うことにした

docker golang

はじめに

期待していない挙動を示した時にそもそもgo1.7とgo1.6とで動作が異なるのではないか?という疑いをもった時があった。 ところで手元の環境は全部1.7でわざわざ1.6をbuildするのも面倒と言うような感じ。 本当に些細なコードだったので、goの1.6用のplaygroundがあればそちらを使いたかったのだけれど。みつけられなかった。

特にvmや他の環境を持っていない場合にどうするのが一番楽なんだろう?という話。

dockerを使うことにした

dockerを使うことが一番ラクかもしれない。以下にdocker用のイメージがある。alpineがたぶんいちばん小さいだろうということでそれを使うことにする。

以下のようなdocker-compose.ymlを書いてあげる。

go1.6:
  image: golang:1.6-alpine
  volumes:
    - "$PWD:/go"

例えば以下の様なコードを実行してみる。

dump-info.go

package main

import (
    "fmt"
    "os"
    "runtime"
)

func main() {
    fmt.Printf("go version: %q\n", runtime.Version())
    pwd, _ := os.Getwd()
    fmt.Printf("cwd: %q\n", pwd)
}

以下の様にして実行できる。

$ docker-compose run go1.6 go run dump-info.go
go version: "go1.6.3"
cwd: "/go"

1.6で動いている。

参考

既存のmethodを壊さずに内部のmethodを差し替えて実行したい

golang

はじめに

例えば、以下のような状況で M.F0() を呼びたい。しかしその内部で呼ばれる m.f()は呼んでほしくない場合。

type M struct {
}

func (m *M) F0() error {
    // do something
    return m.f()
}

func (m *M) F1() error {
    // do something
    return m.f()
}

func (m *M) f() error {
    // この処理を呼びたくない
    panic("don't call!!1")
}

通常のアプリケーションコードとして結合された状態で呼ばれる分には何ら問題無いコードも、テスト中では呼び出したくない場合がある。 例えば、 M.f() が外部のリソースに依存するような処理だった場合など、テスト中では呼び出したくない。

もちろん現状で以下の呼び出しはpanicする。

m.F0() // panic
m.F1() // panic

この時、公開された関数である M.F0() , M.F1() の内部で呼ばれる M.f() の挙動を差し替えたい。

単純なembedではダメ

以下はどう考えてもダメ。

type mockM struct {
    *M
}

func (m *M) f() error {
    // こちらが呼ばれてくれると嬉しいけれど
}

embedにより書き換えたつもりになっているのかもしれないけれど。mockM.F0() の呼び出しで、実質呼ばれるのは内部の M.F0()。 その為、結局 M.f() の方が呼ばれてしまう。

内部の部分を切り分けてembedするのが無難そう

内部の部分を切り分けてembedするのが無難そう。型定義を以下の様に変える。

type mClient interface {
    f() error
}

type M struct {
    mClient
}

内部で利用するclientのinterfaceを定義。 デフォルトではactualMClientの方を利用し、テストの時などはmockedMClientの方を利用する。

type actualMClient struct {
}

func (c *actualMailerClient) f() error {
    // この処理を呼びたくない
}

type mockedMClient struct {
}

func (c *mockedMClient) f() error {
    // こちらが呼ばれてくれると嬉しい
}

以下のような関数があるとうれしいかも?

func NewM() *M{
    client := actualMClient{}
    m := M{mClient: &client}
    return &m
}

python3.5でpyobjcのコードを動かそうとしたら Symbol not found: _PyObject_REPR

mac macports python

はじめに

python3.5でpyobjcのコードを動かそうとした際に、以下のようなエラーが出てしまっていた。

Symbol not found: _PyObject_REPR

結論からいうとパッケージが古かったので更新した。(3.0.4 -> 3.1.1)

stack overflowで該当する質問

現在の状態

個人的にmacportsを使っている。

$ port installed | grep pyobjc
  py35-pyobjc @3.0.4_0 (active)
  py35-pyobjc-cocoa @3.0.4_0 (active)

portfileの書き換え

pypiを見たら最新は3.1.1だったのでそれを使うように書き換える。pipで単にインストールしても良いけれど。拡張ライブラリが含まれたようなものはその環境固有の方法で管理したほうが良い気がしている。

ここに書いてある作業に従ってportfileを書き換えれば良い

$ cd `port dir py35-pyobjc-cocoa`
$ sudo cp -p Portfile Portfile.orig
# versionの書き換え
$ sudo port edit py35-pyobjc-cocoa
$ sudo port -d checksum py35-pyobjc-cocoa
# distのchecksumの方に書き換える
$ sudo port edit py35-pyobjc-cocoa
$ sudo port -d install py35-pyobjc-cocoa

$ cd `port dir py35-pyobjc`
$ sudo cp -p Portfile Portfile.orig
# versionの書き換え
$ sudo port edit py35-pyobjc
$ sudo port -d checksum py35-pyobjc
$ sudo port -d livecheck py35-pyobjc
# distのchecksumの方に書き換える
$ sudo port edit py35-pyobjc
$ sudo port -d install py35-pyobjc

portfileを書き換えたらインストールする。

versionの書き換え

3.0.4 -> 3.1.1

checksumの書き換え

checksumを実行してわざと失敗させて新しいchecksumの値を取るのが便利。以下のような出力からDistfile ... の部分を取り出す。

Portfile checksum: pyobjc-core-3.1.1.tar.gz rmd160 12ee6d8132c59420c768dafdb2ee73f17ea97cc1
Distfile checksum: pyobjc-core-3.1.1.tar.gz rmd160 20a806aed2a7396ca86ca680060947ff6b48eccb
DEBUG: Calculated (sha256) is 07c4e9c4fa5bec881cd86bf1b7dbcdb306b16567b490d06fa538c0d1b2677d2e
Error: Checksum (sha256) mismatch for pyobjc-core-3.1.1.tar.gz
Portfile checksum: pyobjc-core-3.1.1.tar.gz sha256 a4708886ff7844ff7537c60d14e903014a90b3e3cdbad5e717acfcbe150768b7
Distfile checksum: pyobjc-core-3.1.1.tar.gz sha256 07c4e9c4fa5bec881cd86bf1b7dbcdb306b16567b490d06fa538c0d1b2677d2e

結果

$ port installed | grep pyobjc
  py35-pyobjc @3.0.4_0
  py35-pyobjc @3.1.1_0 (active)
  py35-pyobjc-cocoa @3.0.4_0
  py35-pyobjc-cocoa @3.1.1_0 (active)

hello worldが動くようになった。

goのtext/templateでprivateなattributeを出力しようとするとエラーになるという話

golang

goのtext/templateでprivateなattributeを出力しようとすると、エラーになるという話を知らなかった。

全部public

例えば、以下はOK

type User struct {
    Name    string
    Message string
}
type V map[string]interface{}

func main() {

    const tmpl = `{{.user.Name}}: {{.user.Message}}`

    tpl := template.Must(template.New("mytemplate").Parse(tmpl))

    user := User{Name: "foo", Message: "hello world"}

    if err := tpl.Execute(os.Stdout, V{"user": user}); err != nil {
        fmt.Println(err)
    }
}

一部privateに

これを以下の様にする

@@ -7,19 +7,20 @@
 )
 
 type User struct {
-  Name    string
+   name    string
    Message string
 }
 type V map[string]interface{}
 
 func main() {
 
-  const tmpl = `{{.user.Name}}: {{.user.Message}}`
+   const tmpl = `{{.user.name}}: {{.user.Message}}`
 
    tpl := template.Must(template.New("mytemplate").Parse(tmpl))
 
-  user := User{Name: "foo", Message: "hello world"}
+   user := User{name: "foo", Message: "hello world"}
 

User.NameUser.name にした。

するとエラーになる。

$ go run xxx.go
template: mytemplate:1:7: executing "mytemplate" at <.user.name>: name is an unexported field of struct type interface {}

実際のところドキュメントに書いてあった

template - The Go Programming Language

Template is the representation of a parsed template. The *parse.Tree field is exported only for use by html/template and should be treated as unexported by all other clie

exported/unexportedチェックしているところ

単純に、template部分は別パッケージという扱いで非公開のメンバーにアクセスしようとしたからエラーということっぽい。 reflectionを使っているしruntime時にしかわからないのでruntime error。

text/template/exec.go の以下の部分で

func (s *state) evalField(dot reflect.Value, fieldName string, node parse.Node, args []parse.Node, final, receiver reflect.Value) reflect.Value {

// snip...

    switch receiver.Kind() {
    case reflect.Struct:
        tField, ok := receiver.Type().FieldByName(fieldName)
        if ok {
// snip...
            if tField.PkgPath != "" { // field is unexported
                s.errorf("%s is an unexported field of struct type %s", fieldName, typ)
            }

tFieldは StructField型

reflect/type.go

// A StructField describes a single field in a struct.
type StructField struct {
    // Name is the field name.
    Name string
    // PkgPath is the package path that qualifies a lower case (unexported)
    // field name. It is empty for upper case (exported) field names.
    // See https://golang.org/ref/spec#Uniqueness_of_identifiers
    PkgPath string

    Type      Type      // field type
    Tag       StructTag // field tag string
    Offset    uintptr   // offset within struct, in bytes
    Index     []int     // index sequence for Type.FieldByIndex
    Anonymous bool      // is an embedded field
}

reflect/type.go のあたりでexportされていないfieldに対してはpkgPathを設定しているという感じになっているので。確かにexportedかどうかの判別に使えそう。

func (t *structType) Field(i int) (f StructField) {
// snip..
    if !p.name.isExported() {
        // Fields never have an import path in their name.
        f.PkgPath = t.pkgPath.name()
    }
}