Python2とPython3での日本語文字列対応について

2019/SEP/19

目次

毎回、同じようにつまづいて、同じような感じで対応してます。

なので、いいかげん自分に判りやすいようにまとめておきます。

日本語対応といっても、ソースコード中に日本語を書くつもりはありません。

データはYAML形式のファイルで用意。

そして、YAML形式データファイル中の文字列を、UTF-8で日本語に置き換えようとして、 毎回「うぅっ」とうなってます。


結論

日本語を含む文字列処理で .format() や .join() を使いたい

Python2 では日本語を含む文字列は .encode('utf-8')して UTF-8 で保持。

Python3 では日本語の有無に関係なく、素直にstrのままでよし。

日本語を含むUTF-8のテキストを標準入力、標準出力で扱いたい

Python2 では素直に sys.stdin/sys.stdout を read/write。

Python3 では sys.stdin.buffer , sys.stdout.buffer を使う。

Python2 の世界には .buffer は存在しないので注意。

日本語を含むUTF-8のファイルをリード・ライトしたい

Python2 では素直に 'r', 'w'モードでopen()。

Python3 では 'rb', 'wb' を使う。

Python2 でも 'rb', 'wb' は使えるので、'rb', 'wb'にしておけば良いかも。

TCPソケットなどで日本語を含むテキストを送信/受信したい

.encode('utf-8') で UTF-8 にして send()

recv() した UTF-8、Python2 なら、そのまま .format(), .join() できる。

recv() した UTF-8、Python3 なら .decode('utf-8') で str に。

日本語を含むYAML形式のデータを扱いたい

UTF-8のテキストやファイルは、Python2, 3ともに yaml.load()できる。

ただし、Python3でファイルのオープンは'rb'モードで。

yaml.load()した結果のデータは、Python2では、日本語を含む文字列はunicode型、それ以外はstr型。

yaml.load()した結果のデータは、Python3では、全てstr型 (実体はunicode)。

yaml.dump()のオプションにはallow_unicode=Trueを指定する。

yaml.dump()に渡すデータは、Python2では、日本語を含む文字列はuniocde型、それ以外はstr型にしておくと、 !!python/xxx の記述が出ない。

dump()結果のYAMLデータはUTF-8エンコーディング。

yaml.dump()に渡すデータは、Python3では、全てstr型 (実体unicode) で良い。

dump()結果のYAMLデータはunicode。(dump()のオプション指定特になければ) .encode('utf-8')してからファイルに'wb'で書き込むべし。


Python2で標準入出力でやりとりする場合

自分のプログラムでよく使う、 ありがちな処理だけ対応できれば良いので...

まずはこのようなプログラムから

p2.py
#!/usr/bin/env python2

import sys
import yaml

if __name__ == "__main__":
	s = sys.stdin.read()
	d = yaml.load(s)
	foo = d.get('foo')
	bar = d.get('bar')
	d['hoge'] = foo + ' ' + bar
	d['fuga'] = 'foo={} bar={}'.format(foo, bar)
	d['guha'] = '(^_^)'.join( d.values() )
	s = yaml.dump(d, default_flow_style=False)
	sys.stdout.write(s)
# EOF

データ

dat.yaml
foo: hello
bar: world

実行してみます。

$ ./p2.py < dat.yaml > res.yaml

$ cat res.yaml
bar: world
foo: hello
fuga: foo=hello bar=world
guha: hello(^_^)world(^_^)foo=hello bar=world(^_^)hello world
hoge: hello world

ではここで、データファイルに日本語をば。

dat_u8.yaml
foo: こんちには
bar: 世界

エンコーディングはUTF-8

$ nkf -g dat_u8.yaml
UTF-8

$ hd dat_u8.yaml
00000000  66 6f 6f 3a 20 e3 81 93  e3 82 93 e3 81 a1 e3 81  |foo: ...........|
00000010  ab e3 81 af 0a 62 61 72  3a 20 e4 b8 96 e7 95 8c  |.....bar: ......|
00000020  0a                                                |.|
00000021

実行してみます。

$ ./p2.py < dat_u8.yaml > res_u8.yaml
Traceback (most recent call last):
  File "./p2.py", line 12, in <module>
    d['fuga'] = 'foo={} bar={}'.format(foo, bar)
UnicodeEncodeError: 'ascii' codec can't encode characters in position 0-4: ordinal not in range(128)

文字列の.format()による展開というか変換で、 fooやbarが保持してる値の中に128以上のものがあると、おっしゃる。

そらありますわな。

文字列のtypeを表示してみます。

p22.py
#!/usr/bin/env python2

import sys
import yaml

if __name__ == "__main__":
	s = sys.stdin.read()
	d = yaml.load(s)
	foo = d.get('foo')
	bar = d.get('bar')
	sys.stderr.write( '{}\n'.format( type(foo) ) )
	d['hoge'] = foo + ' ' + bar
	d['fuga'] = 'foo={} bar={}'.format(foo, bar)
	d['guha'] = '(^_^)'.join( d.values() )
	s = yaml.dump(d, default_flow_style=False)
	sys.stdout.write(s)
# EOF

$ diff -u p2.py p22.py
--- p2.py 2019-09-18 23:14:28.431519000 +0900
+++ p22.py 2019-09-18 23:25:38.204708000 +0900
@@ -8,6 +8,7 @@
      d = yaml.load(s)
      foo = d.get('foo')
      bar = d.get('bar')
+     sys.stderr.write( '{}\n'.format( type(foo) ) )
      d['hoge'] = foo + ' ' + bar
      d['fuga'] = 'foo={} bar={}'.format(foo, bar)
      d['guha'] = '(^_^)'.join( d.values() )
$ ./p22.py < dat_u8.yaml
<type 'unicode'>
Traceback (most recent call last):
  File "./p22.py", line 13, in <module>
    d['fuga'] = 'foo={} bar={}'.format(foo, bar)
UnicodeEncodeError: 'ascii' codec can't encode characters in position 0-4: ordinal not in range(128)

strでなくてunicode

データファイルはUTF-8。

標準入力経由で読み込んで、yaml.load()でデータに仕立てられるとunicode。

unicodeからUTF-8にエンコードしなおしてみます。

p23.py
#!/usr/bin/env python2

import sys
import yaml

if __name__ == "__main__":
	s = sys.stdin.read()
	d = yaml.load(s)

	d['foo'] = d.get('foo').encode('utf-8')
	d['bar'] = d.get('bar').encode('utf-8')

	foo = d.get('foo')
	bar = d.get('bar')
	sys.stderr.write( '{}\n'.format( type(foo) ) )
	d['hoge'] = foo + ' ' + bar
	d['fuga'] = 'foo={} bar={}'.format(foo, bar)
	d['guha'] = '(^_^)'.join( d.values() )
	s = yaml.dump(d, default_flow_style=False)
	sys.stdout.write(s)
# EOF

$ diff -u p22.py p23.py
--- p22.py 2019-09-18 23:25:38.204708000 +0900
+++ p23.py 2019-09-18 23:31:48.975535000 +0900
@@ -6,6 +6,10 @@
 if __name__ == "__main__":
      s = sys.stdin.read()
      d = yaml.load(s)
+
+     d['foo'] = d.get('foo').encode('utf-8')
+     d['bar'] = d.get('bar').encode('utf-8')
+
      foo = d.get('foo')
      bar = d.get('bar')
      sys.stderr.write( '{}\n'.format( type(foo) ) )
