2023年3月3日金曜日

できるかな Python:全角文字でもPython書式を揃えたい [ Python ]

[ できるかな Python ] のコーナー


ここでは実用になるかはさておき、Pythonで出来たら楽しいって事をやってみるだけのコーナーです。

前回前々回と全角文字が揃わない問題を取り扱ってきたのだが!!
とうとう、禁断のクラスを作ることに手を染めてしまった・・・

strをサブクラス化してみるという禁断の手に!!

だって、__format__()とかオーバーライドしてみたいじゃん!

で、割とうまく言ったのだが!!
内部で使った正規表現が。。。いまいち自信ないね(笑)
まあ、動いてるっぽいよ未来の俺!


で、今回のレシピはこれ!

<材料>
  • str型
  • east_asian_width()
  • 特殊メソッド__format__()
  • 特殊メソッド__add__()
  • 特殊メソッド__radd__()

<作り方のポイント>
  • __format__()メソッドで全角文字の時に対応する
  • __add__()および__radd__()をオーバーライドして文字列の結合に対応する
  • east_asian_width()で全角文字を判断する
  • formatを正規表現を使って上手に料理する ※これは保証できない(笑)


stringw.py
from __future__ import annotations # 自分自身のクラスを型注釈できるように(必ずソースの先頭) from unicodedata import east_asian_width from functools import reduce from typing import TypeVar, Generic, Callable import re class StringW(str): @property def length(self) -> int: return len(self) @property def width(self) -> int: # Japanese wide-char size is calculated as 2 bytes. return reduce(lambda a, c: a + 1 + (east_asian_width(c) in "FWA"), self, 0) @property def diff(self) -> int: return self.width - self.length def __add__(self, other: StringW) -> StringW: return StringW(super().__add__(other)) def __radd__(self, other: StringW) -> StringW: return StringW(other.__add__(self)) def __format__(self, format: str) -> str: formatreg = r"((?:\S?[<>=^])?[-+ ]?z?#?0?)(\d*)([_,]?\.?\d*\w?)" m = re.match(formatreg, format) def convert(m): if m[2]: width = int(m[2]) - self.diff if not width < 0: return f"{m[1]}{str(width)}{m[3]}" return m[0] newformat = re.sub(formatreg, convert, format) return super().__format__(newformat)
使い方は、文字列をStringW()に渡して、オブジェクトにするだけ!!
あ〜ら不思議!文字列が崩れることなく、表示されちゃう!!
しかも!Pythonのフォーマットも使えちゃう!!

     
<例>
def example(): member: list[dict[str, str | int | float]] = [ { "user name":"あいうえ", "weight":90, "height":177 }, { "user name":"Super 真理雄", "weight":1250, "height":188 }, { "user name":"Ninja", "weight":70, "height":160 }, { "user name":"Mr.エックス", "weight":66, "height":167 }, ] mr = StringW("こんにちは、Mr.x") mr += "!!" print(mr) print(f"length: {mr.length} width: {mr.width}\n") for m in member: name = StringW(m['user name']) print(f"{name:_<13s} : {m['weight']:>8,.1f}Kg : {m['height']:>5}cm") if __name__ == "__main__": example()

<表示結果>
こんにちは、Mr.x!! length: 12 width: 18 あいうえ_____ : 90.0Kg : 177cm Super 真理雄_ : 1,250.0Kg : 188cm Ninja________ : 70.0Kg : 160cm Mr.エックス__ : 66.0Kg : 167cm

ちなみに、プロパティの length はlen()関数と同じ文字数で、
width全角文字を2とした幅を表していますぞ!

めでたしめでたし!
おお!今日は『ひな祭り』ではないですか!!
めでたい!めでたい!


0 件のコメント:

コメントを投稿