draft, created: 2017/04/22;
キーワードを使った引数の初期値(keyword argument's default value)を扱った節である。まず、以下の事例を挙げている。
import datetime
import time
def log(message, when=datetime.datetime.now()):
print('%s: %s' % (when, message))
log('Hi there!')
time.sleep(1)
log('Hi again!')
なお、以下を修正。
# 追加
import datetime
import time
# 修正
def log(message, when=datetime.datetime.now()):
time.sleep(1)
# もとは以下
def log(message, when=datetime.now()):
sleep(0.1)
問題は、以下のような出力結果となること。
2017-04-22 09:23:27.583447: Hi there!
2017-04-22 09:23:27.583447: Hi again!
time.sleep(1)
があるのに、出力時間が同じだ。def log
を呼び出す度に現在時刻を呼び出し、when
に代入するコードに見えるのだが……。その理由は以下の通り、とのこと。
The timestamps are the same because
datetime.now
is only executed a single time;when the function is defined. Default argument values are evaluated only once per module load, which usually happens when a program starts up. After the module containing this code is loaded, thedatetime.now
default argument will never be evaluated again.
という訳で、以下の解決策を提示している。
import datetime
import time
def log(message, when=None):
"""Log a message with a timestamp.
Args:
message: Message to print.
when: datetime of when the message occurred. Defaults to the present time.
"""
when = datetime.datetime.now() if when is None else when
print('%s: %s' % (when, message))
log('Hi! there,')
time.sleep(1)
log('Hi! again.')
ただねぇ、というのが正直な心境なのだ。こんな面倒なことをしなくても、以下。
import datetime
import time
def log(message):
print('%s: %s' % (datetime.datetime.now(), message))
log('Hi! there,')
time.sleep(1)
log('Hi! again.')
結果として、「effective」じゃないよなぁ、と。それから、敢えてコードを面倒にするこの手法は、一体、どこで、どう使うんだ? そう思う。さらに次の事例が続く。
import json
def decode(data, default={}):
try:
return json.loads(data)
except ValueError:
return default
foo = decode('bad data')
foo['stuff'] = 5
bar = decode('also bad')
bar['meep'] = 1
print('Foo:', foo)
print('Bar:', bar)
なお、import json
を追加してある。問題は結果。以下。
Foo: {'stuff': 5, 'meep': 1}
Bar: {'stuff': 5, 'meep': 1}
上のコードに以下を追加して実行する。
assert foo is bar
print('foo is bar')
結果は、以下。
Foo: {'stuff': 5, 'meep': 1}
Bar: {'stuff': 5, 'meep': 1}
foo is bar
出力結果が、やはり同じなのだ。なぜだろう。その前に、jsonのこと。以下のサイトを参考にして、復習しておこう。
- わかりやすい。[Python] JSONを扱う - YoheiM .NET
- 【Python入門】JSON形式データの扱い方 - Qiita
- 少し難しい。事例が複雑すぎる。非エンジニアに贈る「具体例でさらっと学ぶJSON」 | Developers.IO
- 上記3つを理解するために。=>Pythonでのコメントアウトの書き方 | UX MILK
で、jsonとは、である。3.から。
JSON は「JavaScript Object Notation」の略で、「JavaScript 言語の表記法をベースにしたデータ形式」と言えます。しかし、表記が JavaScript ベースなだけで、それ以外のさまざまな言語で利用できます。JSON では、ある数値と、その数値の名前であるキーのペアをコロンで対にして、それらをコンマで区切り、全体を波かっこで括って表現します。
その事例。やはり、3.から。
{
"book1":{
"title": "Python Beginners",
"year": 2005 ,
"page": 399
},
"book2":{
"title": "Python Developers",
"year": 2006 ,
"page": 650
}
}
pythonでjsonを扱うには、1.から以下。JSON文字列からPythonオブジェクトに変換するコードは以下の通り。たぶん、正確には、jsonの形式で書かれたテキストデータを、pythonで扱えるオブジェクトに変換する、ということだと思う。
import json
# json文字列の記述方法はいろいろある。
good = '''
{
"good": 333,
"bad": 444
}
'''
good2 = '''
{"good": 333, "bad":444}
'''
good3 = '{"good": 333, "bad":444}'
good4 = '''{"good": 333, "bad":444}'''
foo = json.loads(good)
print(foo)
foo = json.loads(good2)
print(foo)
foo = json.loads(good3)
print(foo)
foo = json.loads(good4)
print(foo)
# もう少し、複雑なjson文字列。
book = '''
{
"book1":{
"title": "Python Beginners",
"year": 2005 ,
"page": 399
},
"book2":{
"title": "Python Developers",
"year": 2006 ,
"page": 650
}
}
'''
bar = json.loads(book)
print(bar)
# ついでに、辞書型データのテスト。
print(bar["book2"])
print(bar["book2"]["year"])
出力結果は以下。
{'good': 333, 'bad': 444}
{'good': 333, 'bad': 444}
{'good': 333, 'bad': 444}
{'good': 333, 'bad': 444}
{'book1': {'title': 'Python Beginners', 'year': 2005, 'page': 399}, 'book2': {'title': 'Python Developers', 'year': 2006, 'page': 650}}
{'title': 'Python Developers', 'year': 2006, 'page': 650}
2006
そんなわけで、jsonを理解した上で、実験。問題の事例を改変。そもそも、
foo = decode('bad data')
が意味不明なので。json.load
で直接やってみた。
import json
def decode(data, default={}):
try:
return json.loads(data)
except ValueError:
return default
dame = json.loads('bad data')
print(dame)
foo = decode('bad data')
foo['stuff'] = 5
bar = decode('also bad')
bar['meep'] = 1
print('Foo:', foo)
print('Bar:', bar)
もちろん、エラー。なので、別の実験。
import json
def decode(data, default={}):
try:
return json.loads(data)
except ValueError:
return default
foo = decode('bad data')
print('Foo-1:', foo)
foo['stuff'] = 5
print('Foo-2:', foo)
bar = decode('also bad')
print('Foo-3:', foo)
bar['meep'] = 1
print('Foo:', foo)
print('Bar:', bar)
出力結果は以下。
Foo-1: {}
Foo-2: {'stuff': 5}
Foo-3: {'stuff': 5}
Foo: {'stuff': 5, 'meep': 1}
Bar: {'stuff': 5, 'meep': 1}
ということは、変なデータを入れてるのでエラーになり、その結果、return default
になり、foo
は、{}
となる。そうであるなら、ちゃんとしたデータをdecode
に入れると、どうなるのだろう。実験。
import json
def decode(data, default={}):
try:
return json.loads(data)
except ValueError:
return default
good = '''
{"hoge": 333, "fuga":444}
'''
bad = '''
{"tako": 2, "hebi":6}
'''
foo = decode(good)
foo['stuff'] = 5
bar = decode(bad)
bar['meep'] = 1
print('Foo:', foo)
print('Bar:', bar)
出力は以下。
Foo: {'hoge': 333, 'fuga': 444, 'stuff': 5}
Bar: {'tako': 2, 'hebi': 6, 'meep': 1}
つまり、decode
にちゃんとしたデータが入力されると、順当な結果となる。という、まあ自然な話なのだが、「effective」は、None
を持ち出して、ややこしくする。冒頭のコードを以下のように改良すべきだというのだ。
import json
def decode(data, default=None):
"""Load JSON data from a string.
Args:
data: JSON data to decode.
default: Value to return if decoding fails. Defaults to an empty dictionary.
"""
if default is None:
default = {}
try:
return json.loads(data)
except ValueError:
return default
foo = decode('bad data')
foo['stuff'] = 5
bar = decode('also bad')
bar['meep'] = 1
print('Foo:', foo)
print('Bar:', bar)
結果は以下。default
が常にNone
なので、decode
がエラーとなるような引数を入れると、default={}
に設定される。つまり、毎回、空のオブジェクトが生成される。その結果、以下。
Foo: {'stuff': 5}
Bar: {'meep': 1}
想定していた出力となる。ただし、これも、最悪、以下でいいのではないか。
import json
def decode(data):
try:
return json.loads(data)
except ValueError:
return {}
foo = decode('bad data')
foo['stuff'] = 5
bar = decode('also bad')
bar['meep'] = 1
print('Foo:', foo)
print('Bar:', bar)
出力は、以下。想定通りである。
Foo: {'stuff': 5}
Bar: {'meep': 1}
それにしても、これが、どう使えるのか。不思議な事例である。