2023年2月12日日曜日

Python書式:全角文字の幅問題 [ Python ]

いやー、久しぶりにPythonやってみましたよ。
プログラミングたのしいね〜!

今回、print()関数で文字を揃えようと思って、"%-15s"みたいな書式を指定したわけですよ!!
そうしたら、なんと!
全角と半角が混在していると、見た目が崩れてしまう問題がはっせい!!

全角文字幅が1文字扱いのようです!!!

「全角文字も揃えたい」ですよね〜


<<追記>>


member.py
member = [ { "name":"あいうえ", "weight":90, "height":177 }, { "name":"Super 真理雄", "weight":250, "height":188 }, { "name":"Ninja", "weight":70, "height":160 }, { "name":"Mr.エックス", "weight":66, "height":167 }, ] for m in member: print("%-15s : %5dKg %5dcm" % (m["name"], m["weight"], m["height"]))
全角文字揃えが崩れちゃう
あいうえ : 90Kg 177cm Super 真理雄 : 250Kg 188cm Ninja : 70Kg 160cm Mr.エックス : 66Kg 167cm

困りました!てなわけで、世の中の人に聞いてみると!!
note.nkmk.meさんが教えてくれていました!感謝ですな★

なんと!unicodedata.east_asian_width()を使うと素敵になれるようです!

ただ、この関数は全角・半角というのを直接教えてくれるわけではないのです。
いろいろ複雑な事情があるようです・・・
まあ、そこは良いとして、とにかく "F" "W" "A" なら全角、それ以外なら半角ということで判断しましょう!!

よって、全角文字なら幅をもう1文字分必要とするように引き算してやればOKですな!
member.py
from unicodedata import east_asian_width from functools import reduce member = [ { "name":"あいうえ", "weight":90, "height":177 }, { "name":"Super 真理雄", "weight":250, "height":188 }, { "name":"Ninja", "weight":70, "height":160 }, { "name":"Mr.エックス", "weight":66, "height":167 }, ] for m in member: name_w = reduce(lambda a, c: east_asian_width(c) in "FWA" and a-1 or a, m["name"], 15) print("%-*s : %5dKg %5dcm" % (name_w, m["name"], m["weight"], m["height"]))
すばらしい!そろってる!
あいうえ : 90Kg 177cm Super 真理雄 : 250Kg 188cm Ninja : 70Kg 160cm Mr.エックス : 66Kg 167cm

じゃじゃ〜ん!!
素敵になりました!!!

forループとか書くとコードが長くなるので、reduce()を使いました。
これは、functoolsモジュールに入ってます。(python2の頃は組み込み関数だったらしい)

reduce()の行はちょっとゴチャッとしてますが、一行でかけるので、コードの流れがスマートになって良いと思います。(自画自賛)


ポイントは、書式幅も引数(タプル)で指定してやることですぞ!
"%-*s"の"*"が肝です!!


あっ!今気づいたけど、サンプルコードの身長と体重の書式を整数にしてしまった・・・
まあいいや。なおすのめんどい・・・・


もうちょっとすすめて、ラムダ式で関数にしてみた。

member.py
from unicodedata import east_asian_width from functools import reduce member = [ { "name":"あいうえ", "weight":90, "height":177 }, { "name":"Super 真理雄", "weight":250, "height":188 }, { "name":"Ninja", "weight":70, "height":160 }, { "name":"Mr.エックス", "weight":66, "height":167 }, ] withw = lambda s,w:(reduce(lambda a, c: east_asian_width(c) in "FWA" and a-1 or a, s, w), s) for m in member: print("%-*s : %5dKg %5dcm" % (*withw(m["name"], 15) , m["weight"], m["height"]))

このほうが使いやすいな!
ポイントは、このwithw()関数 がタプル ( 文字幅 ,  文字列 ) を返すので、で展開してやるのだ!

う〜ん。まんぞく!
めでたしめでたし!!



<追記>
いやいや、よく考えたら文字幅分をスペースで埋めとけば良かったんじゃね?
アホでした。

member.py
from unicodedata import east_asian_width from functools import reduce member = [ { "name":"あいうえ", "weight":90, "height":177 }, { "name":"Super 真理雄", "weight":250, "height":188 }, { "name":"Ninja", "weight":70, "height":160 }, { "name":"Mr.エックス", "weight":66, "height":167 }, ] padstrl = lambda s,w: reduce(lambda a, c: c+a[:east_asian_width(c) in "FWA" and -2 or -1], s[::-1], " "*w) padstrr = lambda s,w: reduce(lambda a, c: a[east_asian_width(c) in "FWA" and 2 or 1:]+c, s, " "*w) for m in member: print(f'{padstrl(m["name"], 15)} : {m["weight"]:5}Kg {m["height"]:5}cm') print("-"*50) for m in member: print(f'{padstrr(m["name"], 15)} : {m["weight"]:5}Kg {m["height"]:5}cm')

前回のwithw()関数の場合はPythonの書式の方で左寄せと右寄せを指定できたけど、
今回のラムダ式で左寄せと右寄せを一つの式にするのは、
俺の頭では短くかけないので、2つに分けた!


ちなみに、f"" 書式のこともすっかり忘れてました(汗)




ふんどしの持ち主

0 件のコメント:

コメントを投稿