この記事は Qiita に 2018/08/04 に投稿したものです。2020/3/26 移行しました。
目次
print や類似の関数の基本的な使い方から、一緒に使うと便利な関数について紹介したいと思います。
今回はREPLの表示をそれなりにきれいにすることを目標とします。
人の美への追求は際限なく 「IJulia 使えば Markdown も使えるからキレイに出力できる!」と考えるかもしれませんが、掛けた労力に対して得られる成果があまりに微々たるものなので今回は扱いません。1つの数字を MathJax で表示するくらいなら良いのですが配列が絡んでくると地獄です。
例えば "量子力学と言えば Dirac 表記でしょ!" といった軽いノリで 下記のように表示されていたものを
0.7071067811865475 0.0 0.0 0.7071067811865475
MathJaxを使って下記のように表示させようとするのはおすすめしません。地獄を見ます。 $$0.707 | 00 \rangle + 0.707 | 11 \rangle$$
たかが1行の出力のために何故600行以上もコードを書いているんだと虚無感が襲ってきます。
できるようになること
今まで↓のように表示させていたものが
Name Age Point Carmen.Blanco 21 11.7303 Corrales.Cristobal 21 1.5121000000000002 Wendolin.Urías 30 1.1905999999999999 Nazario.Roberto 43 2.7626 zSantacruz 47 6.9193999999999996 Sisneros.Emilio 30 2.5084 Delia.Galarza 31 19.1949 Collado.Tomás 43 10.206900000000001 Abraham80 50 7.512499999999999 wAlonso 24 4.7661
↓のように表示させることができるようになります。
Name Age Point Carmen.Blanco 21 11.7303 Corrales.Cristobal 21 1.5121 Wendolin.Urías 30 1.1906 Nazario.Roberto 43 2.7626 zSantacruz 47 6.9194 Sisneros.Emilio 30 2.5084 Delia.Galarza 31 19.1949 Collado.Tomás 43 10.2069 Abraham80 50 7.5125 wAlonso 24 4.7661
自作の型の表示が↓から
Horse("ハリボテエレジー", "手作好太郎", "ダンボウルガクエン", "ガムテイプマツリ", 6, [8, 8, 8, 8, 8, 8, 8, 8, 8, 8 … 8, 8, 8, 8, 8, 8, 8, 8, 8, 8])
↓になります。
Horse 競走馬名: ハリボテエレジー 騎手 : 手作好太郎 父馬 : ダンボウルガクエン 母馬 : ガムテイプマツリ 馬齢 : 6 着順 8 8 8 ⋮ 8 8
一見すると簡単に出来そうですが、この記事の長さが物語っているようにこの程度の出力を得るだけでもすごーく面倒です。
環境
julia> versioninfo() Julia Version 1.0.0 Commit 5d4eaca0c9 (2018-08-08 20:58 UTC) Platform Info: OS: Linux (x86_64-pc-linux-gnu) CPU: Intel(R) Core(TM) i5-4460T CPU @ 1.90GHz WORD_SIZE: 64 LIBM: libopenlibm LLVM: libLLVM-6.0.0 (ORCJIT, haswell) Environment: JULIA_SHELL = /usr/bin/zsh JULIA_EDITOR = nvim
今回使うJulia は安定版の v1.0 です。Julia v0.6 とは文法がほとんど違うので注意!
基本
他のプログラミング言語よろしく、print を使うと文字列や変数を画面に出力することができます。
ここで println を使うと最後に改行が入ります。
julia> print("Hello World") Hello World julia> println("Hello World"); println("Hello goropikari") Hello World Hello goropikari
各変数をカンマ区切りで渡すと連結されて出力されます。
julia> print("Hello", 1, "World") Hello1World
printと似たもので show もありますが、こちらは print よりも変数について時として詳しく表示されます。
julia> x = 10 10 julia> println(x) 10 julia> show(x) 10 julia> x = Float16(1) Float16(1.0) julia> println(x) 1.0 julia> show(x) Float16(1.0)
print と show はちょいちょい表示のされ方が違うので要注意。
julia> print("Hello\n") Hello julia> show("Hello\n") "Hello\n"
出力先を明示的に指定しなかった場合、結果は標準出力に出力されます。出力先を指定したい場合は print の第一引数に出力先を指定します。
# 標準出力 julia> println(stdout, "Hello World") Hello World # 標準エラー出力 julia> println(stderr, "Hello World") Hello World
JuliaにはC言語スタイルで print するためのマクロ @printf が用意されています。
これを使うとC言語と同じ様に print することができます。
julia> using Printf julia> @printf("Hello%010d", 12345) Hello0000012345
print や @printf に似たものに sprint と @sprintf があります。
違いは何かと言えば頭に s がついているほうは print したものを画面に表示するのではなく文字列として返します。
julia> using Printf julia> @sprintf("Hello%04d", 3) "Hello0003" julia> @sprintf("Hello%04d", 3); # 標準出力に出しているわけではないので # ; をつけると何も表示されない
@sprintf の方は比較的直感的です。一方で sprint の方はどうでしょう?
julia> sprint("Hello") ERROR: MethodError: no method matching sprint(::String) Closest candidates are: sprint(::Function, ::Any...; context, sizehint) at strings/io.jl:97 Stacktrace: [1] top-level scope at none:0
ありゃま、エラーが出ました。@sprintf と違い sprint には第一引数に関数を与えなければなりません。また、与える関数は第一引数に IO を受け取れるようなものに限ります。例えば print や show など。
julia> sprint(print, "Hello") "Hello" julia> sprint(print, 12345) "12345"
IOContext
凝った出力をするため、また、出力結果を期待通りにするために IOContext の理解は避けては通れないのでここで紹介します。
IOContext は簡単に言えばどこにどう出力するかをまとめたものです。
"どこ" には標準出力や標準エラー出力、書き込み可能なファイルなど。 "どう" はコンパクトに表示する、省略して表示する、色をつけて表示するなどなどです。
基本文法は
IOContext(io::IO, key1 => value1, key2 => value2, ...)
です。ここで key の部分は自分で決めることができますが、以下のよく使われているものに関しては同じ名前をつけると不具合が出る可能性があるので他の名前をつけましょう。
- :compact 値をコンパクトに表示するか否か
- :limit 表示を省略するか否か。Juliaでは配列は省略されて表示されますがそれはこれが
trueになっているから。 - :displaysize 表示部分のサイズ。
- :color 出力の文字に色をつけるか否か
- :typeinfo 私は使ったことがないのでよくわかりません。すいません。
showを使ったときには表示が変わるなぁということだけ確認しました。
julia> show(Float16(1)) Float16(1.0) julia> show(IOContext(stdout, :typeinfo => Float16), Float16(1)) 1.0
例えば、コンパクトに表示させたときとそうでないときの差は以下のとおりです。
julia> using Random; Random.seed!(2018); x = rand() 0.6545394330653942 julia> io = IOContext(stdout, :compact => true) IOContext(Base.TTY(RawFD(0x0000000d) open, 0 bytes waiting)) julia> println(io, x) 0.654539 julia> io = IOContext(stdout, :compact => false) IOContext(Base.TTY(RawFD(0x0000000d) open, 0 bytes waiting)) julia> println(io, x) 0.6545394330653942
IO が特定の key を持っているか否かを調べる時は haskeyを使います。
key を持っていたらその値を、持ってなかったら指定したの値を返すようにするには get を使います。
julia> io = IOContext(stdout, :compact => false) IOContext(Base.TTY(RawFD(0x0000000d) open, 0 bytes waiting)) julia> haskey(io, :compact) true julia> haskey(io, :hoge) false julia> get(io, :compact, true) false julia> get(io, :hoge, true) true
普段遣いのときには IOContext はあまり使わないと思いますが、自分のパッケージを作って後述の pretty-print をするときは結構重要になってきます。
特に変数の値でなく、表示のされ方をテストしたいときは IOContext を知らないと、ちゃんとしたテストケースが書けません。
出力をきれいに魅せる
カラフルにする
printstyled を使うと色付きだったりボールド体で出力することができます。
色の指定方法は色の名前を指定するか、0〜255までの整数値で決めることができます。
julia> printstyled("Hello\n", color=:cyan) julia> printstyled("Hello\n", color=:reverse) # 反転 julia> printstyled("Hello\n", color=:underline) # アンダーラインを引く julia> printstyled("Hello\n", color=:light_blue, bold=true) # Julia v0.5 を使ったことがある人には懐かしのスタイル