$ ./p23.py < dat_u8.yaml
<type 'str'>
bar: !!python/str "\u4E16\u754C"
foo: !!python/str "\u3053\u3093\u3061\u306B\u306F"
fuga: !!python/str "foo=\u3053\u3093\u3061\u306B\u306F bar=\u4E16\u754C"
guha: !!python/str "\u3053\u3093\u3061\u306B\u306F(^_^)\u4E16\u754C(^_^)foo=\u3053\
  \u3093\u3061\u306B\u306F bar=\u4E16\u754C(^_^)\u3053\u3093\u3061\u306B\u306F \u4E16\
  \u754C"
hoge: !!python/str "\u3053\u3093\u3061\u306B\u306F \u4E16\u754C"

エラーは出なくなりました。

UTF-8にエンコードするとtypeの表示はstrに。

unicodeだと、.format() でエラーでしたが、strなら問題なさそうです。

yaml.dump()によるYAML形式のデータには !!python/str の文字が。

yaml.dump()する前に、デコードしてunicodeに戻してみます。

p24.py
#!/usr/bin/env python2

import sys
import yaml

if __name__ == "__main__":
	s = sys.stdin.read()
	d = yaml.load(s)

	d['foo'] = d.get('foo').encode('utf-8')
	d['bar'] = d.get('bar').encode('utf-8')

	foo = d.get('foo')
	bar = d.get('bar')
	sys.stderr.write( '{}\n'.format( type(foo) ) )
	d['hoge'] = foo + ' ' + bar
	d['fuga'] = 'foo={} bar={}'.format(foo, bar)
	d['guha'] = '(^_^)'.join( d.values() )

	d = dict( map( lambda kv: ( kv[0], kv[1].decode('utf-8') ), d.items() ) )

	s = yaml.dump(d, default_flow_style=False)
	sys.stdout.write(s)
# EOF

$ diff -u p23.py p24.py
--- p23.py 2019-09-18 23:31:48.975535000 +0900
+++ p24.py 2019-09-18 23:33:19.082966000 +0900
@@ -16,6 +16,9 @@
      d['hoge'] = foo + ' ' + bar
      d['fuga'] = 'foo={} bar={}'.format(foo, bar)
      d['guha'] = '(^_^)'.join( d.values() )
+
+     d = dict( map( lambda kv: ( kv[0], kv[1].decode('utf-8') ), d.items() ) )
+
      s = yaml.dump(d, default_flow_style=False)
      sys.stdout.write(s)
 # EOF
$ ./p24.py < dat_u8.yaml
<type 'str'>
bar: "\u4E16\u754C"
foo: "\u3053\u3093\u3061\u306B\u306F"
fuga: "foo=\u3053\u3093\u3061\u306B\u306F bar=\u4E16\u754C"
guha: "\u3053\u3093\u3061\u306B\u306F(^_^)\u4E16\u754C(^_^)foo=\u3053\u3093\u3061\u306B\
  \u306F bar=\u4E16\u754C(^_^)\u3053\u3093\u3061\u306B\u306F \u4E16\u754C"
hoge: "\u3053\u3093\u3061\u306B\u306F \u4E16\u754C"

!!python/str の文字は消えました。

文字列のデータは \uxxxx の形式です。

yaml.dump()のオプションを調べると、allow_unicode とうそれらしいものが。

p25.py
#!/usr/bin/env python2

import sys
import yaml

if __name__ == "__main__":
	s = sys.stdin.read()
	d = yaml.load(s)

	d['foo'] = d.get('foo').encode('utf-8')
	d['bar'] = d.get('bar').encode('utf-8')

	foo = d.get('foo')
	bar = d.get('bar')
	sys.stderr.write( '{}\n'.format( type(foo) ) )
	d['hoge'] = foo + ' ' + bar
	d['fuga'] = 'foo={} bar={}'.format(foo, bar)
	d['guha'] = '(^_^)'.join( d.values() )

	d = dict( map( lambda kv: ( kv[0], kv[1].decode('utf-8') ), d.items() ) )

	s = yaml.dump(d, default_flow_style=False, allow_unicode=True)
	sys.stdout.write(s)
# EOF

$ diff -u p24.py p25.py
--- p24.py 2019-09-18 23:33:19.082966000 +0900
+++ p25.py 2019-09-18 23:39:00.230364000 +0900
@@ -19,6 +19,6 @@

      d = dict( map( lambda kv: ( kv[0], kv[1].decode('utf-8') ), d.items() ) )

-     s = yaml.dump(d, default_flow_style=False)
+     s = yaml.dump(d, default_flow_style=False, allow_unicode=True)
      sys.stdout.write(s)
 # EOF
$ ./p25.py < dat_u8.yaml
<type 'str'>
bar: 世界
foo: こんちには
fuga: foo=こんちには bar=世界
guha: こんちには(^_^)世界(^_^)foo=こんちには bar=世界(^_^)こんちには 世界
hoge: こんちには 世界

$ ./p25.py < dat_u8.yaml  > res_u8.yaml
<type 'str'>

$ nkf -g res_u8.yaml
UTF-8

お望みの形式で出力できました。

エンコーディングは確かにUTF-8です。

日本語を含まない最初のデータで確認を。

$ ./p25.py < res_u8.yaml
<type 'str'>
Traceback (most recent call last):
  File "./p25.py", line 18, in <module>
    d['guha'] = '(^_^)'.join( d.values() )
UnicodeDecodeError: 'ascii' codec can't decode byte 0xe3 in position 0: ordinal not in range(128)

おっと、これはプログラム側の問題。

出力側のように map() で一括変換しておきます。

p26.py
#!/usr/bin/env python2

import sys
import yaml

if __name__ == "__main__":
	s = sys.stdin.read()
	d = yaml.load(s)

	d = dict( map( lambda kv: ( kv[0], kv[1].encode('utf-8') ), d.items() ) )

	foo = d.get('foo')
	bar = d.get('bar')
	sys.stderr.write( '{}\n'.format( type(foo) ) )
	d['hoge'] = foo + ' ' + bar
	d['fuga'] = 'foo={} bar={}'.format(foo, bar)
	d['guha'] = '(^_^)'.join( d.values() )

	d = dict( map( lambda kv: ( kv[0], kv[1].decode('utf-8') ), d.items() ) )

	s = yaml.dump(d, default_flow_style=False, allow_unicode=True)
	sys.stdout.write(s)
# EOF

$ diff -u p25.py p26.py
--- p25.py 2019-09-18 23:39:00.230364000 +0900
+++ p26.py 2019-09-19 00:08:30.805695000 +0900
@@ -7,8 +7,7 @@
      s = sys.stdin.read()
      d = yaml.load(s)

-     d['foo'] = d.get('foo').encode('utf-8')
-     d['bar'] = d.get('bar').encode('utf-8')
+     d = dict( map( lambda kv: ( kv[0], kv[1].encode('utf-8') ), d.items() ) )

      foo = d.get('foo')
      bar = d.get('bar')
$ ./p26.py < res_u8.yaml
<type 'str'>
bar: 世界
foo: こんちには
fuga: foo=こんちには bar=世界
guha: こんちには 世界(^_^)こんちには(^_^)世界(^_^)foo=こんちには bar=世界(^_^)こんちには(^_^)世界(^_^)foo=こんちには
  bar=世界(^_^)こんちには 世界
hoge: こんちには 世界

日本語は問題なく。

元のアルファベットだけのデータで

