今川館

都内勤務の地味OLです

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ってやつはすごいと思ったのであった。

参考リンク

Go for Pythonistas

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 ON を予め実行する必要がある。

オートインクリメント列を事前に採番しておきたい場合

テーブルに共有ロック(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を見たら別の解決方法が書いてあった。

python - InsecurePlatformWarning: A true SSLContext object is not available. This prevents urllib3 from configuring SSL appropriately - Stack Overflow

"requests[security]"をインストールすると良いとのこと。

手元の環境がUbuntuだったので

$ sudo apt-get install libffi-dev libssl-dev
$ . ./bin/activate
$ pip install requests requests[security]

これで確かに警告が消えた。

django-pyodbcでselect_for_updateは機能しないので要注意 (2015/4時点)

django-pyodbcはselect_for_updateメソッドを呼び出しても普通のSELECT文を発行する

Django1.4以上でモデルマネージャやクエリセットにselect_for_updateというメソッドが使えるけれども、django-pyodbc(=SQLServer)だとこれが使えないので注意が必要。

続きを読む

Pythonのdate, datetimeをisinstanceで調べるとき

Pythonのdatetimeをisinstanceで調べると、date, datetimeの両方にTrueを返すので注意。

>>> from datetime import date, datetime
>>> today, now = date.today(), datetime.now()
>>> isinstance(today, date)
True
>>> isinstance(today, datetime)
False
>>> isinstance(now, datetime)
True
>>> isinstance(now, date)
True
続きを読む