文字幅
数字を普通に出力すると以下の様に左寄せに出力されますが、時として位の位置を揃えて出力したいと思いますよね。
julia> for i in [1, 10, 100, 1000] println(i) end 1 10 100 1000
このように。
1 10 100 1000
第一の方法は @printf を使う方法です。
julia> for i in [1, 10, 100, 1000] @printf("%4d\n", i) end 1 10 100 1000
しかし私はC言語風の書き方に慣れていないので @printf は使いづらいです。
変数の数が増えてくるとどこがどこに対応しているのかパット見だとわからないので好きになれません。そのため私は lpad を使っています。
julia> for i in [1, 10, 100, 1000] println(lpad(i, 4)) end 1 10 100 1000
lpad は指定した文字数 $n$ よりも文字列 $s$ が短かったら、足りない分だけ $s$ の左に空白を入れた文字列 $s'$ を返します。右に空白を入れる場合は rpad を使います。
第3引数も指定すると空白以外の文字で埋めることができます。0埋めの連番ファイルを作りたいときなどにも便利です。
julia> lpad("Hello", 10) " Hello" julia> lpad("Hello", 10, "-") "-----Hello" julia> lpad(1, 10, "hoge") "hogehogeh1" julia> rpad("Hello", 20) "Hello " julia> rpad("Hello", 20, "-") "Hello---------------" julia> lpad(1, 6, "0") "000001"
小数・複素数の表示をきれいにする Base.alignment
表示させたいものが整数ならば lpad を使えば位を揃えて表示することが出来ました。次は小数や複素数をきれいに表示させてみましょう。
配列を作った時、小数だったら位の位置を揃えて、複素数だったら実部と虚部を結ぶ符号の位置が揃って表示されますが、それを自力で実現するための方法を紹介します。
julia> x = randn(3) * 100 3-element Array{Float64,1}: -99.60800864461076 28.202047839771822 -158.385848352147 julia> randn(ComplexF64, 3) * 100 3-element Array{Complex{Float64},1}: -11.13643420355874 + 21.75267947789508im 41.913378452353996 - 56.76768974241606im 83.73402073531838 + 0.15488964757358356im
完全に自力でやろうとすると整数部が何桁で小数部が何桁、実部が何桁で虚部が何桁・・・とやらないといけないわけですが、幸いそれらをやってくれる関数があります。それが Base.alignment です。
ちなみに Base.alignment はマニアックな関数なので Julia の公式ドキュメントに使い方は書いてありません。あまりに誰も使わないせいか docstring も2年近く間違ったまま放置されています。それくらいマニアックです。
Base.alignment の基本文法
Base.alignment(io::IO, x)
Base.alignment に IO と数字を入れると要素数2のタプルが返ってきます。もし入れた数字が小数ならば第1要素は整数部の桁数、第2要素は点を含めた小数部の桁数になります。
print や show と違い、 Base.alignment の場合は IO を省略できません。何故かと言えば context (compact とか) によって同じ数字でも表示される桁数が違うからです。
julia> x = rand() 0.6022015583915965 julia> println(IOContext(stdout, :compact => true), x) 0.602202 julia> io = IOContext(stdout, :compact => true) IOContext(Base.TTY(RawFD(0x0000000d) open, 0 bytes waiting)) julia> Base.alignment(io, x) (1, 7) julia> println(IOContext(stdout, :compact => false), x) 0.6022015583915965 julia> io = IOContext(stdout, :compact => false) IOContext(Base.TTY(RawFD(0x0000000d) open, 0 bytes waiting)) julia> Base.alignment(io, x) (1, 17)
それでは、実際に Base.alignment を使って小数をきれいに表示させてみましょう。
方針は配列の要素全てについて Base.alignment で調べ、整数部の桁数の最大値と小数部の桁数の最大値の和を文字幅とするように表示させます。整数部、小数部の足りない分の桁は repeat をつかって空白で埋めます。
julia> x = randn(5) * 10 5-element Array{Float64,1}: -9.212777882398562 -0.6891109738516901 -2.6882962369764787 -36.9082211306946 4.914965667105987 julia> l = r = 0 0 julia> a= [] 0-element Array{Any,1} julia> io = IOContext(stdout, :compact => true) IOContext(Base.TTY(RawFD(0x0000000d) open, 0 bytes waiting)) julia> for i in x atmp = Base.alignment(io, i) global l = max(l, atmp[1]) global r = max(r, atmp[2]) push!(a, atmp) end julia> for (al, item) in zip(a, x) ls = repeat(" ", l - al[1]) rs = repeat(" ", r - al[2]) println(io, ls, item, rs) end -9.21278 -0.689111 -2.6883 -36.9082 4.91497
1つ目の例を再現
ここまでで冒頭の1つ目の例を再現することができます。
julia> # using Pkg; Pkg.add("Faker") julia> using Faker, Random julia> Random.seed!(0); julia> un = Faker.user_name user_name (generic function with 1 method) julia> age() = rand(20:50) age (generic function with 1 method) julia> point() = round(abs(randn()), digits=5) point (generic function with 1 method) julia> n = 10 10 julia> data = Matrix{Any}(undef,n,3); julia> for i in 1:n data[i,1] = un() data[i,2] = age() data[i,3] = point() * 10 end julia> begin name = "Name" a = "Age" p = "Point" io = IOContext(stdout, :compact => true) wn = max(length(name), maximum(length.(data[:,1]))) + 5 wa = 7 l = r = 0 for i in 1:n aij = Base.alignment(io, data[i,3]) global l = max(l, aij[1]) global r = max(r, aij[2]) end println( rpad(name, wn) * rpad(a, wa) * rpad(p, max(10,l+r))) for i in 1:10 aij = Base.alignment(io, data[i,3]) ls = repeat(" ", l - aij[1]) rs = repeat(" ", r - aij[2]) println(io, rpad(data[i,1], wn), rpad(data[i,2], wa), ls, data[i,3], rs) end end Name Age Point Valladares.Cynthia 43 4.7313 Rolando20 49 1.5648 Gabino16 45 4.4951 Gabino.Cepeda 25 9.6989 Isabela.Pulido 35 13.5565 Eloy25 43 25.1382 KGuillen 35 5.1041 Sonia54 39 18.9942 Anabel.Mesa 32 28.9205 nGracia 50 6.3489
配列 Base.print_array
続いて配列の表示についてです。
配列を print してみた時、「思ってたのと違う」と感じたことはないでしょうか?
julia> x = rand(5) 5-element Array{Float64,1}: 0.8564231118379442 0.3957938354126007 0.0803914002136299 0.919405100907382 0.3769282818406279 julia> println(x) [0.856423, 0.395794, 0.0803914, 0.919405, 0.376928] julia> y = rand(5,5) 5×5 Array{Float64,2}: 0.528446 0.948737 0.996994 0.862299 0.675343 0.633248 0.637487 0.34968 0.767471 0.960373 0.868802 0.253233 0.235227 0.709841 0.134395 0.587811 0.637926 0.33784 0.455068 0.180991 0.912758 0.550064 0.916355 0.58957 0.386093 julia> println(y) [0.528446 0.948737 0.996994 0.862299 0.675343; 0.633248 0.637487 0.34968 0.767471 0.960373; 0.868802 0.253233 0.235227 0.709841 0.134395; 0.587811 0.637926 0.33784 0.455068 0.180991; 0.912758 0.550064 0.916355 0.58957 0.386093]
変数を定義した時に表示される方法で出力してほしいのであって、一行にずらずら要素を出力してほしいわけじゃないんだ!!!と思ったことはないでしょうか?
そんな時は show を使います。しかし、そのまま使うと print と大差ありません。 IO と MIME を指定すれば望みの結果が得られます。
REPL のときの MIME は "text/plain" と覚えとけば、とりあえず良いと思います。
julia> println(x) [0.856423, 0.395794, 0.0803914, 0.919405, 0.376928] julia> show(x) [0.856423, 0.395794, 0.0803914, 0.919405, 0.376928] julia> io = IOContext(stdout, :limit => true, :compact => false) IOContext(Base.TTY(RawFD(0x0000000d) open, 0 bytes waiting)) julia> show(io, "text/plain", x) 5-element Array{Float64,1}: 0.8564231118379442 0.3957938354126007 0.0803914002136299 0.919405100907382 0.3769282818406279
さてさて、無事に望みの結果を得ることが出来ましたが、人によってはヘッダー部( 5-element Array{Float64,1}: など)を表示させたくないと望むことでしょう。そんなときは Base.print_array を使います。
ちなみに Base.alignment同様、Base.print_array も公式ドキュメントには使い方が載っていないマニアックな関数です。ただ、alignment と比べて実用性高いのでドキュメントに載せて良いのではと個人的には思います。
Base.print_array の基本文法
Base.print_array(io::IO, x)
julia> io = IOContext(stdout, :limit => true, :compact => false) IOContext(Base.TTY(RawFD(0x0000000d) open, 0 bytes waiting)) julia> Base.print_array(io, x) 0.8564231118379442 0.3957938354126007 0.0803914002136299 0.919405100907382 0.3769282818406279 julia> Base.print_array(io, rand(30)) 0.826103394472415 0.12252942018471757 0.2578897738095447 0.9962502442037657 0.7355190804809946 0.7585607760377631 0.9151773834286789 0.3929985249254522 0.6641753306579214 0.33175433327494197 ⋮ 0.7833300985202769 0.18328052862651867 0.23765181941856928 0.023377483952839784 0.09753998271209197 0.5774230030542189 0.8859464218457049 0.6723385022029065 0.47453039665141294 julia> io = IOContext(stdout, :limit => false, :compact => false) IOContext(Base.TTY(RawFD(0x0000000d) open, 0 bytes waiting)) julia> Base.print_array(io, rand(30)) 0.7545909477271988 0.04426425050940841 0.8374874234291312 0.1487924398894196 0.9042352827314581 0.6163873731935219 0.8876188540406658 0.941831168132194 0.5473667368995183 0.06389212057729132 0.31826618585326116 0.5844167820961697 0.2901193343171964 0.04053208788398743 0.12097218515312957 0.008122564658366693 0.26497805735815083 0.6564738355733903 0.058178777741022536 0.3444346924786441 0.3245624908138902 0.8014413904453013 0.9545200342276534 0.154475460973849 0.7799280363802161 0.5400078688551904 0.8594465295979896 0.881281761468725 0.8220828940691287 0.9481617822907571
~~ここでは扱いませんが Markdown で配列を表示させたい人は Base.print_matrix_row の使い方を覚えると良いかもしれません。*1
pretty-print
最後に pretty-print です。pretty-printの正確な定義がよくわかりませんが、垢抜けない野暮ったい出力をプリティーにすることでしょう(多分)。
下図の「ここ」とくくった部分をなんて呼ぶのか、または、「ここ」の部分を表示させる機能をなんて呼ぶのかわからないので説明しづらいのですが、「ここ」とくくった部分が何故このように表示されるかといったら、それはもちろんそのように定義されているからです。