$ ./p26.py < dat.yaml
<type 'str'>
bar: !!python/unicode 'world'
foo: !!python/unicode 'hello'
fuga: !!python/unicode 'foo=hello bar=world'
guha: !!python/unicode 'hello(^_^)world(^_^)foo=hello bar=world(^_^)hello world'
hoge: !!python/unicode 'hello world'

!!python/uniocde がついてます。

出力前の変換でtypeがどうなってるか確認してみます。

p27.py
#!/usr/bin/env python2

import sys
import yaml

if __name__ == "__main__":
	s = sys.stdin.read()
	d = yaml.load(s)

	d = dict( map( lambda kv: ( kv[0], kv[1].encode('utf-8') ), d.items() ) )

	foo = d.get('foo')
	bar = d.get('bar')
	sys.stderr.write( '{}\n'.format( type(foo) ) )
	d['hoge'] = foo + ' ' + bar
	d['fuga'] = 'foo={} bar={}'.format(foo, bar)
	d['guha'] = '(^_^)'.join( d.values() )

	sys.stderr.write( '{}\n'.format( type( d.get('foo') ) ) )
	d = dict( map( lambda kv: ( kv[0], kv[1].decode('utf-8') ), d.items() ) )
	sys.stderr.write( '{}\n'.format( type( d.get('foo') ) ) )

	s = yaml.dump(d, default_flow_style=False, allow_unicode=True)
	sys.stdout.write(s)
# EOF

$ diff -u p26.py p27.py
--- p26.py 2019-09-19 00:08:30.805695000 +0900
+++ p27.py 2019-09-19 00:23:48.656686000 +0900
@@ -16,7 +16,9 @@
      d['fuga'] = 'foo={} bar={}'.format(foo, bar)
      d['guha'] = '(^_^)'.join( d.values() )

+     sys.stderr.write( '{}\n'.format( type( d.get('foo') ) ) )
      d = dict( map( lambda kv: ( kv[0], kv[1].decode('utf-8') ), d.items() ) )
+     sys.stderr.write( '{}\n'.format( type( d.get('foo') ) ) )

      s = yaml.dump(d, default_flow_style=False, allow_unicode=True)
      sys.stdout.write(s)
$ ./p27.py < dat_u8.yaml
<type 'str'>
<type 'str'>
<type 'unicode'>
bar: 世界
foo: こんちには
fuga: foo=こんちには bar=世界
guha: こんちには(^_^)世界(^_^)foo=こんちには bar=世界(^_^)こんちには 世界
hoge: こんちには 世界
$ ./p27.py < dat.yaml
<type 'str'>
<type 'str'>
<type 'unicode'>
bar: !!python/unicode 'world'
foo: !!python/unicode 'hello'
fuga: !!python/unicode 'foo=hello bar=world'
guha: !!python/unicode 'hello(^_^)world(^_^)foo=hello bar=world(^_^)hello world'
hoge: !!python/unicode 'hello world'

データが日本語だろうと、そうでなかろうと、typeの表示は同じです。

typeは同じでも、日本語か、そうでないかで、yaml.dump() の結果に !!python/unicode がついたり、つかなかったり。

もうちょっと詳細にtypeを見てみます。

p28.py
#!/usr/bin/env python2

import sys
import yaml

def show_type(d):
	sys.stderr.write( 'foo {}, '.format( type( d.get('foo') ) ) )
	sys.stderr.write( 'bar {}\n'.format( type( d.get('bar') ) ) )

if __name__ == "__main__":
	s = sys.stdin.read()
	d = yaml.load(s)

	show_type(d)
	d = dict( map( lambda kv: ( kv[0], kv[1].encode('utf-8') ), d.items() ) )
	show_type(d)

	foo = d.get('foo')
	bar = d.get('bar')

	d['hoge'] = foo + ' ' + bar
	d['fuga'] = 'foo={} bar={}'.format(foo, bar)
	d['guha'] = '(^_^)'.join( d.values() )

	show_type(d)
	d = dict( map( lambda kv: ( kv[0], kv[1].decode('utf-8') ), d.items() ) )
	show_type(d)

	s = yaml.dump(d, default_flow_style=False, allow_unicode=True)
	sys.stdout.write(s)
# EOF

$ diff -u p27.py p28.py
--- p27.py 2019-09-19 00:23:48.656686000 +0900
+++ p28.py 2019-09-19 00:18:05.427970000 +0900
@@ -3,22 +3,28 @@
 import sys
 import yaml

+def show_type(d):
+     sys.stderr.write( 'foo {}, '.format( type( d.get('foo') ) ) )
+     sys.stderr.write( 'bar {}\n'.format( type( d.get('bar') ) ) )
+
 if __name__ == "__main__":
      s = sys.stdin.read()
      d = yaml.load(s)

+     show_type(d)
      d = dict( map( lambda kv: ( kv[0], kv[1].encode('utf-8') ), d.items() ) )
+     show_type(d)

      foo = d.get('foo')
      bar = d.get('bar')
-     sys.stderr.write( '{}\n'.format( type(foo) ) )
+
      d['hoge'] = foo + ' ' + bar
      d['fuga'] = 'foo={} bar={}'.format(foo, bar)
      d['guha'] = '(^_^)'.join( d.values() )

-     sys.stderr.write( '{}\n'.format( type( d.get('foo') ) ) )
+     show_type(d)
      d = dict( map( lambda kv: ( kv[0], kv[1].decode('utf-8') ), d.items() ) )
-     sys.stderr.write( '{}\n'.format( type( d.get('foo') ) ) )
+     show_type(d)

      s = yaml.dump(d, default_flow_style=False, allow_unicode=True)
      sys.stdout.write(s)

データは混合っぽく。

dat_mix.yaml
foo: こんちには
bar: world

$ nkf -g dat_mix.yaml
UTF-8

実行してみます。

$ ./p28.py < dat_mix.yaml
foo <type 'unicode'>, bar <type 'str'>
foo <type 'str'>, bar <type 'str'>
foo <type 'str'>, bar <type 'str'>
foo <type 'unicode'>, bar <type 'unicode'>
bar: !!python/unicode 'world'
foo: こんちには
fuga: foo=こんちには bar=world
guha: こんちには(^_^)world(^_^)foo=こんちには bar=world(^_^)こんちには world
hoge: こんちには world

yaml.load()結果の逆として、yaml.dump()を考えてみます。

日本語を含む文字列は unicode で、 そうでないものは str として用意しておけば、 !!python/xxx を含まない、お望みのYAMLデータになるのでは?

p29.py
#!/usr/bin/env python2

import sys
import yaml

def show_type(d):
	sys.stderr.write( 'foo {}, '.format( type( d.get('foo') ) ) )
	sys.stderr.write( 'bar {}\n'.format( type( d.get('bar') ) ) )

need_decode = lambda s: list( filter( lambda c: ord(c) >= 128, s ) )

my_decode = lambda s: s.decode('utf-8') if need_decode(s) else s

if __name__ == "__main__":
	s = sys.stdin.read()
	d = yaml.load(s)

	show_type(d)
	d = dict( map( lambda kv: ( kv[0], kv[1].encode('utf-8') ), d.items() ) )
	show_type(d)

	foo = d.get('foo')
	bar = d.get('bar')

	d['hoge'] = foo + ' ' + bar
	d['fuga'] = 'foo={} bar={}'.format(foo, bar)
	d['guha'] = '(^_^)'.join( d.values() )

	show_type(d)
	d = dict( map( lambda kv: ( kv[0], my_decode(kv[1]) ), d.items() ) )
	show_type(d)

	s = yaml.dump(d, default_flow_style=False, allow_unicode=True)
	sys.stdout.write(s)
