自分でpythonのプログラムを組んでて、ちょいちょいハマる事柄を記録していきます。
2024/MAR/27
更新履歴
日付
変更内容 2020/APR/19
新規作成 2020/MAY/02
boolと整数と辞書
追加 2020/JUL/12
辞書のupdate()メソッド
追加 2020/SEP/08
varsとオブジェクトのアトリビュート
追加 2020/SEP/19
emptyモジュール落とし穴
追加 2020/SEP/20
emptyモジュールのto_attr()の効用
追加 2020/OCT/13
リストに空文字列を追加すると
追加 2020/OCT/30
SIGINT(^Cキー)で終了するか 2020/OCT/31
forの動作について 2020/DEC/29
map()のままでも良い場合 2021/JAN/12
map()のままでも良い場合
len
boolの判定 2021/JAN/30
importしたファイルに定義されているグローバル変数の値 2021/FEB/11
map()のままでも良い場合
sumの引数
mapとsum2021/MAR/04
変数や内部関数の定義順の違い 2021/MAR/19
map()のままでも良い場合
allとanyの引数 2021/MAR/22
map()のままでも良い場合
文字列のjoin 2021/JUN/30
map()のままでも良い場合
dict生成 2024/MAR/27
bytesのスライス
クロージャを多用するので、内部関数はよく使います。
pythonのユーティリティ・プログラム 2020冬 の empty.py の通り。
eval()が絡むとちょいちょいハマってしまうので、メモしておきます。
例えば
$ cat foo.py #!/usr/bin/env python a = 1 def foo(): a = 2 def bar(): a = 3 return a return bar if __name__ == "__main__": f = foo() print( f() ) # EOF
$ chmod +x foo.py $ ./foo.py 3
a = 3 行をコメントアウトすると
$ cat foo.py #!/usr/bin/env python a = 1 def foo(): a = 2 def bar(): #a = 3 return a return bar if __name__ == "__main__": f = foo() print( f() ) # EOF
$ ./foo.py 2
a = 2 行もコメントアウトすると
$ cat foo.py #!/usr/bin/env python a = 1 def foo(): #a = 2 def bar(): #a = 3 return a return bar if __name__ == "__main__": f = foo() print( f() ) # EOF
$ ./foo.py 1
ですね。
ところが
$ cat foo.py #!/usr/bin/env python a = 1 def foo(): a = 2 def bar(s): a = 3 return eval( s ) return bar if __name__ == "__main__": f = foo() print( f( 'a' ) ) # EOF
の場合
$ ./foo.py 3
a = 3 行をコメントアウトすると
$ cat foo.py #!/usr/bin/env python a = 1 def foo(): a = 2 def bar(s): #a = 3 return eval( s ) return bar if __name__ == "__main__": f = foo() print( f( 'a' ) ) # EOF
$ ./foo.py 1
!!! なんと!
そして
$ cat foo.py #!/usr/bin/env python a = 1 def foo(): #a = 2 def bar(s): #a = 3 return eval( s ) return bar if __name__ == "__main__": f = foo() print( f( 'a' ) ) # EOF
$ ./foo.py 1
a = 2 のスコープはeval()に見えてないのですね。
execでは?
$ cat foo.py #!/usr/bin/env python a = 1 def foo(): a = 2 def bar(s): a = 3 exec( s ) return bar if __name__ == "__main__": f = foo() f( 'print( a )' ) # EOF
$ ./foo.py 3
a = 3 行をコメントアウトすると
$ cat foo.py #!/usr/bin/env python a = 1 def foo(): a = 2 def bar(s): #a = 3 exec( s ) return bar if __name__ == "__main__": f = foo() f( 'print( a )' ) # EOF
$ ./foo.py 1
a = 2 行もコメントアウトすると
$ cat foo.py #!/usr/bin/env python a = 1 def foo(): #a = 2 def bar(s): #a = 3 exec( s ) return bar if __name__ == "__main__": f = foo() f( 'print( a )' ) # EOF
$ ./foo.py 1
eval()と同じですね。
例えば、引数のローカル変数で、初期値として与えたら
$ cat foo.py #!/usr/bin/env python a = 1 def foo(): a = 2 def bar(s, a=a): #a = 3 return eval( s ) return bar if __name__ == "__main__": f = foo() print( f( 'a' ) ) # EOF
$ ./foo.py 2
a = 2 のスコープが見えます。
当然aの束縛は変更できません。
あるいは bar で一度 a = 2 を参照しておくと
$ cat foo.py #!/usr/bin/env python a = 1 def foo(): a = 2 def bar(s): a #a = 3 return eval( s ) return bar if __name__ == "__main__": f = foo() print( f( 'a' ) ) # EOF
$ ./foo.py 2
これでも a = 2 見えます。
むー。もやっとしますな。
locals()を使ってみると
$ cat foo.py #!/usr/bin/env python a = 1 def foo(): a = 2 def bar(s): locals()[ 'a' ] = a #a = 3 return eval( s ) return bar if __name__ == "__main__": f = foo() print( f( 'a' ) ) # EOF
$ ./foo.py 2
いっそうの事、fooのローカル全部を持ち込むならば
$ cat foo.py def foo(): a = 2 lcs = locals() print( lcs ) def bar(s): lcs_ = locals() print( lcs_ ) f = lambda kv: kv[ 0 ] not in lcs_ add = dict( filter( f, lcs.items() ) ) lcs_.update( add ) print( lcs_ ) #a = 3 return eval( s ) return bar if __name__ == "__main__": f = foo() print( f( 'a' ) ) # EOF
$ ./foo.py {'a': 2} {'s': 'a', 'lcs': {'a': 2}} {'s': 'a', 'lcs': {'a': 2}, 'a': 2} 2
foo()の実行で lcs に代入した時点での locals は a = 2
内部関数 bar() が返されて、bar() が呼び出されます。
bar()が呼び出されてすぐの locals() は、引数の s と、lcs。
lcsはlcs.items()として参照してるから、見えてますね。
そして、一応fooの方のlcsからキーがダブらないものだけ選んで、a = 2 を追加
eval( 'a' )で、locals() に追加した a = 2 が見えます。
空クラス empty.py を使って
$ cat foo.py #!/usr/bin/env python import empty def foo_new(): e = empty.new() e.a = 2 def bar(s, e=e): return eval( s ) return empty.to_attr( e, locals() ) if __name__ == "__main__": foo = foo_new() print( foo.bar( 'e.a' ) ) # EOF
$ ./foo.py 2
barの引数でeだけをとりこんでおいて、文字列側で'e.'つきで指定する。
このくらいで手討ちでしょうか。
すぐ忘れてしまいます。
Boolean オブジェクト Python の Bool 型は整数のサブクラスとして実装されています。
である事を。
$ python >>> d = { 0: 'zero', 1: 'one', True: 'True', False: 'False' }
などという辞書を作ると
>>> d.get( 0 ) 'False' >>> d.get( False ) 'False' >>> d.get( 1 ) 'True' >>> d.get( True ) 'True'
なんで?
>>> list( d.keys() ) [0, 1]
0, 1の2つしかキーが無いのに
>>> True in d True >>> False in d True
TrueもFalseもある?
>>> d {0: 'False', 1: 'True'}
ああ、boolはintのサブクラス
>>> d2 = { 0: 'zero', 1: 'one' } >>> d2 {0: 'zero', 1: 'one'}
boolが辞書のキーとして扱われるときは、int !!!
>>> d2[ False ] = 'False' >>> d2 {0: 'False', 1: 'one'} >>> d2[ True ] = 'True' >>> d2 {0: 'False', 1: 'True'}
上書きされてしまい、意図しなかった辞書の出来上がり orz
辞書の「値」としては、ちゃんと区別されてるので...
>>> d3 = { 0: False, 1: True, 2: 0, 3: 1 } >>> d3 {0: False, 1: True, 2: 0, 3: 1}
ちょいちょいひっかかります。
>>> False == 0 True >>> False is 0 False >>> True == 1 True >>> True is 1 False >>> True == 2 False
という事は、辞書のキーの判定は'is'ではなく、'=='という事ですね。
当然ですが、階層的な再帰処理ではありません。
なのですが、YAMLデータでついつい階層的に作ってしまい、 update()メソッドで意図に反した動作になり、
「しまった...」
と、なりがちです。
>>> targ = { 1: 'a', 2: 'b' } >>> targ {1: 'a', 2: 'b'} >>> add = { 2: 'B', 3: 'C' } >>> targ.update( add ) >>> targ {1: 'a', 2: 'B', 3: 'C'}
>>> s = ''' ... foo: ... kind: a ... num: 2 ... bar: ... kind: b ... num: 3 ... ''' >>> s '\nfoo:\n kind: a\n num: 2\nbar:\n kind: b\n num: 3\n'
階層的な辞書のYAMLデータを作っておいて
>>> import yaml >>> targ = yaml.load( s ) >>> targ {'foo': {'kind': 'a', 'num': 2}, 'bar': {'kind': 'b', 'num': 3}} >>> print( yaml.dump( targ ) ) bar: {kind: b, num: 3} foo: {kind: a, num: 2}
ロードして辞書に。
>>> s_add = ''' ... foo: ... num: 5 ... ''' >>> add = yaml.load( s_add ) >>> add {'foo': {'num': 5}}
fooのnumだけを2から5に更新したく、そのようなデータを作って
>>> targ.update( add )
update()
>>> targ {'foo': {'num': 5}, 'bar': {'kind': 'b', 'num': 3}} >>> print( yaml.dump( targ ) ) bar: {kind: b, num: 3} foo: {num: 5}
あちゃー。
fooのkindが消えてしまいました。
def dic_update_r(targ, add): for (k, v) in add.items(): tv = targ.get( k ) if type( v ) == dict and type( tv ) == dict: dic_update_r( tv, v ) else: targ[ k ] = v
pythonのユーティリティ・プログラム 2020冬 の base.py に入れてみました。
>>> s '\nfoo:\n kind: a\n num: 2\nbar:\n kind: b\n num: 3\n' >>> targ = yaml.load( s ) >>> targ {'foo': {'kind': 'a', 'num': 2}, 'bar': {'kind': 'b', 'num': 3}} >>> add {'foo': {'num': 5}}
update前の状態に戻して
>>> import base >>> base.dic_update_r( targ, add )
再帰的にupdate
>>> targ {'foo': {'kind': 'a', 'num': 5}, 'bar': {'kind': 'b', 'num': 3}} >>> print( yaml.dump( targ ) ) bar: {kind: b, num: 3} foo: {kind: a, num: 5}
これで無事意図した通り、fooのnumが2から5に更新されました。
オブジェクトにvars()して、アトリビュートの辞書を取得したとき。
なぜだか「辞書はコピーだ」と、強く思い込んでおりました。
実際にはそんな事はなく、辞書を変更すると本体のオブジェクトに反映されます。
長年の間違った思い込みのせいで、驚きです!
$ python >>> class Empty: ... pass ... >>> e = Empty() >>> e.foo = 'foo' >>> d = vars( e ) >>> d {'foo': 'foo'} >>> d[ 'foo' ] = 123 >>> e.foo 123
>>> d[ 'bar' ] = 'BAR' >>> e.bar 'BAR'
できます。
>>> d.pop( 'foo' ) 123 >>> e.foo Traceback (most recent call last): File "<stdin>", line 1, in <module> AttributeError: 'Empty' object has no attribute 'foo'
できます。
hasattr(), getattr(), setattr()
まぁ、あまり意味ないですが
>>> hasattr( e, 'bar' ) True >>> 'bar' in vars( e ) True
>>> getattr( e, 'bar' ) 'BAR' >>> vars( e ).get( 'bar' ) 'BAR'
>>> setattr( e, 'hoge', 'HOGE' ) >>> e.hoge 'HOGE' >>> vars( e )[ 'fuga' ] = 'FUGA' >>> e.fuga 'FUGA'
>>> len( dir( e ) ) 28 >>> dir( e )[ -5: ] ['__subclasshook__', '__weakref__', 'bar', 'fuga', 'hoge'] >>> list( filter( lambda s: not s.startswith( '__' ), dir( e ) ) ) ['bar', 'fuga', 'hoge']
>>> list( vars( e ).keys() ) ['hoge', 'fuga', 'bar']
ただの空のクラスなのですが、クロージャとしてクラスの代わりに多用してきました。
例えば
import empty def foo_new(add_v): d = { 'add_v': add_v, 'sum': 0 } def add(): d[ 'sum' ] += d[ 'add_v' ] d[ 'add_v' ] += 1 return d[ 'sum' ] return empty.new( locals() )
を定義して
foo = foo_new( 3 ) foo.add() foo.add() print( foo.add() )
を実行すると
12
辞書で状態を記録するのもアレなので
def bar_new(add_v): e = empty.new() e.add_v = add_v e.sum = 0 def add(): e.sum += e.add_v e.add_v += 1 return e.sum return empty.to_attr( e, locals() )
などとして
bar = bar_new( 3 ) bar.add() bar.add() print( bar.add() )
を実行すると
12
では、生成時に、指定回数add()できるようにした版に
def foo_new(add_v, init_n=0): d = { 'add_v': add_v, 'sum': 0 } def add(): d[ 'sum' ] += d[ 'add_v' ] d[ 'add_v' ] += 1 return d[ 'sum' ] for i in range( init_n ): add() return empty.new( locals() )
foo = foo_new( 3, 2 ) print( foo.add() )
12
bar_new()も同様に
def bar_new(add_v, init_n=0): e = empty.new() e.add_v = add_v e.sum = 0 def add(): e.sum += e.add_v e.add_v += 1 return e.sum for i in range( init_n ): add() return empty.to_attr( e, locals() )
bar = bar_new( 3, 2 ) print( bar.add() )
10
あれ?
そう!
bar_new()呼び出しでinit_nで2回add()実行するところまでは、 foo_new()と同様です。
bar_new()を抜ける直前の
return empty.to_attr( e, locals() )
が問題ありです。
locals() が返す辞書には { 'add_v': 3, 'init_n': 2, ... } としてキー 'add_v' があります。
to_attr() で e.add_v の値が5から3に上書きされて戻ってしまいます。
オブジェクトを生成関数の最後の処理
return empty.to_attr( e, locals() )
kon_ut の色々な場面で多用してきました。
しれっと落とし穴を回避してきてるはずですが、 改めて「わかりにくいな」と実感です。
to_attr(e, dic, **kwds) では、
最後のeへの追加で、 「既に存在するキーについては上書きしない」 という動作にすれば、良さそうです。
empty.pyに add(e, dic={}, **kwds) として、そのような関数を追加して
def bar_new(add_v, init_n=0): e = empty.new() e.add_v = add_v e.sum = 0 def add(): e.sum += e.add_v e.add_v += 1 return e.sum for i in range( init_n ): add() return empty.add( e, locals() )
bar = bar_new( 3, 2 ) print( bar.add() )
12
を今後の新たなパターンとして使っていきたいところです。
(サンプルのメソッドとしてもadd()なので、ちょとややこしいですが...)
先日の varsとオブジェクトのアトリビュート の思い違いもあり、 この際 empty.py の実装を見直す機会かと。
class Empty: pass def to_attr(e, dic, **kwds): kwds.update(dic) for (k, v) in kwds.items(): setattr(e, k, v) return e new = lambda dic={}, **kwds: to_attr( Empty(), dic, **kwds ) new_kwds = lambda **kwds: new(kwds) :
この冒頭のあたりが主要部分で、ほぼ全てです。
to_attr()のdicと**kwdsの関係を確かめてみると、
kwds.update( dic )
なので、kwdsをベースに、dicで上書きした辞書になります。
dicとkwdsでは、dicの方が強い仕様です。
逆に言うと、dicをベースに、kwdsからdicに含まれないキーの内容だけの追加と言えます。
そして、引数として与えたdic自体の内容は変化しません。
その「dicと**kwdsとの結果」で、eのアトリビュートを上書きしてます。
次のような関数を用意したら、整理しやすいでしょうか?
def get_dic(base, over_wrt): d = base.copy() d.update( over_wrt ) return d
base, over_wrtには辞書を指定します。
baseを下敷きにしてover_wrtの内容で上書きした辞書を返します。
base, over_wrt自体の内容は変化しません。
これを使うとto_attr()は、
def to_attr(e, dic={}, **kwds): vars( e ).update( get_dic( kwds, dic ) ) return e
しかし、しかし、、、
よく考えると、引数の並びからすると、dicをkwdsで上書きする印象があります。
そもそも、これまでのto_attr()の使用場面では、dicとkwdsでキーが重複するような場面は無く。
結構「テキトー」で大丈夫だった感じです。
この際、dic, kwdsは逆の上書き関係に修正した方がすっきりするし、 今なら、互換性の影響も無いかもです。
思い切って仕様を変えてしまいたいところ。
def to_attr(e, dic={}, **kwds): vars( e ).update( get_dic( dic, kwds ) ) return e
new()はそのまま
new = lambda dic={}, **kwds: to_attr( Empty(), dic, **kwds )
new_kwds()は
new_kwds = lambda **kwds: new(kwds)
これは廃止したいところですが、互換性のため
new_kwds = new
とでもして残しておきます。
そして今回追加したいadd()
新規追加なので、dicとkwdsの関係は、順番通りkwdsで上書きタイプに。
そしてto_attr()とは違って、eの既存の部分はそのまま残します。
def add(e, dic={}, **kwds): d = get_dic( get_dic( dic, kwds ), vars( e ) ) vars( e ).update( d ) return e
変更前
class Empty: pass def to_attr(e, dic, **kwds): kwds.update(dic) for (k, v) in kwds.items(): setattr(e, k, v) return e new = lambda dic={}, **kwds: to_attr( Empty(), dic, **kwds ) new_kwds = lambda **kwds: new(kwds)
変更後
class Empty: pass def get_dic(base, over_wrt): d = base.copy() d.update( over_wrt ) return d def to_attr(e, dic={}, **kwds): vars( e ).update( get_dic( dic, kwds ) ) return e new = lambda dic={}, **kwds: to_attr( Empty(), dic, **kwds ) new_kwds = new def add(e, dic={}, **kwds): d = get_dic( get_dic( dic, kwds ), vars( e ) ) vars( e ).update( d ) return e
果たして、これで問題なしか?
ダメならまた戻すかもしれません。
一旦、この仕様、実装に変更してみます。
C言語の場合代入演算子は値を返します。
古来からK&Rの記述のごとく
int ch; while ( ( ch = getchar() ) != EOF ) putchar( toupper( ch ) );
関数呼び出しの結果を変数に代入しつつ、結果による判定をして、後続の処理で関数の結果の値を参照できます。
例えば
: int ret = -1, n = 3; for ( i = 0; i < n; i++ ) if ( ( ret = foo() ) >= 0 ) break; sleep( 1 ); if ( ret < 0 ) return err( ret ); :
pythonでは変数へのバインドは、文であって式ではなく。
値は返しません。
: (ret, n) = ( -1, 3 ) for i in range( n ): ret = foo() if ret >= 0: break time.sleep( 1 ) if ret < 0: return err( ret ) :
副作用の問題もなく平和なのですが...
ret = foo() if ret >= 0:
ここ。ここを何とか
if ( ret = foo() ) >= 0:
的な事にしてみたい。
そこで、 kon_ut の empty.py モジュールのto_attr()関数。
import empty : set = empty.to_attr e = empty.new( ret=-1, n=3 ) for i in range( e.n ): if set( e, ret=foo() ).ret >= 0: break time.sleep( 1 ) if e.ret < 0: return err( e.ret ) :
さらに繰り返し変数iもアトリビュートに保持すると
set = empty.to_attr e = empty.new( ret=-1, i=0, n=3 ) while set( e, ret=foo(), i=e.i+1 ).ret < 0 and e.i < e.n: time.sleep( 1 ) if e.ret < 0: return err( e.ret ) :
決して視認性が向上したとは言えません。
むしろ、引き換えに色々と大切なものを失っている気がします。
これは以前に 簡易なYAMLパーサ 2018夏 の デバッグ修正V16 で遭遇した現象です。
原因解りません。なぜか '+=' だと通ります。当時の記述
$ python
>>> lst = ['']
>>> lst += ''
>>> lst
['']
>>> lst + ''
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: can only concatenate list (not "str") to list
>>> lst = lst + ''
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: can only concatenate list (not "str") to list
最初にリストに入ってる値は別に関係なくて、 リストに文字列の要素を追加したい場面にて。
本来は
>>> lst = [] >>> lst += [''] >>> lst ['']
とすべきところで、間違えて
>>> lst = [] >>> lst += '' >>> lst []
としてしまっても、エラーにならずに通ってしまいます。
>>> [] + '' Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: can only concatenate list (not "str") to list
だとエラーになるのに、と。
では、空文字でなければ?
>>> lst = [] >>> lst += [ 'abc' ] >>> lst ['abc']
のところを、手がすべって
>>> lst = [] >>> lst += 'abc' >>> lst ['a', 'b', 'c']
なんと!
>>> [] + 'abc' Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: can only concatenate list (not "str") to list
ですが
'abc' が ['a', 'b', 'c'] ならば
>>> [] + ['a', 'b', 'c'] ['a', 'b', 'c']
つまり変数の値がリストオブジェクトで
変数 += 文字列
ならば、文字列が1文字ごとのリストに変換されてから、 リスト同士の足し算になる?
>>> lst = [] >>> lst += 'abc' >>> lst ['a', 'b', 'c']
そして '+=' でないとこうなりません。'+' では変換されません。
>>> lst = [] >>> lst + 'abc' Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: can only concatenate list (not "str") to list
また '+=' なので、変数でないとダメ。
>>> [] += 'abc' File "<stdin>", line 1 SyntaxError: illegal expression for augmented assignment
変数でもなく、'+=' でもなければ当然ダメ。
>>> [] + 'abc' Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: can only concatenate list (not "str") to list
これは、手をすべらさないように気をつけないと。
見つけにくいバグ、要注意ですね。
threading.Event()のオブジェクトevに対して、ev.wait()で待ってるとき。
^Cキーで終了せず。ショック。
time.sleep()中や、read()ブロック中と同じように、 ^Cキーで終了できるものと思ってたのですが...
でもでも、Macなpython3の環境で試してみると、確かに^Cで止まります ???
ひょっとして止まらないのはpython2だけ?
この際、色々試してみました。
とりあえず、何でもありのプログラムを作成。
bar.py
#!/usr/bin/env python
import sys
import time
import threading
import subprocess
try:
import queue
except ImportError:
import Queue as queue
def run():
s = ' '.join( sys.argv[ 1 : ] )
print( s )
exec( s )
if __name__ == "__main__":
run()
# EOF
$ ./bar.py "time.sleep( 60 )" time.sleep( 60 ) ^CTraceback (most recent call last): File "./bar.py", line 19, in <module> run() File "./bar.py", line 16, in run exec( s ) File "<string>", line 1, in <module> KeyboardInterrupt $
python2, python3 ともに停止。
$ ./bar.py "sys.stdin.read( 1 )" sys.stdin.read( 1 ) ^CTraceback (most recent call last): File "./bar.py", line 19, in <module> run() File "./bar.py", line 16, in run exec( s ) File "<string>", line 1, in <module> KeyboardInterrupt $
python2, python3 ともに停止。
$ ./bar.py "threading.Event().wait()" threading.Event().wait() ^C^C^C^Z [1]+ Stopped ./bar.py "threading.Event().wait()" $ kill %1 $ [1]+ Terminated ./bar.py "threading.Event().wait()" $
python2 止まりません。
^Zでsuspendしてからkill。
$ ./bar.py "threading.Event().wait()" threading.Event().wait() ^CTraceback (most recent call last): File "./bar.py", line 19, in <module> run() File "./bar.py", line 16, in run exec( s ) File "<string>", line 1, in <module> File "/usr/local/Cellar/python/3.6.5/Frameworks/Python.framework/Versions/3.6/lib/python3.6/threading.py", line 551, in wait signaled = self._cond.wait(timeout) File "/usr/local/Cellar/python/3.6.5/Frameworks/Python.framework/Versions/3.6/lib/python3.6/threading.py", line 295, in wait waiter.acquire() KeyboardInterrupt $
python3 停止。
$ ./bar.py "queue.Queue().get()" queue.Queue().get() ^C^C^Z [1]+ Stopped ./bar.py "queue.Queue().get()" $ kill %1 $ [1]+ Terminated ./bar.py "queue.Queue().get()" $
python2 止まりません。
$ ./bar.py "queue.Queue().get()" queue.Queue().get() ^CTraceback (most recent call last): File "./bar.py", line 19, in <module> run() File "./bar.py", line 16, in run exec( s ) File "<string>", line 1, in <module> File "/usr/local/Cellar/python/3.6.5/Frameworks/Python.framework/Versions/3.6/lib/python3.6/queue.py", line 164, in get self.not_empty.wait() File "/usr/local/Cellar/python/3.6.5/Frameworks/Python.framework/Versions/3.6/lib/python3.6/threading.py", line 295, in wait waiter.acquire() KeyboardInterrupt $
python3 停止。
python2 はthread系は止まらないっぽい?
$ ./bar.py "l = threading.Lock(); l.acquire(); l.acquire()" l = threading.Lock(); l.acquire(); l.acquire() ^C^C^Z [1]+ Stopped ./bar.py "l = threading.Lock(); l.acquire(); l.acquire()" $ kill %1 [1]+ Stopped ./bar.py "l = threading.Lock(); l.acquire(); l.acquire()" $ [1]+ Terminated ./bar.py "l = threading.Lock(); l.acquire(); l.acquire()" $
$ ./bar.py "c = threading.Condition(); c.acquire(); c.wait()" c = threading.Condition(); c.acquire(); c.wait() ^C^C^Z [1]+ Stopped ./bar.py "c = threading.Condition(); c.acquire(); c.wait()" $ kill %1 $ [1]+ Terminated ./bar.py "c = threading.Condition(); c.acquire(); c.wait()" $
python2 止まりません。
$ ./bar.py "l = threading.Lock(); l.acquire(); l.acquire()" l = threading.Lock(); l.acquire(); l.acquire() ^CTraceback (most recent call last): File "./bar.py", line 19, in <module> run() File "./bar.py", line 16, in run exec( s ) File "<string>", line 1, in <module> KeyboardInterrupt $
$ ./bar.py "c = threading.Condition(); c.acquire(); c.wait()" c = threading.Condition(); c.acquire(); c.wait() ^CTraceback (most recent call last): File "./bar.py", line 19, in <module> run() File "./bar.py", line 16, in run exec( s ) File "<string>", line 1, in <module> File "/usr/local/Cellar/python/3.6.5/Frameworks/Python.framework/Versions/3.6/lib/python3.6/threading.py", line 295, in wait waiter.acquire() KeyboardInterrupt $
python3 停止。
やはりthread系。
そして、python3 だと全部問題なし?
$ ./bar.py "subprocess.call( 'sleep 60', shell=True )" subprocess.call( 'sleep 60', shell=True ) ^CTraceback (most recent call last): File "./bar.py", line 19, in <module> run() File "./bar.py", line 16, in run exec( s ) File "<string>", line 1, in <module> File "/usr/lib/python2.7/subprocess.py", line 523, in call return Popen(*popenargs, **kwargs).wait() File "/usr/lib/python2.7/subprocess.py", line 1392, in wait pid, sts = _eintr_retry_call(os.waitpid, self.pid, 0) File "/usr/lib/python2.7/subprocess.py", line 476, in _eintr_retry_call return func(*args) KeyboardInterrupt $
$ ./bar.py "subprocess.check_output( 'sleep 60', shell=True )" subprocess.check_output( 'sleep 60', shell=True ) ^CTraceback (most recent call last): File "./bar.py", line 19, in <module> run() File "./bar.py", line 16, in run exec( s ) File "<string>", line 1, in <module> File "/usr/lib/python2.7/subprocess.py", line 568, in check_output output, unused_err = process.communicate() File "/usr/lib/python2.7/subprocess.py", line 792, in communicate stdout = _eintr_retry_call(self.stdout.read) File "/usr/lib/python2.7/subprocess.py", line 476, in _eintr_retry_call return func(*args) KeyboardInterrupt $
python2, python3 止まります。
Popen()でwait()では?
$ ./bar.py "proc = subprocess.Popen( 'sleep 60', shell=True ); proc.wait()" proc = subprocess.Popen( 'sleep 60', shell=True ); proc.wait() ^CTraceback (most recent call last): File "./bar.py", line 19, in <module> run() File "./bar.py", line 16, in run exec( s ) File "<string>", line 1, in <module> File "/usr/lib/python2.7/subprocess.py", line 1392, in wait pid, sts = _eintr_retry_call(os.waitpid, self.pid, 0) File "/usr/lib/python2.7/subprocess.py", line 476, in _eintr_retry_call return func(*args) KeyboardInterrupt $
python2, python3 止まります。
$ ./bar.py "th = threading.Thread( target=( lambda : time.sleep( 60 ) ) ); th.start(); th.join()" th = threading.Thread( target=( lambda : time.sleep( 60 ) ) ); th.start(); th.join() ^C^C^Z [1]+ Stopped ./bar.py "th = threading.Thread( target=( lambda : time.sleep( 60 ) ) ); th.start(); th.join()" $ kill %1 [1]+ Stopped ./bar.py "th = threading.Thread( target=( lambda : time.sleep( 60 ) ) ); th.start(); th.join()" $ [1]+ Terminated ./bar.py "th = threading.Thread( target=( lambda : time.sleep( 60 ) ) ); th.start(); th.join()" $
python2 止まりません。
例えばmain threadでjoin()せずに、time.sleep()してみたら?
$ ./bar.py "th = threading.Thread( target=( lambda : time.sleep( 60 ) ) ); th.start(); time.sleep( 60 )" th = threading.Thread( target=( lambda : time.sleep( 60 ) ) ); th.start(); time.sleep( 60 ) ^CTraceback (most recent call last): File "./bar.py", line 19, in <module> run() File "./bar.py", line 16, in run exec( s ) File "<string>", line 1, in <module> KeyboardInterrupt (この間 ^C 連打) ^Z [1]+ Stopped ./bar.py "th = threading.Thread( target=( lambda : time.sleep( 60 ) ) ); th.start(); time.sleep( 60 )" $ kill %1 [1]+ Stopped ./bar.py "th = threading.Thread( target=( lambda : time.sleep( 60 ) ) ); th.start(); time.sleep( 60 )" $ [1]+ Terminated ./bar.py "th = threading.Thread( target=( lambda : time.sleep( 60 ) ) ); th.start(); time.sleep( 60 )" $
どうやら、main threadのsleep()からは抜けてきたものの、
sub thread側が粘ってるので終了せずな感じです。
そう! threadのdaemon属性がありました。
$ ./bar.py "th = threading.Thread( target=( lambda : time.sleep( 60 ) ) ); th.daemon = True; th.start(); time.sleep( 60 )" th = threading.Thread( target=( lambda : time.sleep( 60 ) ) ); th.daemon = True; th.start(); time.sleep( 60 ) ^CTraceback (most recent call last): File "./bar.py", line 19, in <module> run() File "./bar.py", line 16, in run exec( s ) File "<string>", line 1, in <module> KeyboardInterrupt $
daemon = True にしておくと、main threadが終了すると、sub threadも終了して、結果、終了できてます。
いくらdaemon = Trueにしていても、肝心のmain threadでjoin()待ちしてると
$ ./bar.py "th = threading.Thread( target=( lambda : time.sleep( 60 ) ) ); th.daemon = True; th.start(); th.join()" th = threading.Thread( target=( lambda : time.sleep( 60 ) ) ); th.daemon = True; th.start(); th.join() ^C^C^C ^Z [1]+ Stopped ./bar.py "th = threading.Thread( target=( lambda : time.sleep( 60 ) ) ); th.daemon = True; th.start(); th.join()" $ kill %1 [1]+ Stopped ./bar.py "th = threading.Thread( target=( lambda : time.sleep( 60 ) ) ); th.daemon = True; th.start(); th.join()" $ [1]+ Terminated ./bar.py "th = threading.Thread( target=( lambda : time.sleep( 60 ) ) ); th.daemon = True; th.start(); th.join()" $
main threadが終了せず。結果止まりません。
逆に、main threadさえ終了できれば、sub threadでイベント待ちしていようとも...
$ ./bar.py "th = threading.Thread( target=( lambda : threading.Event().wait() ) ); th.daemon = True; th.start(); time.sleep( 60 )" th = threading.Thread( target=( lambda : threading.Event().wait() ) ); th.daemon = True; th.start(); time.sleep( 60 ) ^CTraceback (most recent call last): File "./bar.py", line 19, in <module> run() File "./bar.py", line 16, in run exec( s ) File "<string>", line 1, in <module> KeyboardInterrupt $
終了できてます。
python3 で同様に試すと、main thread は th.join() でも time.sleep() でも止まります。
daemon = True な sub thread も、main thread が止まると、止まります。
daemon = True でない sub thread は、main thread が止まっても、止まりませんでした。
SIGINT(^Cキー)で止まるか?
package | method | python2 | python3 |
---|---|---|---|
time | sleep | ○ | ○ |
sys.stdin | read | ○ | ○ |
subprocess | call | ○ | ○ |
check_output | ○ | ○ | |
Popen | ○ | ○ | |
queue | Queue.get | × | ○ |
threading | Event.wait | × | ○ |
Lock.acquire | × | ○ | |
Condition.wait | × | ○ | |
Thread.join | × | ○ | |
Thread daemon=True | ○ | ○ | |
Thread daemon=False | × | × |
例えば
for.py
#!/usr/bin/env python def run(): n = 8 i = 0 while i < n: print( i ) i += 1 if __name__ == "__main__": run() # EOF
$ ./for.py 0 1 2 3 4 5 6 7 $
なループ処理は
def run(): n = 8 for i in range( n ): print( i )
としてfor文にして
$ ./for.py 0 1 2 3 4 5 6 7 $
同様に動作します。
listにして保持しても
def run(): n = 8 lst = list( range( n ) ) for i in lst: print( i )
$ ./for.py 0 1 2 3 4 5 6 7 $
同様に動作。
ここまでは、あたり前です。
途中でちょいと list を変更してみたら?
def run(): n = 8 lst = list( range( n ) ) for i in lst: print( i ) lst.pop()
lst.pop()でlistの末尾の要素から削除していってみると
$ ./for.py 0 1 2 3 $
前から表示しつつ、後ろから削除していくので、 listの最初の状態からすると、中ほどで終了。
という事はfor文の判定処理では、 毎回 in の後の記述箇所を参照してるはず?
例えばこれならば?
def run(): n = 8 lst = list( range( n ) ) for i in lst[ : ]: print( i ) lst.pop()
for文のinの後には lst[ : ] として、 lst のコピーを渡してみます。
$ ./for.py 0 1 2 3 4 5 6 7 $
ふむー。
inの後を毎回参照していたとしたら、 毎回「その時点」のlstの内容のコピーが作られて、それが使われているかというと...
動作結果からして、そうでは無いです。
考察するに、、、
for文実行の最初で、inの後の「オブジェクト」を「取り込み」ます。
繰り返し処理では、取り込んだオブジェクトの先頭から順に要素を返していきます。
「取り込み」は最初の1回だけですが、 取り込まれたオブジェクトと同じオブジェクトを変化させると、 for文の中の処理に影響します。
例えば、初回の取り込みでコピーしたオブジェクトを渡しておくと、 for文の繰り返し処理にはコピーした側のオブジェクトが使われるので、 オリジナルのオブジェクトを変化させても、影響はありません。
を使って、ちょっとforの真似をしてみます。
#!/usr/bin/env python import empty def for_fake_new(lst): e = empty.new() e.i = 0 e.v = None def next(): ret = e.i < len( lst ) if ret: e.v = lst[ e.i ] e.i += 1 return ret return empty.add( e, locals() ) def run(): n = 8 for_fake = for_fake_new( range( n ) ) while for_fake.next(): print( for_fake.v ) if __name__ == "__main__": run() # EOF
$ ./for.py 0 1 2 3 4 5 6 7 $
listに保持してみて
def run(): n = 8 lst = list( range( n ) ) for_fake = for_fake_new( lst ) while for_fake.next(): print( for_fake.v )
$ ./for.py 0 1 2 3 4 5 6 7 $
listを末尾から削除していくと
def run(): n = 8 lst = list( range( n ) ) for_fake = for_fake_new( lst ) while for_fake.next(): print( for_fake.v ) lst.pop()
$ ./for.py 0 1 2 3 $
最初にlistのコピーを渡しておくと
def run(): n = 8 lst = list( range( n ) ) for_fake = for_fake_new( lst[ : ] ) while for_fake.next(): print( for_fake.v ) lst.pop()
$ ./for.py 0 1 2 3 4 5 6 7 $
考察通り、同様に動作します。
zipの引数 | ◯ |
関数の引数への展開 | ◯ |
アンパック | ◯ |
for文 | ◯ |
mapの引数 | ◯ |
filterの引数 | ◯ |
スライスによる参照 | × |
numpyのarrayの引数 | × |
len | × |
boolの判定 | × |
sumの引数 | ◯ |
allとanyの引数 | ◯ |
文字列のjoin | ◯ |
dict生成 | ◯ |
まだまだありそうですが、とりあえず。
ぼちぼち追加していきます。
python2ではmap()関数はlistを返していました。
python3ではmap()関数はmapオブジェクトを返すように変わりました。
map( func, lst )
としていた箇所を全て
list( map( func, lst ) )
に書き換えるのであれば問題無いハズです。
ですが、
map( func, lst )
のままでも、問題なく動作する場合もあります。
今だに「この場合はどうだったかな?」と、 pythonインタプリタを起動して手を動かして、 簡単に確認する事しばしば。
なので、自分でよく使う場合の記録を取ってまとめておきます。
zip()自体がlistを返さなくなったので、確認がややこしい、、、
>>> zip( [ 1, 2, 3 ], [ 4, 5, 6 ] ) <zip object at 0x10a0866c8>
list()で囲えばOK
>>> list( zip( [ 1, 2, 3 ], [ 4, 5, 6 ] ) ) [(1, 4), (2, 5), (3, 6)]
引数の値をそのまま返す関数 f を用意しておきます。
>>> f = lambda i: i
zipの引数にmap()結果を渡す場合は
>>> list( zip( map( f, [ 1, 2, 3 ] ), map( f, [ 4, 5, 6] ) ) ) [(1, 4), (2, 5), (3, 6)]
OKです。
あと、zip()の引数そのものでは無いですが、よく使うパターンなので...
>>> list( zip( *map( f, [(1, 4), (2, 5), (3, 6)] ) ) ) [(1, 2, 3), (4, 5, 6)]
OKです。
先のzipの例で自明ですが、一応確認を。
>>> f_rev = lambda *lst: [ lst[ 2 ], lst[ 1 ], lst[ 0 ] ] >>> f_rev( 0, 1, 2 ) [2, 1, 0]
>>> f_rev( *map( f, [ 0, 1, 2 ] ) ) [2, 1, 0]
OKです。
>>> (a, b) = [ 1, 2 ] >>> a 1 >>> b 2
とかのやつです。
>>> (a, b) = map( f, [ 1, 2 ] ) >>> a 1 >>> b 2
OKです。
>>> for i in map( f, range( 3 ) ): ... print( i ) ... 0 1 2
OKです。
>>> list( map( f, map( f, [ 1, 2, 3 ] ) ) ) [1, 2, 3]
OKです。
>>> list( filter( lambda v: v % 2 == 0, map( f, range( 10 ) ) ) ) [0, 2, 4, 6, 8]
OKです。
>>> [ 0, 1, 2 ][ 1 ] 1 >>> [ 0, 1, 2 ][ :2 ] [0, 1]
なところですが
>>> map( f, [ 0, 1, 2 ]) [ 1 ] Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: 'map' object is not subscriptable
>>> map( f, [ 0, 1, 2 ]) [ :2 ] Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: 'map' object is not subscriptable
これがダメです。
>>> import numpy as np >>> np.array( [ 0, 1, 2 ] ) array([0, 1, 2])
なところを
>>> np.array( map( f, [ 0, 1, 2 ] ) ) array(<map object at 0x10a085978>, dtype=object)
エラーは出てませんが、ダメです。
意味が変わります。意図した結果じゃないです。
>>> len( [ 1, 2, 3 ] ) 3
>>> len( map( f, [ 1, 2, 3 ] ) ) Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: object of type 'map' has no len()
ダメです。
>>> 'T' if [] else 'F' 'F' >>> 'T' if [ 1, 2, 3 ] else 'F' 'T'
>>> 'T' if not [] else 'F' 'T' >>> 'T' if not [ 1, 2, 3 ] else 'F' 'F'
>>> bool( [] ) False >>> bool( [ 1, 2, 3 ] ) True
なやつです。
>>> 'T' if map( f, [] ) else 'F' 'T' >>> 'T' if map( f, [ 1, 2, 3 ] ) else 'F' 'T'
>>> bool( map( f, [] ) ) True >>> bool( map( f, [ 1, 2, 3 ] ) ) True
常にmapオブジェクトが返り、Noneでは無いので、Trueばかり。
ダメです。
>>> f = lambda i: i
>>> sum( map( f, [ 1, 2, 3 ] ) ) 6
>>> sum( map( f, [ [ 1, 2 ], [], [ 4, 5 ] ] ), [] ) [1, 2, 4, 5]
OKです。
>>> f = lambda i: i
>>> all( map( f, [ True, True, True ] ) ) True >>> all( map( f, [ True, True, False ] ) ) False
>>> any( map( f, [ True, True, False ] ) ) True >>> any( map( f, [ False, False, False ] ) ) False
OKです。
>>> f = lambda i: i
>>> '/'.join( map( f, [ 'foo', 'bar', 'hoge' ] ) ) 'foo/bar/hoge'
OKです。
>>> f = lambda i: i
>>> dict( map( f, [ ( 'foo', 1 ), ( 'bar', 2 ) ] ) ) {'foo': 1, 'bar': 2}
OKです。
例えば
foo.py
v = 123
このファイルfoo.pyを別のファイルでimportして参照します。
bar.py
import foo
pythonインタプリタを起動します。
$ python
foo.pyをimportして
>>> import foo
foo.pyのvの値は当然
>>> foo.v 123
です。
変更も
>>> foo.v = 0 >>> foo.v 0
可能です。
この状態からbar.py経由でfoo.pyをimportしてみると、どうか?
>>> import bar >>> bar.foo.v 0
bar.pyからimportされているfoo.pyは、 先にインタプリタから直接importしたものと同じものが見えてます。
>>> bar.foo.v = 999 >>> foo.v 999
確かに同じvが見えてます。
例えば、このようなhoge.pyを用意すると?
hoge.py
import foo
foo.v = 777
>>> foo.v 999 >>> import hoge >>> foo.v 777
なるほど。importした瞬間に値が書き換わりますね。
>>> foo.v = 'hellow' >>> bar.foo.v 'hellow' >>> hoge.foo.v 'hellow'
そして、どの経路からアクセスしても、同じものが共有されていますね。
map, filter, reduceについて
python3ではreduceがimportが必要になって、ついつい敬遠しがちになってしまいました。
mapはリスト各要素に変換をかけて、結果のリストの要素数は変わらず。 (リストと言ってしまってはアレですが...)
filterは各要素を「ふるい」にかけて、要素の値は変わらずに、要素数が減ります。
要素に変換をかけつつ、要素数を減らしたり、あるいは逆に要素数を増やしたい状況が、ちょいちょいあります。
例えば、ファイル丸ごとリードした文字列を、まず改行で区切って行のリストに。
s = f.read() lst = s.strip().split( '\n' )
その行のリストの各行について、 空白で区切って単語のリストにしたいときなど。
>>> lst = [ 'foo', 'bar hoge', '', 'fuga guha', '' ] >>> f = lambda s: s.strip().split( ' ' )
を用意して
>>> list( map( f, lst ) ) [['foo'], ['bar', 'hoge'], [''], ['fuga', 'guha'], ['']]
変換関数 f では、 要素の値である「1行分の文字列」に変換をかけて、単語の「リスト」 を返すので、まぁそうなります。
変換結果が、「必ずリストを返す」というお決まりならば。
変換結果が1つなら、要素1つのリスト。
2つなら、要素2つのリスト。
削除したいなら空のリスト。
ならば、それらのリストを sum( リスト, [] ) で合計すると、リストの平滑化。
という事で
>>> sum( map( f, lst ), [] ) ['foo', 'bar', 'hoge', '', 'fuga', 'guha', '']
もうひとこえ
>>> f = lambda s: list( filter( lambda w: w, s.strip().split( ' ' ) ) )
ならば
>>> f( 'foo' ) ['foo'] >>> f( 'abc def' ) ['abc', 'def'] >>> f( ' ' ) []
となって、変換関数は必ずリストを返します。
結果の要素数が0ならば、削除。
>>> lst ['foo', 'bar hoge', '', 'fuga guha', ''] >>> sum( map( f, lst ), [] ) ['foo', 'bar', 'hoge', 'fuga', 'guha']
となりましょう。
>>> map_sum = lambda f, lst: sum( map( f, lst ), [] )
としておいて
>>> map_sum( f, lst ) ['foo', 'bar', 'hoge', 'fuga', 'guha']
pythonのユーティリティ・プログラム 2020冬 のどこかに入れておきたい気もしますが、 なんか単純すぎて、毎回その場で定義しても良いかなと。
まず変数について。
$ python >>> def foo(): ... a = 1 ... b = 2 ... c = a + b ... print( locals() ) ... >>> foo() {'a': 1, 'c': 3, 'b': 2}
なところを、定義前に参照すると
>>> def foo(): ... a = 1 ... c = a + b ... b = 2 ... print( locals() ) ... >>> foo() Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<stdin>", line 3, in foo UnboundLocalError: local variable 'b' referenced before assignment
当然、怒られます。
内部関数でも
>>> def foo(): ... a = 1 ... b = 2 ... c = a + b ... def bar(): ... d = b + c ... bar() ... >>> foo() >>>
なところを、定義前に呼び出すと
>>> def foo(): ... a = 1 ... b = 2 ... c = a + b ... bar() ... def bar(): ... d = b + c ... >>> foo() Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<stdin>", line 5, in foo UnboundLocalError: local variable 'bar' referenced before assignment
怒られます。
このような事は?
>>> def foo(): ... def bar(): ... d = b + c ... a = 1 ... b = 2 ... c = a + b ... bar() ... >>> foo() >>>
これは通ります。
bar()を定義した時点では、bもcも定義されてません。
ですが、bar()を呼び出したときには、b, c は定義されています。
なので
>>> def foo(): ... def bar(): ... d = b + c ... bar() ... a = 1 ... b = 2 ... c = a + b ... >>> foo() Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<stdin>", line 4, in foo File "<stdin>", line 3, in bar NameError: free variable 'b' referenced before assignment in enclosing scope
これは、怒られます。
bar()を呼び出した時点で、b, cが定義されていません。
内部関数から内部関数を呼び出す場合でも
>>> def foo(): ... def bar(): ... hoge() ... bar() ... def hoge(): ... d = b + c ... a = 1 ... b = 2 ... c = a + b ... >>> foo() Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<stdin>", line 4, in foo File "<stdin>", line 3, in bar NameError: free variable 'hoge' referenced before assignment in enclosing scope
bar()を呼び出したときに、hoge()は定義されておらず、怒られます。
>>> def foo(): ... def bar(): ... hoge() ... def hoge(): ... d = b + c ... bar() ... a = 1 ... b = 2 ... c = a + b ... >>> foo() Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<stdin>", line 6, in foo File "<stdin>", line 3, in bar File "<stdin>", line 5, in hoge NameError: free variable 'b' referenced before assignment in enclosing scope
bar()呼び出したときに、hoge()は定義されているのですが、 hoge()で参照しているb, cが定義されておらず、そこで怒られます。
>>> def foo(): ... def bar(): ... hoge() ... def hoge(): ... d = b + c ... a = 1 ... b = 2 ... c = a + b ... bar() ... >>> foo() >>>
これだと、無事に通ります。
例えば、bar()の実行をタイマーで1秒後に。
a の定義を2秒後に。
>>> import threading >>> import time >>> def foo(): ... def bar(): ... b = a ... threading.Timer( 1, bar ).start() ... time.sleep( 2 ) ... a = 1 ... >>> foo() Exception in thread Thread-2: Traceback (most recent call last): File "/usr/lib/python2.7/threading.py", line 801, in __bootstrap_inner self.run() File "/usr/lib/python2.7/threading.py", line 1073, in run self.function(*self.args, **self.kwargs) File "<stdin>", line 3, in bar NameError: free variable 'a' referenced before assignment in enclosing scope
aの定義が間に合わずに怒られます。
ですが
>>> def foo(): ... def bar(): ... b = a ... threading.Timer( 1, bar ).start() ... time.sleep( 0.5 ) ... a = 1 ... >>> foo() >>>
aの定義を0.5秒後にすると、通ります。
bar()の1秒後の実行は、aの定義後になるのでOKです。
実に、3年弱ぶりの追記です。
byesと文字列は似たような物だろうと永年思っていましたが、落とし穴にはまりました。
文字列から1文字取り出したら、それは文字列
バイト列 (bytes) から1バイト取り出したら、それは整数 (int) !!!
ただし、バイト列 (bytes) から「1バイト列」を取り出したら、それはバイト列 (bytes)
>>> s = "あいう" >>> type( s ) <class 'str'> >>> s[ 1 ] 'い' >>> type( s[ 1 ] ) <class 'str'>
問題なく、今まで思っていた通りです。
>>> bt = s.encode() >>> bt b'\xe3\x81\x82\xe3\x81\x84\xe3\x81\x86' >>> type( bt ) <class 'bytes'> >>> bt[ 1 ] 129 >>> type( bt[ 1 ] ) <class 'int'>
int です!
ただし、
>>> bt[ 1 : 2 ] b'\x81' >>> type( bt[ 1 : 2 ] ) <class 'bytes'> >>> 0x81 129
こう切り出すと、1バイトでもバイト列 (bytes)
例えば、文字列のお尻から1文字ずつバッファに移動さして処理するケース
s = "foo bar" buf = "" while s != "foo": buf = s[ -1 ] + buf s = s[ : -1 ] :
s が "foo" で、buf に " bar" が貯まります。
ここで、utf-8でバイト列 (bytes) にしても同じだろう?
s = "foo bar" bt = s.encode() buf = b"" while bt != "foo".encode(): buf = bt[ -1 ] + buf bt = bt[ : -1 ] :
エラーでます!
>>> s = "foo bar" >>> bt = s.encode() >>> buf = b"" >>> while bt != "foo".encode(): ... buf = bt[ -1 ] + buf ... bt = bt[ : -1 ] ... Traceback (most recent call last): File "<stdin>", line 2, in <module> TypeError: unsupported operand type(s) for +: 'int' and 'bytes' >>>
buf = bt[ -1 : ] + buf
にすれば OK
まぁ、C言語で
char *s = "hoge"; char c = s[ 0 ]; int ch = s[ 1 ];
的な、文字列と文字の感覚からしたら、あまり違和感ない気がします。
むしろ、pythonの文字列の扱いの方こそが特殊。
$ python3 Python 3.10.6 (main, Mar 10 2023, 10:55:28) [GCC 11.3.0] on linux Type "help", "copyright", "credits" or "license" for more information. >>>