FineCache 0.2.1
科研项目中实验记录工具
License: MIT
Requires Python: >=3.9.6

# FineCache

科研项目中实验记录工具。

在科研项目（尤其是在深度学习项目）中代码运行的时间都比较长，有复杂的数据预处理步骤和诸多配置。我发现自己经常出现实验运行完成后，都忘记自己改了哪些东西。

本项目旨在保存一些项目的基本修改信息，以供分析和复现。

按照预期的方式使用本项目将为每次实验生成单独的文件夹，文件夹中将包含：

- `information.json`: 必要的信息。文件至少包含以下字段，也能存储添加的其它信息。
    - `commit`: HEAD的commit ID
    - `project_root`: git项目的根目录
    - `patch_time`: 记录patch的时间
    - `main_start`: main开始的时间
    - `main_end`: main结束的时间
    - `tracking_records`: 额外记录的文件名列表（相对于项目根目录的路径）。
- `console.log`: 记录的被装饰函数的输出。
- `changes.patch`: 与HEAD的差距patch。
- 其它 `FineCache.tracking_files` 中记录的文件。
- 以pickle存储的中间结果文件。

## 安装

```shell
pip install FineCache
```

依赖 git。

## 详细说明

### FineCache(self, base_path=None, template: str = "exp{id}", **kwargs)

- `base_path`。基础目录，默认为当前目录。在初始化时，将在 `base_path` 下创建以 `template` 命名自增的实验文件夹，后续在该文件夹下保存内容。

  可用`self.dir`获取创建的实验文件夹。

- `template`。文件夹命名模板字符串。其中`{id}`为自增的序号，可以以str.format的语法插入其它变量，并通过 `**kwargs` 传入具体的参数。

```python
fc = FineCache('.exp_log', "exp{id}-{name}", name="DeepLearningModel")
# 运行一次将产生 `./.exp_log/exp1-DeepLearningModel/`
```

> 由于需要正则表达式匹配`{id}`以自增，所以应该尽量避免在`{id}`的周围没有间隔符地放入太多其他变量。

### FineCache.information

这个变量是一个Dict，并在 `FineCache.record_main` 结束时保存到文件夹中。

在 `FineCache` 初始化时，就已经存储了以下变量：

- `commit`: HEAD的commit ID。
- `project_root`: git项目的根目录。

在其它函数的使用中，也会向此字典存储相应的变量。

### FineCache.tracking_files

这个变量是一个List，其元素为需要保存的配置文件或任何其它文件。 可以使用正则表达式匹配直接的相对路径（不含`./`开头）。

### FineCache.save_changes(self, filename='changes.patch', in_dir=True)

一般认为应该在类初始化后立即调用。保存当前代码到HEAD的所有改动到对应的文件，并向 `information` 中写入时间。

- `filename` 为保存文件名。
- `in_dir`。默认为`True`。即保存是否保存到FineCache对象的dir文件夹下。如果设置为`False`，则保存到仅由`filename`
  指定的路径中。

> 恢复时，首先恢复到 commit ID 对应的提交代码，再使用 `git apply <patch_file>` 命令应用补丁文件。

### FineCache.record(self)

可同时作为装饰器或上下文管理器使用。

```python
# fc = FineCache()
@fc.record()
def main():
    pass


# 或
with fc.record():
    pass
```

一般放在程序的主流程中，记录流程的运行开始时间和结束时间，并在主流程结束后调用 `information` 和 `tracking_files`
对应的内容写入目录。

### FineCache.cache(self, filename_hash: Callable = None, in_dir=True)

这个装饰器能缓存函数的运行结果和参数。每次调用时，检查是否存在已缓存结果，如果存在则直接给出缓存结果。

缓存结果默认以pickle文件形式存储在 dir 文件夹下。

- `filename_hash` 接受一个函数，控制如何产生的缓存文件名。当设置 `in_dir` 时，指定缓存文件的完整路径。

  默认方法是对参数计算md5值，并以`f"{func_name}({str_args};{str_kwargs}).pk"`的方式组装，应该足以应对大多数的情况。

  需要注意的是，类的方法的首个参数是self，即类的对象。下面是一个使用`args_hash`的示例。

- `in_dir`。默认为`True`。即保存是否保存到FineCache对象的dir文件夹下。如果设置为`False`，则保存到仅由`filename_hash`
  指定的路径中。

```python
# fc = FineCache()
class DataLoader:
  @fc.cache(filename_hash=lambda f, *a, **kw: f"{a[0].__class__.__name__}.{f.__name__}.pk")
  def load(self):
    pass


# 将产生缓存文件 "DataLoader.load.pk"
DataLoader().load()
```

Note: 所使用的缓存格式，目前仅支持用Pickle的形式进行存储。对于不支持 pickle 的函数参数，将会跳过存储；对于不支持 pickle
的函数运行结果，将会报错。

cache后的函数可以动态修改其中的参数；其可修改的参数定义如下：

- `filename_hash`和`in_dir`。等同于cache的参数。
- `agent`。默认为PickleAgent。具体请查看 `FineCache/CachedCall.py` 中的定义。
- `fine_cache`。是对FineCache对象的映射。

### 其它函数

#### FineCache.save_console(_self, filename: str = "console.log", in_dir=True)

也可以同时作为装饰器或上下文管理器使用。

在不影响代码段中向stdout的输出的同时，将输出的内容保存到对应的文件。

- `filename` 为保存文件名。
- `in_dir`。默认为`True`。即保存是否保存到FineCache对象的dir文件夹下。如果设置为`False`，则保存到仅由`filename`
  指定的路径中。

## 示例

参见 `examples/`。