# EOF

$ diff -u p28.py p29.py
--- p28.py 2019-09-19 00:18:05.427970000 +0900
+++ p29.py 2019-09-19 00:36:54.802610000 +0900
@@ -7,6 +7,10 @@
      sys.stderr.write( 'foo {}, '.format( type( d.get('foo') ) ) )
      sys.stderr.write( 'bar {}\n'.format( type( d.get('bar') ) ) )

+need_decode = lambda s: list( filter( lambda c: ord(c) >= 128, s ) )
+
+my_decode = lambda s: s.decode('utf-8') if need_decode(s) else s
+
 if __name__ == "__main__":
      s = sys.stdin.read()
      d = yaml.load(s)
@@ -23,7 +27,7 @@
      d['guha'] = '(^_^)'.join( d.values() )

      show_type(d)
-     d = dict( map( lambda kv: ( kv[0], kv[1].decode('utf-8') ), d.items() ) )
+     d = dict( map( lambda kv: ( kv[0], my_decode(kv[1]) ), d.items() ) )
      show_type(d)

      s = yaml.dump(d, default_flow_style=False, allow_unicode=True)

値が128以上のデータを含む場合だけ、デコードするようにしてみました。

$ ./p29.py < dat_mix.yaml
foo <type 'unicode'>, bar <type 'str'>
foo <type 'str'>, bar <type 'str'>
foo <type 'str'>, bar <type 'str'>
foo <type 'unicode'>, bar <type 'str'>
bar: world
foo: こんちには
fuga: foo=こんちには bar=world
guha: こんちには(^_^)world(^_^)foo=こんちには bar=world(^_^)こんちには world
hoge: こんちには world

お望みの動作となりました。

何も考えてない文字列を扱うPython2プログラムでの、 単純な日本語データ対応のまとめ

一般には、文字列処理は unicode で扱うべきとされているはずです。

文字列の長さをlen(s)で求めるときの扱いの問題や、エンコード情報を保持してないとか。

ですが自分的には、.format(), .join() が使えるかどうかが大きいので、 .encode('utf-8') で str になった状態の方がありがたいです。


Python3で標準入出力でやりとりする場合

さてPython3。

とりあえず、そのままPython3で試してみます。

p3.py
#!/usr/bin/env python3

import sys
import yaml

def show_type(d):
	sys.stderr.write( 'foo {}, '.format( type( d.get('foo') ) ) )
	sys.stderr.write( 'bar {}\n'.format( type( d.get('bar') ) ) )

need_decode = lambda s: list( filter( lambda c: ord(c) >= 128, s ) )

my_decode = lambda s: s.decode('utf-8') if need_decode(s) else s

if __name__ == "__main__":
	s = sys.stdin.read()
	d = yaml.load(s)

	show_type(d)
	d = dict( map( lambda kv: ( kv[0], kv[1].encode('utf-8') ), d.items() ) )
	show_type(d)

	foo = d.get('foo')
	bar = d.get('bar')

	d['hoge'] = foo + ' ' + bar
	d['fuga'] = 'foo={} bar={}'.format(foo, bar)
	d['guha'] = '(^_^)'.join( d.values() )

	show_type(d)
	d = dict( map( lambda kv: ( kv[0], my_decode(kv[1]) ), d.items() ) )
	show_type(d)

	s = yaml.dump(d, default_flow_style=False, allow_unicode=True)
	sys.stdout.write(s)
# EOF

$ diff -u p29.py p3.py
--- p29.py 2019-09-19 00:36:54.802610000 +0900
+++ p3.py 2019-09-19 00:42:34.875823000 +0900
@@ -1,4 +1,4 @@
-#!/usr/bin/env python2
+#!/usr/bin/env python3

 import sys
 import yaml

先のPython2での最終形 p29.py のままです。

まずは日本語を含まないデータから。

$ ./p3.py < dat.yaml
foo <class 'str'>, bar <class 'str'>
foo <class 'bytes'>, bar <class 'bytes'>
Traceback (most recent call last):
  File "./p3.py", line 25, in <module>
    d['hoge'] = foo + ' ' + bar
TypeError: can't concat bytes to str

bytes と str を連結できないですよと。

日本語を含まない文字列でも.encode('utf-8')でエンコードすると、bytes型に。

ソース中の1文字の空白' 'は str型なので、連結できないぞと。

日本語を含まない状態なので、一旦エンコードなしで。

p32.py
#!/usr/bin/env python3

import sys
import yaml

def show_type(d):
	sys.stderr.write( 'foo {}, '.format( type( d.get('foo') ) ) )
	sys.stderr.write( 'bar {}\n'.format( type( d.get('bar') ) ) )

need_decode = lambda s: list( filter( lambda c: ord(c) >= 128, s ) )

my_decode = lambda s: s.decode('utf-8') if need_decode(s) else s

if __name__ == "__main__":
	s = sys.stdin.read()
	d = yaml.load(s)

	show_type(d)
	#d = dict( map( lambda kv: ( kv[0], kv[1].encode('utf-8') ), d.items() ) )
	show_type(d)

	foo = d.get('foo')
	bar = d.get('bar')

	d['hoge'] = foo + ' ' + bar
	d['fuga'] = 'foo={} bar={}'.format(foo, bar)
	d['guha'] = '(^_^)'.join( d.values() )

	show_type(d)
	d = dict( map( lambda kv: ( kv[0], my_decode(kv[1]) ), d.items() ) )
	show_type(d)

	s = yaml.dump(d, default_flow_style=False, allow_unicode=True)
	sys.stdout.write(s)
# EOF

$ diff -u p3.py p32.py
--- p3.py 2019-09-19 00:42:34.875823000 +0900
+++ p32.py 2019-09-19 00:44:56.708300000 +0900
@@ -16,7 +16,7 @@
      d = yaml.load(s)

      show_type(d)
-     d = dict( map( lambda kv: ( kv[0], kv[1].encode('utf-8') ), d.items() ) )
+     #d = dict( map( lambda kv: ( kv[0], kv[1].encode('utf-8') ), d.items() ) )
      show_type(d)

      foo = d.get('foo')
$ ./p32.py < dat.yaml
foo <class 'str'>, bar <class 'str'>
foo <class 'str'>, bar <class 'str'>
foo <class 'str'>, bar <class 'str'>
foo <class 'str'>, bar <class 'str'>
bar: world
foo: hello
fuga: foo=hello bar=world
guha: foo=hello bar=world(^_^)world(^_^)hello(^_^)hello world
hoge: hello world
$

問題なし。

日本語のデータにしてみると。

$ ./p32.py < dat_u8.yaml
Traceback (most recent call last):
  File "./p32.py", line 16, in <module>
    d = yaml.load(s)
  File "/usr/lib/python3/dist-packages/yaml/__init__.py", line 70, in load
    loader = Loader(stream)
  File "/usr/lib/python3/dist-packages/yaml/loader.py", line 34, in __init__
    Reader.__init__(self, stream)
  File "/usr/lib/python3/dist-packages/yaml/reader.py", line 74, in __init__
    self.check_printable(stream)
  File "/usr/lib/python3/dist-packages/yaml/reader.py", line 144, in check_printable
    'unicode', "special characters are not allowed")
