BlankTar

about | blog | works | photo

pythonでシングルトンを書くと結構スマートに書けるっぽいことを知ったのでメモ。
まあ、私は滅多にシングルトン使わないんですけれど。

シングルトンっていうのは以下のようなやつです。

class NormalSingleton:
	_instance = None

	def __init__(self):
		print('init')

	@classmethod
	def get_instance(cls):
		if cls._instance is None:
			cls._instance = cls()

		return cls._instance


if __name__ == '__main__':
	a = NormalSingleton.get_instance()  # initが表示される
	b = NormalSingleton.get_instance()  # 何も表示されない
	print(a is b)   # True

	c = NormalSingleton()  # init
	b = NormalSingleton()  # init
	print(a is b)  # False

こいつはよくある普通の実装。
get_instanceメソッドを使ってインスタンスを取得しているうちは良いのですが、普通にインスタンス化出来てしまうのでシングルトンになりません。
他の言語だとコンストラクタをプライベートにするとかで対処するわけですが、pythonではそうも行かず。

で、どうするかっていうと、__new__ってメソッドを定義することにします。
__new__っていうのはクラスのインスタンスを作るときに呼ばれる特殊なメソッドで、__init__の前に呼ばれるようになっています。
こいつを使うとめっちゃ良い感じになる。

class SimpleSingleton:
	_instance = None

	def __init__(self):
		print('init')

	def __new__(cls):
		if cls._instance is None:
			cls._instance = super().__new__(cls)

		return cls._instance


if __name__ == '__main__':
	a = SimpleSingleton()  # initが表示される
	b = SimpleSingleton()  # こっちもinitが表示される
	print(a is b)   # True

使い方がとてもシンプルになりました。シングルトンかそうでないかを利用者が気にする必要性が無くなってハッピー。

__init__が二回呼ばれるようになることだけ注意です。
初期化は__new__の中でやっても良いかもね?

ちなみにどちらの実装もスレッドセーフではないので、スレッドセーフにしたい場合はLockを導入しましょう。
導入すると以下のような感じ。

import threading


class ThreadingSingleton:
	_instance = None
	_lock = threading.Lock()

	def __init__(self):
		print('init')

	def __new__(cls):
		with cls._lock:
			if cls._instance is None:
				cls._instance = super().__new__(cls)

		return cls._instance

使い方は全く変わらないので省略。
めっちゃ単純で良い感じですね。

Elixirの変数は変更不能らしいです。変化出来ない数、変数。ふしぎ。
安全性が高くて良いのですが、Webのアクセスカウンター的なものを作りたいときとか、メモ化とかやりたいときに困ります。
というときに登場するのがAgentってやつらしいです。

とりあえず使ってみる

defmodule Counter do
  def counter do
	Agent.update(:counter, fn x -> x + 1 end)
	Agent.get(:counter, fn x -> x end)
  end

  def start_agent do
	Agent.start_link fn -> 0 end, name: :counter
  end
end

Counter.start_agent
IO.inspect Counter.counter
IO.inspect Counter.counter
IO.inspect Counter.counter

こんな感じで使います。

Counter.start_agent関数でAgentとやらを開始しています。スレッドみたいなもので、変数を保持しておいてくれるらしい。
第一引数は初期値を返す関数で、第二引数は識別子を渡しています。
識別子さえ変えておけば複数のAgentを立ち上げても大丈夫。

Counter.counter関数を実行すると、Agent.update関数を使って値を更新して、Agent.get関数で値を取得しています。
単純にカウントするだけだけどちょっと面倒臭い…。

メモ化のために使ってみる

メモ化といえばフィボナッチ数ということで、フィボナッチ数の実装をやってみました。

defmodule Memorize do
  def fib 0 do
	0
  end

  def fib 1 do
	1
  end

  def fib x do
	case Agent.get(:fib_memo, &Map.get(&1, x)) do
	  nil ->
		v = fib(x - 1) + fib(x - 2)
		Agent.update(:fib_memo, &Map.put(&1, x, v))
		v

	  v -> v
	end
  end

  def start_agent do
	Agent.start_link &Map.new/0, name: :fib_memo
  end
