Goはパッケージトップレベルの関数をreflectで取り出せない
ことの始まりはtimeモジュールのFormat()の挙動が理解できなかったことなのだが、
time.nextStdChunkという関数をどうしても実行したくなり、reflectで取り出そうとしてはまった。
まず、小文字で始まる名前をつけたものは同一パッケージ内からしかアクセスできない。
なので、time.nextStdChunkはtimeパッケージ外から利用できない。
そういうとき、リフレクションを使えばnextStdChunkを取り出せるだろうと思いreflectパッケージの使い方を調べたのだが、以下のコードが動かなくて困った。
import "reflect" import "time" reflect.ValueOf(time) // これができない
どうやら、↓こんな難しいことしないとダメらしい。
Adventures in Go: Accessing Unexported Functions
http://www.alangpierce.com/blog/2016/03/17/adventures-in-go-accessing-unexported-functions/
go getで利用できるようにGithubに上げてあった
https://github.com/alangpierce/go-forceexport
Pythonだとモジュールもインポートしたらただのオブジェクトみたいに使えて、トップレベルに定義したものも難なく取り出せるので意外だった。
Goの日付フォーマットの謎
日付や日時を文字列に変換する方法を調べていて、Goは%Y-%m-%dではなく、"2006-01-02"というフォーマットを採用していることを知る。
年は「2006」、月は「01」、日は「02」と決まっている。
これは、「2016」、「12」、「31」ではいけないのである。
import time fmt.Println(time.Now()) // 2016-11-17 11:46:44.630877067 +0900 JST // わかる。 fmt.Println(time.Now().Format("2006/01/02")) // => 2016/11/17 // なぜなのか? fmt.Println(time.Now().Format("2016/12/31")) // => 17116/1117/1111 fmt.Println(time.Now().Format("2005/01/02")) // => 17044/11/17
ここに各種トークンの意味が定義されている
https://golang.org/src/time/format.go
const ( _ = iota stdLongMonth = iota + stdNeedDate // "January" stdMonth // "Jan" stdNumMonth // "1" stdZeroMonth // "01" stdLongWeekDay // "Monday" stdWeekDay // "Mon" stdDay // "2" stdUnderDay // "_2" stdZeroDay // "02" stdHour = iota + stdNeedClock // "15" stdHour12 // "3" stdZeroHour12 // "03" stdMinute // "4" stdZeroMinute // "04" stdSecond // "5" stdZeroSecond // "05" stdLongYear = iota + stdNeedDate // "2006" stdYear // "06" stdPM = iota + stdNeedClock // "PM" stdpm // "pm" stdTZ = iota // "MST" stdISO8601TZ // "Z0700" // prints Z for UTC stdISO8601SecondsTZ // "Z070000" stdISO8601ShortTZ // "Z07" stdISO8601ColonTZ // "Z07:00" // prints Z for UTC stdISO8601ColonSecondsTZ // "Z07:00:00" stdNumTZ // "-0700" // always numeric stdNumSecondsTz // "-070000" stdNumShortTZ // "-07" // always numeric stdNumColonTZ // "-07:00" // always numeric stdNumColonSecondsTZ // "-07:00:00" stdFracSecond0 // ".0", ".00", ... , trailing zeros included stdFracSecond9 // ".9", ".99", ..., trailing zeros omitted stdNeedDate = 1 << 8 // need month, day, year stdNeedClock = 2 << 8 // need hour, minute, second stdArgShift = 16 // extra argument in high bits, above low stdArgShift stdMask = 1<<stdArgShift - 1 // mask out argument )
非常に不運なことに、わたしはこのサンプルコードを11月17日の午前11時に動かしていたため、「11」と「17」が不規則に並んで出力され(!)、ことさら混乱を招いた。
ちなみに、11月11日は日本を代表する歌手である吉幾三さんの誕生日である。
"2005"を指定すると、「2:日」+「0:無視」+「05:秒」と解釈される。
よって、「17(日)」+「(空文字列)」+「044(秒)」=「17044」となる。
同様に、"2016"は「2:日」+「01:月」+「6(末尾文字はそのまま結合)」解釈され、「17」+「11」+「6」=「17116」となるのである。
はっきり言って、%Y-%m-%dの方が慣れているのでわかりやすい*1。
なまじ具体的な日時の値に即したフォーマットなので、任意の日時を書式指定するとうまく解釈してくれるものと誤解してしまった。
*1:所詮、わかりやすい理由は「慣れているから」にすぎないから、新たに慣れれば良いだけなのだが。
Goのファイル・ファイルライクオブジェクトもろもろ
ファイルの読み取り
Goでファイルを1行ずつ読み出したい場合はbufio.NewScannerを使うらしい。
そのNewScannerはio.Reader型の引数を取り、ファイルやファイルライクオブジェクトを渡す。
では、Goでファイルやファイルライクオブジェクトを扱う方法を調べてみた(以下3つ)。
Go | Python3 | |
---|---|---|
1 | strings.NewReader/bytes.NewReader | io.StringIO |
2 | ioutil.TempFile | tempfile.NamedTemporaryFile |
3 | os.Open | ビルトインのopen関数 |
1. strings.NewReader/bytes.NewReaderを使う方法 (PythonのStringIOに相当)
文字列をファイルのように扱うファイルライクオブジェクトとして、Goではstrings.NewReaderを使うらしい。
[追記: 2016/12/6]
stringを反復する場合はstrings.NewReader, byteを反復する場合はbytes.NewReaderと、2種類用意されている。
テキストファイルの代わりに使うならstrings.NewReaderが妥当。
x := bufio.NewScanner(strings.NewReader(text)) // または // x := bufio.NewScanner(bytes.NewReader([]byte(text))) for x.Scan() { fmt.Println(x.Text()) }
2. ioutil.TempFileを使う方法 (PythonのNamedTemporaryFileに相当)
Goにも一時ファイルをパァーッっと作ってくれるライブラリがあった。ioutil.TempFileがこれに当たる。
// import "io/ioutil" tmp, _ := ioutil.TempFile("", "_") tmp.Write([]byte("| わたしは\n")) tmp.Write([]byte("| い*がわ\n")) tmp.Write([]byte("| よ**と")) defer os.Remove(tmp.Name()) // PythonのNamedTemporaryFileと違い、ファイルの削除は自分でやる。 tmp.Seek(0, 0) x = bufio.NewScanner(tmp) for x.Scan() { fmt.Println(x.Text()) }
3. os.Openを使う方法 (Pythonのビルトインopen関数に相当)
普通にファイルを開くときは、os.Openを使う。
fp, _ := os.Open(tmp.Name()) defer fp.Close() x = bufio.NewScanner(fp) for x.Scan() { fmt.Println(x.Text()) }
まとめ
当然ながら、これらはすべてio.Reader型の変数に代入できる(以下)。
package main import ( "fmt" "io" "io/ioutil" "bufio" "bytes" "os" ) func main(){ var reader io.Reader var scanner *bufio.Scanner reader = bytes.NewReader([]byte("Hello, world.")) scanner = bufio.NewScanner(reader) fmt.Println(scanner) tmp, _ := ioutil.TempFile("", "_") defer os.Remove(tmp.Name()) tmp.Write([]byte("I have a pen.")) tmp.Seek(0, 0) reader = tmp scanner = bufio.NewScanner(reader) fmt.Println(scanner) fp, _ := os.Open(tmp.Name()) defer fp.Close() reader = fp scanner = bufio.NewScanner(reader) fmt.Println(scanner) }
Goのtype宣言はすごい
Goのtype宣言を使うと、特定の型を元にして別の型を定義できる。
一番単純な例: 基本データ型から別の名前の型を作る
type Foo int var x Foo x = 18 fmt.Printf("%T\n", x) // => main.Foo
これはわかる。intからFooという別の型を作るだけである。
関数に型名を付与する使い方
ところが、このGoのtypeってやつは関数にも利用できることに驚いた。
type NumFunc func(xs []int) int
これは全然okで、構文エラーにはならない。
つまり、
// intのスライスを引数に取って合計値を「文字列で」返す func NumString(xs []int) string { acc := 0 for i := 0; i < len(xs); i++ { acc += xs[i] } return strconv.Itoa(acc) } // intのスライスを引数に取って合計値を返す func Sum(xs []int) int { acc := 0 for i := 0; i < len(xs); i++ { acc += xs[i] } return acc } type NumFunc func(xs []int) int var fn NumFunc fn = Sum // これはok // fn = NumString // これはコンパイルエラー // cannot use NumString (type func([]int) string) as type NumFunc in assignment
このように、関数を任意の型として宣言し、所定のシグネチャを守る関数しか代入できなくさせられる。
動くコードはhttps://play.golang.org/p/Em_52ORgYqこちら
関数のファクトリを簡潔に記述する
そもそもこの話に興味を持ったのは、Pythonのデコレータに相当することをGoでやる方法を考え始めたときだった。
Pythonのデコレータは関数のファクトリだから同じことをすれば良いのだが、最初以下のような安直なやり方でしか書けないと思っていた。
package main import "fmt" func main() { var foo = func(x int) int { return x + 5 } var deco = func(fn func(int) int) func(int) int { return func(x int) int { return fn(x) } } var fn = deco(foo) fmt.Println(fn(100)) // => 105 }
とにかくdecoの定義の部分がひどい。
「func(int) int」という記載が3回も並ぶと実に読みにくい・・
なので、この重複の多い部分をtypeでくくり出す。
package main import "fmt" func main() { type F func(x int) int deco := func(fn F) F { return func(x int) int { return fn(x) } } var fn = deco(func(x int) int { return x + 5 }) fmt.Println(fn(100)) // => 105 }
すると、decoの記載がだいぶスッキリするじゃないか。というところでこのtypeってやつはすごいと思ったのであった。
参考リンク
SQL ServerのIDENTITY値
基本事項
IDENTITY値にはSEED(初期値), INCR(増分)という概念がある。
また、IDENT_????という関数が用意されている(以下)
- IDENT_CURRENT 現在値
- IDENT_SEED 初期値
- IDENT_INCR 増分
CREATE TABLE foo ( [ID] INT IDENTITY(25, 2) PRIMARY KEY, [NAME] NVARCHAR(16) NOT NULL, [AGE] INT ); INSERT INTO foo (NAME) VALUES (N'あ'), (N'い'), (N'う'); SELECT * FROM foo;
ID NAME
25 あ
27 い
29 う
SELECT IDENT_CURRENT('foo') [Current], IDENT_SEED('foo') [Seed], IDENT_INCR('foo') [Incr] ;
Current Seed Incr
29 25 2
IDENTITY列に明示的に値を指定して挿入する
通常、IDENTITYが設定されている列に値を明示的に指定して挿入はできない。
これがやりたいときは SET IDENTITY_INSERT
オートインクリメント列を事前に採番しておきたい場合
テーブルに共有ロック(SELECTは許すがUPDATE, INSERT, DELETEを許さない)をかけてから先述のIDENT_????関数を使って採番。
DECLARE @items TABLE ([ID] INT NOT NULL, NAME NVARCHAR(16) NOT NULL, AGE INT); BEGIN TRANSACTION; WITH X (NAME, AGE) AS ( SELECT N'わ', NULL UNION ALL SELECT N'を', NULL UNION ALL SELECT N'ん', 13 ) INSERT INTO @items ([ID], NAME, AGE) SELECT IDENT_CURRENT('foo') + (IDENT_INCR('foo') * ROW_NUMBER() OVER (ORDER BY [NAME])), X.NAME, X.AGE FROM X WITH (TABLOCK, HOLDLOCK) -- 共有ロック。トランザクション終了まで有効 ; SET IDENTITY_INSERT foo ON; INSERT INTO foo ([ID], NAME, AGE) SELECT [ID], NAME, AGE FROM @items; COMMIT;
名称を指定せずテーブルのIDENTITY列を参照する -- $IDENTITY
MSDNのサンプルの中に $IDENTITY というキーワードがあって、最初何を表すのかわからなかった。
これは対象としたテーブルのIDENTITY列を指すキーワードらしい。
SELECT $IDENTITY FROM foo
ID
25
27
29
31
33
35
requestsを使ってInsecurePlatformWarningが出てしまうときの対処
requestsを使っていたらいつの間にかInsecurePlatformWarningという警告が出るようになってしまった。
/home/echizen/pecan/local/lib/python2.7/site-packages/requests/packages/urllib3/util/ssl_.py:90: InsecurePlatformWarning: A true SSLContext object is not available. This prevents urllib3 from configuring SSL appropriately and may cause certain SSL connections to fail. For more information, see https://urllib3.readthedocs.org/en/latest/security.html#insecureplatformwarning.
InsecurePlatformWarning
SoftLayer - Python実行時にInsecurePlatformWarningが出る場合の対応方法 - Qiita
この記事を読むと
- requestsパッケージを2.5.3以前に戻す
- Pythonのバージョンを2.7.9以降に上げる
上記ふたつの対処方法が紹介されているが、どちらも採用しがたいと思い見送った。
困ったのでstackoverflowを見たら別の解決方法が書いてあった。
"requests[security]"をインストールすると良いとのこと。
手元の環境がUbuntuだったので
$ sudo apt-get install libffi-dev libssl-dev $ . ./bin/activate $ pip install requests requests[security]
これで確かに警告が消えた。