yaml.reader.ReaderError: unacceptable character #xdce3: special characters are not allowed
  in "<unicode string>", position 5

そもそも yaml.load() の段階でエラー。

これ知ってます。

Python3 の sys.stdin はテキストだけでバイナリはダメ。

sys.stdin.buffer を使うべし。

p33.py
#!/usr/bin/env python3

import sys
import yaml

def show_type(d):
	sys.stderr.write( 'foo {}, '.format( type( d.get('foo') ) ) )
	sys.stderr.write( 'bar {}\n'.format( type( d.get('bar') ) ) )

need_decode = lambda s: list( filter( lambda c: ord(c) >= 128, s ) )

my_decode = lambda s: s.decode('utf-8') if need_decode(s) else s

if __name__ == "__main__":
	#s = sys.stdin.read()
	s = sys.stdin.buffer.read()
	d = yaml.load(s)

	show_type(d)
	#d = dict( map( lambda kv: ( kv[0], kv[1].encode('utf-8') ), d.items() ) )
	show_type(d)

	foo = d.get('foo')
	bar = d.get('bar')

	d['hoge'] = foo + ' ' + bar
	d['fuga'] = 'foo={} bar={}'.format(foo, bar)
	d['guha'] = '(^_^)'.join( d.values() )

	show_type(d)
	d = dict( map( lambda kv: ( kv[0], my_decode(kv[1]) ), d.items() ) )
	show_type(d)

	s = yaml.dump(d, default_flow_style=False, allow_unicode=True)
	sys.stdout.write(s)
# EOF

$ diff -u p32.py p33.py
--- p32.py 2019-09-19 00:44:56.708300000 +0900
+++ p33.py 2019-09-19 00:46:46.103044000 +0900
@@ -12,7 +12,8 @@
 my_decode = lambda s: s.decode('utf-8') if need_decode(s) else s

 if __name__ == "__main__":
-     s = sys.stdin.read()
+     #s = sys.stdin.read()
+     s = sys.stdin.buffer.read()
      d = yaml.load(s)

      show_type(d)
$ ./p33.py < dat_u8.yaml
foo <class 'str'>, bar <class 'str'>
foo <class 'str'>, bar <class 'str'>
foo <class 'str'>, bar <class 'str'>
Traceback (most recent call last):
  File "./p33.py", line 31, in <module>
    d = dict( map( lambda kv: ( kv[0], my_decode(kv[1]) ), d.items() ) )
  File "./p33.py", line 31, in <lambda>
    d = dict( map( lambda kv: ( kv[0], my_decode(kv[1]) ), d.items() ) )
  File "./p33.py", line 12, in <lambda>
    my_decode = lambda s: s.decode('utf-8') if need_decode(s) else s
AttributeError: 'str' object has no attribute 'decode'

Python3のstrは、Python2のunicode。なのでtype表示は全てstr。

yaml.load()は無事成功して、文字列のゴニョゴニョもOK。

yaml.dump()前のデコードでエラー。

そもそもエンコードしてないから、デコード済のsは.decode()を持ってないと。 ごもっとも。

ということはPython3では、エンコードもデコードも不要かな。

p34.py
#!/usr/bin/env python3

import sys
import yaml

def show_type(d):
	sys.stderr.write( 'foo {}, '.format( type( d.get('foo') ) ) )
	sys.stderr.write( 'bar {}\n'.format( type( d.get('bar') ) ) )

need_decode = lambda s: list( filter( lambda c: ord(c) >= 128, s ) )

my_decode = lambda s: s.decode('utf-8') if need_decode(s) else s

if __name__ == "__main__":
	#s = sys.stdin.read()
	s = sys.stdin.buffer.read()
	d = yaml.load(s)

	show_type(d)
	#d = dict( map( lambda kv: ( kv[0], kv[1].encode('utf-8') ), d.items() ) )
	show_type(d)

	foo = d.get('foo')
	bar = d.get('bar')

	d['hoge'] = foo + ' ' + bar
	d['fuga'] = 'foo={} bar={}'.format(foo, bar)
	d['guha'] = '(^_^)'.join( d.values() )

	#show_type(d)
	#d = dict( map( lambda kv: ( kv[0], my_decode(kv[1]) ), d.items() ) )
	#show_type(d)

	s = yaml.dump(d, default_flow_style=False, allow_unicode=True)
	sys.stdout.write(s)
# EOF

$ diff -u p33.py p34.py
--- p33.py 2019-09-19 00:46:46.103044000 +0900
+++ p34.py 2019-09-19 00:48:39.927561000 +0900
@@ -27,9 +27,9 @@
      d['fuga'] = 'foo={} bar={}'.format(foo, bar)
      d['guha'] = '(^_^)'.join( d.values() )

-     show_type(d)
-     d = dict( map( lambda kv: ( kv[0], my_decode(kv[1]) ), d.items() ) )
-     show_type(d)
+     #show_type(d)
+     #d = dict( map( lambda kv: ( kv[0], my_decode(kv[1]) ), d.items() ) )
+     #show_type(d)

      s = yaml.dump(d, default_flow_style=False, allow_unicode=True)
      sys.stdout.write(s)
$ ./p34.py < dat_u8.yaml
foo <class 'str'>, bar <class 'str'>
foo <class 'str'>, bar <class 'str'>
Traceback (most recent call last):
  File "./p34.py", line 35, in <module>
    sys.stdout.write(s)
UnicodeEncodeError: 'ascii' codec can't encode characters in position 5-6: ordinal not in range(128)

Python3のstrこと、unicodeのままで無事にyaml.dump()できて。 yaml.dump()結果はUTF-8であろうか?

ああ。標準出力もバイナリの出力はだめ。

p35.py
#!/usr/bin/env python3

import sys
import yaml

def show_type(d):
	sys.stderr.write( 'foo {}, '.format( type( d.get('foo') ) ) )
	sys.stderr.write( 'bar {}\n'.format( type( d.get('bar') ) ) )

need_decode = lambda s: list( filter( lambda c: ord(c) >= 128, s ) )

my_decode = lambda s: s.decode('utf-8') if need_decode(s) else s

if __name__ == "__main__":
	#s = sys.stdin.read()
	s = sys.stdin.buffer.read()
	d = yaml.load(s)

	show_type(d)
	#d = dict( map( lambda kv: ( kv[0], kv[1].encode('utf-8') ), d.items() ) )
	show_type(d)

	foo = d.get('foo')
	bar = d.get('bar')

	d['hoge'] = foo + ' ' + bar
	d['fuga'] = 'foo={} bar={}'.format(foo, bar)
	d['guha'] = '(^_^)'.join( d.values() )

	#show_type(d)
	#d = dict( map( lambda kv: ( kv[0], my_decode(kv[1]) ), d.items() ) )
	#show_type(d)

	s = yaml.dump(d, default_flow_style=False, allow_unicode=True)
	#sys.stdout.write(s)
	sys.stdout.buffer.write(s)
# EOF

$ diff -u p34.py p35.py
--- p34.py 2019-09-19 00:48:39.927561000 +0900
+++ p35.py 2019-09-19 00:49:26.196358000 +0900
@@ -32,5 +32,6 @@
      #show_type(d)

      s = yaml.dump(d, default_flow_style=False, allow_unicode=True)
-     sys.stdout.write(s)
+     #sys.stdout.write(s)
+     sys.stdout.buffer.write(s)
 # EOF
