Open12

python関連雑記 ( 反応が微妙だった記事の墓場 )

karamawanukaramawanu

例外に対する対応 TypeError: expected str, bytes or os.PathLike object

多くの場合は実装ミスだが、エラー自体は 特定のオブジェクトにファイル名として使えることを示唆している。

__fspath__ を実装する

karamawanukaramawanu

例外に対する対応 TypeError: XXXXXX object is not subscriptable

多くの場合は実装ミスだが、エラー自体は、オブジェクトに配列のふりをさせる可能性を示唆している。

__getitem__(self, n) を実装する

karamawanukaramawanu

タイムゾーンを指定すると高速になる....わけではない

Python3のdatetimeはタイムゾーンを指定するだけで高速になる - Qiita

記事ではプロファイラをつかってるが、python/3はソース公開してるので、ソースを眺めたほうが早い。処理を順に追いかけてみよう、

https://github.com/python/cpython/blob/3.11/Lib/datetime.py#L1802-L1805

タイムゾーンは指定してないので、tz=Noneは確定だとして、

https://github.com/python/cpython/blob/297465a4db0aff45e57cf5bfef4f3ab4d9538de0/Lib/datetime.py#L1787-L1794

ようやく本命 datetime._fromtimestamp(t,True,None)

https://github.com/python/cpython/blob/297465a4db0aff45e57cf5bfef4f3ab4d9538de0/Lib/datetime.py#L1742-L1746

この関数の中盤に、明確に tz is Noneに関する処理がある

https://github.com/python/cpython/blob/297465a4db0aff45e57cf5bfef4f3ab4d9538de0/Lib/datetime.py#L1760-L1781

githubの履歴をたどるとPEP495に対する修正であることがわかる

https://github.com/python/cpython/blame/297465a4db0aff45e57cf5bfef4f3ab4d9538de0/Lib/datetime.py#L1760-L1781

https://github.com/python/cpython/commit/5d0c59838223ce46a6e2b90a7d3ed48ee1ac481e

PEP495

PEP495に関しては、オフィシャルで日本語訳が存在する。

https://docs.python.org/ja/3/whatsnew/3.6.html#pep-495-local-time-disambiguation

世界の大抵の場所で、地域時計が1日繰り下げられることがこれまでありましたし、これからもあるでしょう。そういった時に、同じ日に同じ時間を指す区間があります。そういった状況では地域時計が表示する (あるいは Python の datetime インスタンスが格納する) 情報では、時間内の特定の瞬間を識別することはできません。
PEP 495 は時間内の地域時間が同じ二つの瞬間を区別するために、新たな fold 属性を datetime.datetime インスタンスならびに datetime.time クラスに追加しました。

早い話が、夏時間対応ということだ。

似たようなことはRubyでも起こるらしい。

https://hackerslab.aktsk.jp/2019/12/01/141551

Ruby の time.c を見てみると、 find_time_t 関数にてシステムの localtime_r に指定時刻の2時間前後の時刻を与えて、何が返ってくるかを調べています。これはどうやら、夏時間の切り替わりの際には同一の時刻が2回ある場合があるので、そういうときに必ず決まった側を返すための処理のようでした。

karamawanukaramawanu

decorators in the python standard lib (@deprecated specifically) - Stack Overflow https://stackoverflow.com/questions/2536307/decorators-in-the-python-standard-lib-deprecated-specifically/48632082#48632082

import warnings

def deprecated(message):
  def deprecated_decorator(func):
      def deprecated_func(*args, **kwargs):
          warnings.warn("{} is a deprecated function. {}".format(func.__name__, message),
                        category=DeprecationWarning,
                        stacklevel=2)
          warnings.simplefilter('default', DeprecationWarning)
          return func(*args, **kwargs)
      return deprecated_func
  return deprecated_decorator
karamawanukaramawanu

色々なサイトで、python2|3での 画像処理の実装として、ImageMagick/wandをおすすめしてるが、実はやや処理が遅い。