end


Memorize.start_agent
IO.inspect Memorize.fib 1024

Agentにmapを持っておいてもらって、fib関数が呼ばれる度に検索しにいっています。
見付かったらその値をそのまま返し、無ければ計算して保存する感じ。

メモ化しておけば1024とかで計算させてもすごいスピードが出ます。
事実上は1024回ループが回ってるだけだからね。

なんだか面倒臭い感じがするのですが、向いてないってことなのかなぁ…。

参考: Go VS Elixir (1)フィボナッチ数列 - 技術備忘記

昨日のディレクトリ操作に引き続いて、本日はElixirでファイル入出力をやってみます。

簡単な読み書き

単純に読み込む/書き込むだけならものすごく簡単で、以下のように出来ます。

iex> File.write "test.txt", "hello"
:ok
iex> File.read "test.txt"
{:ok, "hello"}

iex> File.write "test.txt", " world", [:append]
:ok
iex> File.read "test.txt"
{:ok, "hello world"}

一回目の書き込みが通常の上書き的な書き込み、二回目の書き込みが追記書き込みになっています。
File.writeの第三引数にオプションを渡すわけですね。使えるオプションについては後述。

ファイルハンドラを使った読み書き

一般的なopenしてwriteして…って流れでも読み書きが出来ます。以下のような感じ。

iex> File.write "test.txt", "はろーわーるど"
:ok

iex> {:ok, fp} = File.open "test.txt"
{:ok, #PID<0.100.0>}

iex> IO.binread fp, 3
"は"

iex> File.close fp
:ok

デフォルトでは読み込みモード、バイナリモード?で開かれるようです。
IO.binreadの第二引数はバイト数です。3文字読みたかったのに1文字しか返って来ませんでした。

ファイルがutf-8で保存されているなら、utf-8モードが使えます。

iex> {:ok, fp} = File.open "test.txt", [:utf8]
{:ok, #PID<0.100.0>}

iex> IO.read fp, 3
"はろー"
iex> IO.read fp, 4
"わーるど"
iex> IO.read fp, 1
:eof

iex> File.close fp
:ok

IO.binreadではなくてIO.readになりました。ちゃんと文字数で指定出来ています。
ファイルの終了まで行ったら:eofが返るのもポイントですね。

書き込みも何の捻りもなく以下のように行ないます。

iex> {:ok, fp} = File.open "test.txt", [:write, utf8]
{:ok, #PID<0.100.0>}

iex> IO.binwrite fp, "はろー"
:ok

iex> File.close fp
:ok

iex> File.read "test.txt"
{:ok, "はろー"}

openするときにオプションに:writeを渡すだけです。

オプション

:read:writeはそのまんま読み込みモード/書き込みモードです。
両方指定すると読み書き両方出来るようになります。
:readを指定しているときは存在しないとエラーに、:writeを指定しているときは存在しなければ作るようです。

:appendを指定すると追記書き込みになります。上でちょこっと使ったやつです。
存在しない場合は新規作成になります。

:exclusiveオプションを:writeと同時に使うと、ファイルが存在するとエラーが返るようになります。
新規作成だけを認める場合に使うオプションですね。

:char_list:utf8を使うと読み込んだときに返る型を決められるようです。
何も付けないとbinaryで返るようになっています。
エンコーディングについては:utf16utf32:big:littleなど、色々オプションがあるようです。

:compressedを付けるとファイルがgzipで圧縮されるようになります。
圧縮レベルなどのオプションもあるのかもしれませんが、見付けられませんでした。

:delayed_write:syncを付けると、書き込みを即座に行なうかバッファするかを選べます。
デフォルトでは:syncのようです。細かく沢山書き込むときは:delayed_writeにしておくのが賢いのかも?

参考:
File - Elixir v1.2.5
IO - Elixir v1.2.5

[ << ] [ 2 ] [ 4 ] [ 6 ] [ >> ]