$ ./p35.py < dat_u8.yaml
foo <class 'str'>, bar <class 'str'>
foo <class 'str'>, bar <class 'str'>
Traceback (most recent call last):
  File "./p35.py", line 36, in <module>
    sys.stdout.buffer.write(s)
TypeError: a bytes-like object is required, not 'str'

となると、yaml.dump()結果はUTF-8じゃなくてunicodeかな。

yaml.dump()のオプションもあるような気がするけど、 とりあえず明示的にUTF-8にエンコードしてみます。

p36.py
#!/usr/bin/env python3

import sys
import yaml

def show_type(d):
	sys.stderr.write( 'foo {}, '.format( type( d.get('foo') ) ) )
	sys.stderr.write( 'bar {}\n'.format( type( d.get('bar') ) ) )

need_decode = lambda s: list( filter( lambda c: ord(c) >= 128, s ) )

my_decode = lambda s: s.decode('utf-8') if need_decode(s) else s

if __name__ == "__main__":
	#s = sys.stdin.read()
	s = sys.stdin.buffer.read()
	d = yaml.load(s)

	show_type(d)
	#d = dict( map( lambda kv: ( kv[0], kv[1].encode('utf-8') ), d.items() ) )
	show_type(d)

	foo = d.get('foo')
	bar = d.get('bar')

	d['hoge'] = foo + ' ' + bar
	d['fuga'] = 'foo={} bar={}'.format(foo, bar)
	d['guha'] = '(^_^)'.join( d.values() )

	#show_type(d)
	#d = dict( map( lambda kv: ( kv[0], my_decode(kv[1]) ), d.items() ) )
	#show_type(d)

	s = yaml.dump(d, default_flow_style=False, allow_unicode=True)
	#sys.stdout.write(s)
	s = s.encode('utf-8')
	sys.stdout.buffer.write(s)
# EOF

$ diff -u p35.py p36.py
--- p35.py 2019-09-19 00:49:26.196358000 +0900
+++ p36.py 2019-09-19 00:51:37.067715000 +0900
@@ -33,5 +33,6 @@

      s = yaml.dump(d, default_flow_style=False, allow_unicode=True)
      #sys.stdout.write(s)
+     s = s.encode('utf-8')
      sys.stdout.buffer.write(s)
 # EOF
$ ./p36.py < dat_u8.yaml
foo <class 'str'>, bar <class 'str'>
foo <class 'str'>, bar <class 'str'>
bar: 世界
foo: こんちには
fuga: foo=こんちには bar=世界
guha: こんちには 世界(^_^)こんちには(^_^)世界(^_^)foo=こんちには bar=世界
hoge: こんちには 世界

これでようやく。お望みの結果。

混合やアルファベットだけでも確認。

$ ./p36.py < dat_mix.yaml | nkf -j
foo <class 'str'>, bar <class 'str'>
foo <class 'str'>, bar <class 'str'>
bar: world
foo: こんちには
fuga: foo=こんちには bar=world
guha: こんちには world(^_^)world(^_^)こんちには(^_^)foo=こんちには bar=world
hoge: こんちには world
$ ./p36.py < dat.yaml
foo <class 'str'>, bar <class 'str'>
foo <class 'str'>, bar <class 'str'>
bar: world
foo: hello
fuga: foo=hello bar=world
guha: hello(^_^)foo=hello bar=world(^_^)world(^_^)hello world
hoge: hello world

OK。

何も考えてない文字列を扱うPython3プログラムでの、 単純な日本語データ対応のまとめ


Python2でファイルのリード/ライトやソケットでやりとりする場合

ファイルをオープンしてデータをリードしたり、 TCPソケットでsend(), recv()でデータをやりとりする場合についてです。

プロセス間をソケットでデータのやりとり。よくやるパターンです。

p200.py
#!/usr/bin/env python2

import sys
import yaml
import socket
import threading

def func(port, f_out):
	ss = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
	ss.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
	ss.bind(('', port))
	ss.listen(5)
	(fd, adr) = ss.accept()
	s = fd.recv(1024)
	f_out.write(s)
	f_out.close()
	fd.close()
	ss.close()

if __name__ == "__main__":
	if len(sys.argv) < 3:
		sys.stderr.write('Usage: {} input.yaml output.yaml\n'.format(sys.argv[0]))
		sys.exit(1)

	f = open(sys.argv[1], 'r')
	d = yaml.load(f)
	f.close()

	foo = d.get('foo')
	bar = d.get('bar')
	d['hoge'] = foo + ' ' + bar
	d['fuga'] = 'foo={} bar={}'.format(foo, bar)
	d['guha'] = '(^_^)'.join( d.values() )

	s = yaml.dump(d, default_flow_style=False, allow_unicode=True)

	port = 11233
	f_out = open(sys.argv[2], 'w')
	th = threading.Thread( target=func, args=(port, f_out) )
	th.daemon = True
	th.start()

	fd = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
	fd.connect(('localhost', port))
	fd.sendall(s)
	th.join()
# EOF

$ ./p200.py dat.yaml res.yaml

$ cat res.yaml
bar: world
foo: hello
fuga: foo=hello bar=world
guha: hello(^_^)world(^_^)foo=hello bar=world(^_^)hello world
hoge: hello world

問題なし。

では日本語のデータで。

$ ./p200.py dat_u8.yaml res_u8.yaml
Traceback (most recent call last):
  File "./p200.py", line 32, in <module>
    d['fuga'] = 'foo={} bar={}'.format(foo, bar)
UnicodeEncodeError: 'ascii' codec can't encode characters in position 0-4: ordinal not in range(128)

まぁそうですね。Python2でのsys.stdinのときと同じ。

Python2の標準入出力版の最終形の対策を考慮して。

p202.py
#!/usr/bin/env python2

import sys
import yaml
import socket
import threading

need_decode = lambda s: list( filter( lambda c: ord(c) >= 128, s ) )

my_decode = lambda s: s.decode('utf-8') if need_decode(s) else s

def func(port, f_out):
	ss = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
	ss.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
	ss.bind(('', port))
	ss.listen(5)
	(fd, adr) = ss.accept()
	s = fd.recv(1024)
	f_out.write(s)
	f_out.close()
	fd.close()
	ss.close()

if __name__ == "__main__":
	if len(sys.argv) < 3:
		sys.stderr.write('Usage: {} input.yaml output.yaml\n'.format(sys.argv[0]))
		sys.exit(1)

	f = open(sys.argv[1], 'r')
	d = yaml.load(f)
	f.close()

	d = dict( map( lambda kv: ( kv[0], kv[1].encode('utf-8') ), d.items() ) )

	foo = d.get('foo')
	bar = d.get('bar')
	d['hoge'] = foo + ' ' + bar
	d['fuga'] = 'foo={} bar={}'.format(foo, bar)
	d['guha'] = '(^_^)'.join( d.values() )

	d = dict( map( lambda kv: ( kv[0], my_decode(kv[1]) ), d.items() ) )

	s = yaml.dump(d, default_flow_style=False, allow_unicode=True)

	port = 11233
	f_out = open(sys.argv[2], 'w')
	th = threading.Thread( target=func, args=(port, f_out) )
	th.daemon = True
	th.start()

	fd = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
	fd.connect(('localhost', port))
	fd.sendall(s)
	th.join()
# EOF

$ diff -u p200.py p202.py
--- p200.py 2019-09-19 00:01:16.435258000 +0900
+++ p202.py 2019-09-19 00:39:59.999188000 +0900
@@ -5,6 +5,10 @@
 import socket
 import threading