https://docs.wand-py.org/en/0.6.10/

https://github.com/emcconville/wand

小生の場合、連続して画像処理をさせているスクリプトがあり、straceしてたまたま気づいたのだが結構怪しい処理を色々やってる。

wandとPILで処理時間を比較してみる。

中央にポツンと小さい模様があるだけの 4000バイト以下のモノクロ画像 test.pngを使った例をあげる。増色&減色をするだけの簡単な実装である。

画像の詳細は本件の本質にはあまり影響しないため割愛する。

wandの実装例は次の通り。

w.py
import wand
import wand.image

aa = wand.image.Image(filename="test.png")
aa.type = "truecolor"
aa.type = "palette"
#aa.auto_level()

PIL/pillow の実装例は次の通り。

https://pillow.readthedocs.io/en/stable/

p.py
import PIL.Image

aa = PIL.Image.open("test.png")
aa.convert("RGBA")
aa.convert("P")
#aa.auto_level()

これを小生の約7000bogomipsの環境で実行するとこうなる。

$ time python3 p.py 

real    0m0.073s
user    0m0.059s
sys     0m0.014s
$ time python3 w.py 

real    0m0.454s
user    0m0.440s
sys     0m0.099s

PIL版の0.07秒は十分納得できる数字だが、wand版の0.4秒が明らかに遅いことがわかるだろう。

wandで0.4秒掛かってる理由

紆余曲折あったが、理由はわかった。

Imagemagickのライブラリを探すために複雑な処理をしているためだ。

https://github.com/emcconville/wand/blame/master/wand/api.py#L103-L100

ctypes.util.find_libraryが曲者なのだが、実は内部でgccを実行して、ライブラリのpathを探そうとしてる。

https://github.com/python/cpython/blame/main/Lib/ctypes/util.py#L118

これは imagemagickの方も問題で、非標準なディレクトリへのインストールも提唱してるためだ。

https://imagemagick.org/script/download.php

Set the MAGICK_HOME environment variable to the path where you extracted the ImageMagick files. For example:
$ export MAGICK_HOME="$HOME/ImageMagick-7.1.0"

wand側でもこのあたりの言及はしている。

https://github.com/emcconville/wand/blob/master/docs/guide/install.rst

PIL(pillow)+α で行こう

手っ取り早く、PILで行くことにした。

前述の通り、減色程度ならPILで容易に代替可能である。
また代表的な画像処理であろう crop および resize==transform も類似の機能があるため、概ねPILで実装可能である。

小生の運用では、さらにこれに明度レベル調整が必要だった。 wandでは非常に簡単に使える機能なので、このためだけにwandを使うのなくはない。

https://github.com/emcconville/wand/blob/master/wand/image.py#L2981

auto_levelはPILには機能がないため、自作する必要がある。が、これはすぐ見つかる。例えば次のリンクでは numpy+cv2を使って自分で計算する。

https://stackoverflow.com/questions/3105603/doing-the-same-as-imagemagicks-level-in-python-pil/53744086#53744086

wand/imagemagickを辞めて。PILに加えてopencv+numpyまで使うなら本末転倒では?と思う方も居るだろう。
しかし小生の場合では、それでもwandより何割か処理が早いことを確認した。そのため、wandを廃止してPIL+opencv/numpyに切り替えた。

karamawanukaramawanu
mb_strimwidth.py
import unicodedata

def mb_strimwidth(s, start, width, trimmarker=''):
    def char_width(c):
        if unicodedata.east_asian_width(c) in 'FWA':
            return 2
        else:
            return 1

    end = start + width
    current_width = 0
    result = ''

    for char in s:
        current_width += char_width(char)
        if current_width > end:
            return result + trimmarker
        result += char

    return result

# 例
text = 'これは非常に長いテキストです'
print(mb_strimwidth(text, 0, 10, '...'))