日々精進

新しく学んだことを書き留めていきます

pythonでdecoratorを付けたメソッドの戻り値の型チェックができるようにする

以下のようにdecoratorを定義すると、@timerを付けたメソッドの戻り値の型がAnyになってしまい、不便。

def timer(fn) :
    from time import perf_counter

    def inner(*args, **kwargs):
        print(f'start {fn.__name__} {CommonUtil.now_datetime_for_log()}')
        CommonUtil.print_mem_usage()
        start_time = perf_counter()
        to_execute = fn(*args, **kwargs)
        end_time = perf_counter()
        execution_time = end_time - start_time
        hhmmss: str = time.strftime('%H:%M:%S', time.gmtime(execution_time))
        print(f'end {fn.__name__} {CommonUtil.now_datetime_for_log()}. elapsed time: {hhmmss}')
        CommonUtil.print_mem_usage()
        return to_execute

    return inner

def timer(fn) :

TCallable = TypeVar("TCallable", bound=Callable)

def timer(fn: TCallable) -> TCallable:

のように変えるとちゃんと戻り値の型をチェックしてくれ、コード補完もできる。

参考:

blog.whtsky.me

pythonでtupleをreturnする時のtype hintは-> Tuple[bool, str]のように書く

今まで
def func() -> (DataFrame, str):
のように書いてたけど、これだとPyCharmでは戻り値の型を正しく推論できないことがあった。つまり、以下のようにfuncの戻り値をa,bで受けた後、a.まで入力してもDataFrameのメソッドが補完候補に出ない。

a, b = func()

def func() -> Tuple[DataFrame, str]:

のように書くのが正しい。

参考:

stackoverflow.com

gradleでインストールしたライブラリが依存しているライブラリがインストールされない

原因は依存関係がoptionalだったため。例えばspring-data-redisだと、jedisは依存ライブラリではあるが(optional)と記載されている。

https://mvnrepository.com/artifact/org.springframework.data/spring-data-redis/2.7.2

optionalは複数のライブラリのどれかをユーザが選べる場合に指定される。spring-data-redisだったらRedisのクライアントライブラリとしてjedisかlettuceかを選べるのでどちらもoptionalな依存関係になっている。

なので上記の場合jedisへの依存関係をbuild.gradleに追加すればよい。

参考:

stackoverflow.com

初期化時に何か処理を追加したい場合は__post_init__が便利

@dataclass
class MyClass:
  _myvar

のように@dataclassを使っていると、initを自動生成してくれて便利だが初期化時に何か処理を追加したい場合に困る。

以下のようにpost_initメソッドを実装するとinitを実行した後で実行してくれるので便利。これでdef initを自分で実装し、_myvarへの代入処理を書かなくて済む。。

@dataclass
class MyClass:
  _myvar
  def __post_init__(self):
    ...

参考:

stackoverflow.com

daskでgroupbyしてから行間のdiffをとる

Pandasの場合は以下のようにgroupbyしてからdiffを呼ぶと1行前とのdiffが取れるが、daskにはdiffメソッドがない。

features[num_features + ["customer_ID"]].groupby(["customer_ID"]).diff(1)

daskの場合、map_overlapメソッドを使って実装する。 diffや移動平均のようにwindowをずらしながら計算するような場合はmap_overlapを使うらしい。diffをとる場合のサンプルコードは以下。

    def _get_difference(self, num_features: List[str]) -> DataFrame:
        def diff(df: DataFrame) -> DataFrame:
            return df.diff(1)
        npartitions: int = min(os.cpu_count() * 3, int(len(self._features) / 100))
        ddf: dd.DataFrame = dd.from_pandas(self._features, npartitions=npartitions).set_index("customer_ID")
        ddf = ddf[num_features].map_overlap(diff, 2, 0)
        ddf = ddf.rename(columns={col: col + "_diff_1" for col in num_features})
        ddf = ddf.groupby("customer_ID").last()
        return ddf.reset_index().compute()

参考:

docs.dask.org

PyCharmで開発していると「The Git process exited with the code 1」エラー

WSL2からアクセスできるパス(「\wsl.localhost\Ubuntu\opt\ml\mypj」のような感じ)をPyCharmで開いて開発していると、掲題のエラーが発生した。PyCharmを開いてすぐは大丈夫だけど、たまにエラーが出る感じ。一旦発生するとWSLを再起動しないと直らないので困る。

以下で報告されているが、原因はWSL2側の不具合らしい。

https://youtrack.jetbrains.com/issue/IDEA-276250

以下に書かれている方法でWSLのバージョンを上げると直ったっぽい(今のところ再発していない)

https://youtrack.jetbrains.com/issue/IDEA-276250/WSL2-The-Git-process-exited-with-the-code-1#focus=Comments-27-6353307.0-0