+need_decode = lambda s: list( filter( lambda c: ord(c) >= 128, s ) )
+
+my_decode = lambda s: s.decode('utf-8') if need_decode(s) else s
+
 def func(port, f_out):
      ss = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
      ss.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
@@ -26,12 +30,16 @@
      d = yaml.load(f)
      f.close()

+     d = dict( map( lambda kv: ( kv[0], kv[1].encode('utf-8') ), d.items() ) )
+
      foo = d.get('foo')
      bar = d.get('bar')
      d['hoge'] = foo + ' ' + bar
      d['fuga'] = 'foo={} bar={}'.format(foo, bar)
      d['guha'] = '(^_^)'.join( d.values() )

+     d = dict( map( lambda kv: ( kv[0], my_decode(kv[1]) ), d.items() ) )
+
      s = yaml.dump(d, default_flow_style=False, allow_unicode=True)

      port = 11233

実行。

$ ./p202.py dat_u8.yaml res_u8.yaml

$ cat res_u8.yaml
bar: 世界
foo: こんちには
fuga: foo=こんちには bar=世界
guha: こんちには(^_^)世界(^_^)foo=こんちには bar=世界(^_^)こんちには 世界
hoge: こんちには 世界
$ ./p202.py dat.yaml res.yaml

$ cat res.yaml
bar: world
foo: hello
fuga: foo=hello bar=world
guha: hello(^_^)world(^_^)foo=hello bar=world(^_^)hello world
hoge: hello world
$ ./p202.py dat_mix.yaml res_mix.yaml

$ cat res_mix.yaml
bar: world
foo: こんちには
fuga: foo=こんちには bar=world
guha: こんちには(^_^)world(^_^)foo=こんちには bar=world(^_^)こんちには world
hoge: こんちには world

Python2では、ファイルやソケットの場合でも、標準入出力の場合と同じ対応でよし。


Python3でファイルのリード/ライトやソケットでやりとりする場合

まずはそのままでPython3で実行してみます。

p300.py
#!/usr/bin/env python3

import sys
import yaml
import socket
import threading

def func(port, f_out):
	ss = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
	ss.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
	ss.bind(('', port))
	ss.listen(5)
	(fd, adr) = ss.accept()
	s = fd.recv(1024)
	f_out.write(s)
	f_out.close()
	fd.close()
	ss.close()

if __name__ == "__main__":
	if len(sys.argv) < 3:
		sys.stderr.write('Usage: {} input.yaml output.yaml\n'.format(sys.argv[0]))
		sys.exit(1)

	f = open(sys.argv[1], 'r')
	d = yaml.load(f)
	f.close()

	foo = d.get('foo')
	bar = d.get('bar')
	d['hoge'] = foo + ' ' + bar
	d['fuga'] = 'foo={} bar={}'.format(foo, bar)
	d['guha'] = '(^_^)'.join( d.values() )

	s = yaml.dump(d, default_flow_style=False, allow_unicode=True)

	port = 11233
	f_out = open(sys.argv[2], 'w')
	th = threading.Thread( target=func, args=(port, f_out) )
	th.daemon = True
	th.start()

	fd = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
	fd.connect(('localhost', port))
	fd.sendall(s)
	th.join()
# EOF

$ diff -u p200.py p300.py
--- p200.py 2019-09-19 00:01:16.435258000 +0900
+++ p300.py 2019-09-19 00:54:02.517980000 +0900
@@ -1,4 +1,4 @@
-#!/usr/bin/env python2
+#!/usr/bin/env python3

 import sys
 import yaml
$ ./p300.py dat.yaml res.yaml
Traceback (most recent call last):
  File "./p300.py", line 45, in <module>
    fd.sendall(s)
TypeError: a bytes-like object is required, not 'str'

Python3のソケットのsend()はstrだめ。bytes。

という事で.encode('utf-8')で。

p302.py
#!/usr/bin/env python3

import sys
import yaml
import socket
import threading

def func(port, f_out):
	ss = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
	ss.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
	ss.bind(('', port))
	ss.listen(5)
	(fd, adr) = ss.accept()
	s = fd.recv(1024)
	f_out.write(s)
	f_out.close()
	fd.close()
	ss.close()

if __name__ == "__main__":
	if len(sys.argv) < 3:
		sys.stderr.write('Usage: {} input.yaml output.yaml\n'.format(sys.argv[0]))
		sys.exit(1)

	f = open(sys.argv[1], 'r')
	d = yaml.load(f)
	f.close()

	foo = d.get('foo')
	bar = d.get('bar')
	d['hoge'] = foo + ' ' + bar
	d['fuga'] = 'foo={} bar={}'.format(foo, bar)
	d['guha'] = '(^_^)'.join( d.values() )

	s = yaml.dump(d, default_flow_style=False, allow_unicode=True)

	port = 11233
	f_out = open(sys.argv[2], 'w')
	th = threading.Thread( target=func, args=(port, f_out) )
	th.daemon = True
	th.start()

	fd = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
	fd.connect(('localhost', port))
	s = s.encode('utf-8')
	fd.sendall(s)
	th.join()
# EOF

--- p300.py 2019-09-19 00:54:02.517980000 +0900
+++ p302.py 2019-09-19 00:56:21.246919000 +0900
@@ -42,6 +42,7 @@

      fd = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
      fd.connect(('localhost', port))
+     s = s.encode('utf-8')
      fd.sendall(s)
      th.join()
 # EOF
$ ./p302.py dat.yaml res.yaml
Exception in thread Thread-1:
Traceback (most recent call last):
  File "/usr/lib/python3.5/threading.py", line 914, in _bootstrap_inner
    self.run()
  File "/usr/lib/python3.5/threading.py", line 862, in run
    self._target(*self._args, **self._kwargs)
  File "./p302.py", line 15, in func
    f_out.write(s)
TypeError: write() argument must be str, not bytes

ソケットのsend()もrecv()もエラーなしですが、 ファイルのwrite()でエラー。

recv()でUTF-8のbytes受け取って、そのままファイルに書けない。

ということは decode() で unicode にしてから write()

p303.py
#!/usr/bin/env python3

import sys
import yaml
import socket
import threading

def func(port, f_out):
	ss = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
	ss.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
	ss.bind(('', port))
	ss.listen(5)
	(fd, adr) = ss.accept()
	s = fd.recv(1024)
	s = s.decode('utf-8')
	f_out.write(s)
	f_out.close()
	fd.close()
	ss.close()

if __name__ == "__main__":
	if len(sys.argv) < 3:
		sys.stderr.write('Usage: {} input.yaml output.yaml\n'.format(sys.argv[0]))
		sys.exit(1)

	f = open(sys.argv[1], 'r')
	d = yaml.load(f)
	f.close()

	foo = d.get('foo')
	bar = d.get('bar')
	d['hoge'] = foo + ' ' + bar
	d['fuga'] = 'foo={} bar={}'.format(foo, bar)
	d['guha'] = '(^_^)'.join( d.values() )

	s = yaml.dump(d, default_flow_style=False, allow_unicode=True)

	port = 11233
	f_out = open(sys.argv[2], 'w')
	th = threading.Thread( target=func, args=(port, f_out) )
	th.daemon = True
	th.start()

	fd = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
	fd.connect(('localhost', port))
	s = s.encode('utf-8')
	fd.sendall(s)
	th.join()