そしてこの「ここ」の部分の表示のさせ方は型によって特徴付けられています。
この「ここ」の部分の表示方法を変えたい場合は Base.show のメソッドを上書きすることで実現できます。
基本的な書き方
function Base.show(io::IO, ::MIME"text/plain", x::MyType) # MyType の部分を好みの型に変える println(io, "A is ", x.a) # print に io を入れることを忘れないように! println(io, "B is ", x.b) ... end
2つ目の例を再現
新たに作った Horse 型の表示方法を定義してみます。
julia> struct Horse
name::String
jockey::String
sire::String
dam::String
age::Int
finpos::Vector{Int}
end
julia> x = Horse("ハリボテエレジー", "手作好太郎", "ダンボウルガクエン", "ガムテイプマツリ", 6, fill(8,100))
Horse("ハリボテエレジー", "手作好太郎", "ダンボウルガクエン", "ガムテイプマツリ", 6, [8, 8, 8, 8, 8, 8, 8, 8, 8, 8 … 8, 8, 8, 8, 8, 8, 8, 8, 8, 8])
julia> function Base.show(io::IO, ::MIME"text/plain", x::Horse)
summary(io, x)
println(io)
println(io, " 競走馬名: ", x.name)
println(io, " 騎手 : ", x.jockey)
println(io, " 父馬 : ", x.sire)
println(io, " 母馬 : ", x.dam)
println(io, " 馬齢 : ", x.age)
println(io, " 着順")
Base.print_array(io, x.finpos)
end
julia> x
Horse
競走馬名: ハリボテエレジー
騎手 : 手作好太郎
父馬 : ダンボウルガクエン
母馬 : ガムテイプマツリ
馬齢 : 6
着順
8
8
8
8
8
8
8
8
8
8
⋮
8
8
8
8
8
8
8
8
8
MIME を "text/markdown" で定義すると、 Jupyter Notebook を使っている場合 "text/markdown" で定義した方法が優先されます。 私がやるとしたら下記のように配列を含んでいなかったら Markdown への対応を考えるかもしれません。
function Base.show(io::IO, ::MIME"text/markdown", x::Number) println(io, "\$", x, "\$") end rand()

配列を含んでいるとどう面倒なのかは下記の例を見ていただくと納得いただけるのではないかと思います。以下は先程の例を "text/markdown" に変えただけです。
function Base.show(io::IO, ::MIME"text/markdown", x::Horse) summary(io, x) println(io) println(io, " 競走馬名: ", x.name) println(io, " 騎手 : ", x.jockey) println(io, " 父馬 : ", x.sire) println(io, " 母馬 : ", x.dam) println(io, " 馬齢 : ", x.age) println(io, " 着順") Base.print_array(io, x.finpos) end x

まとめ
- 凝りだすと際限がないので程々のところでやめましょう。
- print を凝りたい場合、公式ドキュメントは非力なので Julia のソースコードを読んでください。