# EOF

$ diff -u p302.py p303.py
--- p302.py 2019-09-19 00:56:21.246919000 +0900
+++ p303.py 2019-09-19 00:57:33.111471000 +0900
@@ -12,6 +12,7 @@
      ss.listen(5)
      (fd, adr) = ss.accept()
      s = fd.recv(1024)
+     s = s.decode('utf-8')
      f_out.write(s)
      f_out.close()
      fd.close()
$ ./p303.py dat.yaml res.yaml

$ cat res.yaml
bar: world
foo: hello
fuga: foo=hello bar=world
guha: hello(^_^)hello world(^_^)world(^_^)foo=hello bar=world
hoge: hello world

Python3で日本語なし版でお望みの結果。

では日本語で。

$ ./p303.py dat_u8.yaml res_u8.yaml
Traceback (most recent call last):
  File "./p303.py", line 27, in <module>
    d = yaml.load(f)
  File "/usr/lib/python3/dist-packages/yaml/__init__.py", line 70, in load
    loader = Loader(stream)
  File "/usr/lib/python3/dist-packages/yaml/loader.py", line 34, in __init__
    Reader.__init__(self, stream)
  File "/usr/lib/python3/dist-packages/yaml/reader.py", line 85, in __init__
    self.determine_encoding()
  File "/usr/lib/python3/dist-packages/yaml/reader.py", line 124, in determine_encoding
    self.update_raw()
  File "/usr/lib/python3/dist-packages/yaml/reader.py", line 178, in update_raw
    data = self.stream.read(size)
  File "/usr/lib/python3.5/encodings/ascii.py", line 26, in decode
    return codecs.ascii_decode(input, self.errors)[0]
UnicodeDecodeError: 'ascii' codec can't decode byte 0xe3 in position 5: ordinal not in range(128)

これ、知ってます。

標準入力では sys.stdin.buffer でバイナリでした。

ファイル・オープンのモードを'r'でなくて'rb'で。

ついでに出力側も'w'でなくて'wb'で。

p304.py
#!/usr/bin/env python3

import sys
import yaml
import socket
import threading

def func(port, f_out):
	ss = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
	ss.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
	ss.bind(('', port))
	ss.listen(5)
	(fd, adr) = ss.accept()
	s = fd.recv(1024)
	s = s.decode('utf-8')
	f_out.write(s)
	f_out.close()
	fd.close()
	ss.close()

if __name__ == "__main__":
	if len(sys.argv) < 3:
		sys.stderr.write('Usage: {} input.yaml output.yaml\n'.format(sys.argv[0]))
		sys.exit(1)

	f = open(sys.argv[1], 'rb')
	d = yaml.load(f)
	f.close()

	foo = d.get('foo')
	bar = d.get('bar')
	d['hoge'] = foo + ' ' + bar
	d['fuga'] = 'foo={} bar={}'.format(foo, bar)
	d['guha'] = '(^_^)'.join( d.values() )

	s = yaml.dump(d, default_flow_style=False, allow_unicode=True)

	port = 11233
	f_out = open(sys.argv[2], 'wb')
	th = threading.Thread( target=func, args=(port, f_out) )
	th.daemon = True
	th.start()

	fd = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
	fd.connect(('localhost', port))
	s = s.encode('utf-8')
	fd.sendall(s)
	th.join()
# EOF

$ diff -u p303.py p304.py
--- p303.py 2019-09-19 00:57:33.111471000 +0900
+++ p304.py 2019-09-19 00:59:10.391487000 +0900
@@ -23,7 +23,7 @@
        sys.stderr.write('Usage: {} input.yaml output.yaml\n'.format(sys.argv[0]))
        sys.exit(1)

-     f = open(sys.argv[1], 'r')
+     f = open(sys.argv[1], 'rb')
      d = yaml.load(f)
      f.close()

@@ -36,7 +36,7 @@
      s = yaml.dump(d, default_flow_style=False, allow_unicode=True)

      port = 11233
-     f_out = open(sys.argv[2], 'w')
+     f_out = open(sys.argv[2], 'wb')
      th = threading.Thread( target=func, args=(port, f_out) )
      th.daemon = True
      th.start()
$ ./p304.py dat_u8.yaml res_u8.yaml
Exception in thread Thread-1:
Traceback (most recent call last):
  File "/usr/lib/python3.5/threading.py", line 914, in _bootstrap_inner
    self.run()
  File "/usr/lib/python3.5/threading.py", line 862, in run
    self._target(*self._args, **self._kwargs)
  File "./p304.py", line 16, in func
    f_out.write(s)
TypeError: a bytes-like object is required, not 'str'

なー。

'w'モードならstrで書き込みなので、.decode()してたけど。

'wb'モードならbytesにしないとダメ。

ということで、やっぱり.decode()なしで。

p305.py
#!/usr/bin/env python3

import sys
import yaml
import socket
import threading

def func(port, f_out):
	ss = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
	ss.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
	ss.bind(('', port))
	ss.listen(5)
	(fd, adr) = ss.accept()
	s = fd.recv(1024)
	#s = s.decode('utf-8')
	f_out.write(s)
	f_out.close()
	fd.close()
	ss.close()

if __name__ == "__main__":
	if len(sys.argv) < 3:
		sys.stderr.write('Usage: {} input.yaml output.yaml\n'.format(sys.argv[0]))
		sys.exit(1)

	f = open(sys.argv[1], 'rb')
	d = yaml.load(f)
	f.close()

	foo = d.get('foo')
	bar = d.get('bar')
	d['hoge'] = foo + ' ' + bar
	d['fuga'] = 'foo={} bar={}'.format(foo, bar)
	d['guha'] = '(^_^)'.join( d.values() )

	s = yaml.dump(d, default_flow_style=False, allow_unicode=True)

	port = 11233
	f_out = open(sys.argv[2], 'wb')
	th = threading.Thread( target=func, args=(port, f_out) )
	th.daemon = True
	th.start()

	fd = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
	fd.connect(('localhost', port))
	s = s.encode('utf-8')
	fd.sendall(s)
	th.join()
# EOF

$ diff -u p304.py p305.py
--- p304.py 2019-09-19 00:59:10.391487000 +0900
+++ p305.py 2019-09-19 01:03:01.256765000 +0900
@@ -12,7 +12,7 @@
      ss.listen(5)
      (fd, adr) = ss.accept()
      s = fd.recv(1024)
-     s = s.decode('utf-8')
+     #s = s.decode('utf-8')
      f_out.write(s)
      f_out.close()
      fd.close()
$ ./p305.py dat_u8.yaml res_u8.yaml

$ cat res_u8.yaml
bar: 世界
foo: こんちには
fuga: foo=こんちには bar=世界
guha: 世界(^_^)foo=こんちには bar=世界(^_^)こんちには(^_^)こんちには 世界
hoge: こんちには 世界
$ ./p305.py dat.yaml res.yaml

$ cat res.yaml
bar: world
foo: hello
fuga: foo=hello bar=world
guha: foo=hello bar=world(^_^)hello(^_^)hello world(^_^)world
hoge: hello world
$ ./p305.py dat_mix.yaml res_mix.yaml

$ cat res_mix.yaml
bar: world
foo: こんちには
fuga: foo=こんちには bar=world
guha: こんちには(^_^)こんちには world(^_^)foo=こんちには bar=world(^_^)world
hoge: こんちには world

お望みの結果。

Python3でファイルやソケットの場合の対応