使用占位符定义命名目录结构
项目描述
pathtree
命名路径分层
此包的目的是使在脚本和管道中定义和理解文件夹结构变得更加容易。我发现当查看散布在脚本中的多个 os.path.join
命令时很难可视化文件结构,因此,您可以在顶部定义所有路径,然后在稍后填充占位符。
安装
pip install path-tree
教程
快速入门
注意:我希望将其更新得更具说明性,而不是那么冗长。问题是这个包可以做很多事情,但很难在不脱离上下文的情况下简洁地展示它。我不确定何时会有机会,但我将尽力完成它!
此外,我为没有API参考表示歉意。我还没有完全将其整合到我的部署管道中!
import pathtree
# instantiate the path tree - dict nesting == folder nesting !
paths = pathtree.tree('./logs', {
'{log_id}': {
'model.h5': 'model',
'model_spec.pkl': 'model_spec',
'plots': {
'epoch_{i_epoch:04d}': {
'{plot_name}.png': 'plot',
'': 'plot_dir'
}
}
}
})
...
# specify variables along the way as they become available
paths.update(log_id=12345)
# usually they aren't inserted until you call format, so you can still change them
paths.update(log_id=12346)
...
# when you're ready to construct the path, just call .format()
# with the remaining variables and it will give you the formatted
plt.imwrite(paths.plot.format(i_epoch=5, plot_name='f1_score'))
# writes to ./logs/12345/plots/epoch_0005/f1_score.png
旧方法 - 让我的大脑感到难过
通常,您会以这种方式定义路径(除非我在做一些奇怪/愚蠢的事情 - 告诉我 !!)。而且,这些通常散布在您的项目中,很难获得路径层次结构的高级视图。
base_log_dir = './blah/logs'
run_dir = os.path.join(base_log_dir, log_id)
resources_dir = os.path.join(run_dir, 'resources')
...
model_file = os.path.join(run_dir, 'model.h5')
model_spec = os.path.join(run_dir, 'model_spec.pkl')
...
pump_file = os.path.join(resources_dir, 'pump.pkl')
...
plot_dir = os.path.join(run_dir, 'plots', 'epoch_{i_epoch:04d}')
plot_file = os.path.join(plot_dir, '{plot_name}.png')
新方法! *大脑微笑*
相反,您可以在一个地方定义您的路径层次结构,并为每个树节点命名。
import os
import pathtree
# define the entire directory structure
# the tree keys represent folder names.
# the final non-dict key represents the name for
# that directory node.
#
# e.g.: {folder1: {folder2: name}}
# paths.name => folder1/folder2
#
# Notice the blank key under "plots". This takes advantage
# of the fact that os.path.join('plots', '') == 'plots'.
# So the name assigned to the blank string is naming the
# directory
base_paths = pathtree.tree('./logs', {
'{log_id}': {
'model.h5': 'model',
'model_spec.pkl': 'model_spec',
'plots': {
'epoch_{i_epoch:04d}': {
'{plot_name}.png': 'plot',
'': 'plot_dir' # name for the directory
}
}
}
})
# specify the log_id - specify returns a copy, update operates inplace
paths = base_paths.specify(log_id=12345)
基本概念
Paths
- 使用pathtree.tree
定义的路径项集合。本质上,它是一个围绕名称 -> 路径的平面字典和数据字典的包装器,这些数据字典提供给所有路径格式。Path
- 单个路径。它扩展了os.PathLike
,因此可以在需要路径对象的地方使用(例如open(path).read()
)。它是一个围绕pathlib.Path
对象和数据字典的包装器,并提供基本路径操作(join('subdir')
、.up(2)
向上移动2个父目录、.glob()
glob用'*'
替换缺失的字段)。
转换为字符串
您可以使用树中定义的名称来访问路径
assert str(paths.model_spec) == './logs/12345/model_spec.pkl'
Paths
对象(如上定义)实际上是一个名为 => pathtree.Path
对象的字典。这是一个字符串格式模式和数据字典的包装器。
要将 Path
转换为字符串,有几种方法可以实现您想要的结果。
对于完全指定的字符串(这意味着 str.format 将正常运行而不会引发 KeyError),这三种方法都返回相同的结果:一个完全格式化的字符串。
assert paths.model.format() == './logs/12345/model.h5'
assert paths.model.partial_format() == './logs/12345/model.h5'
assert paths.model.maybe_format() == './logs/12345/model.h5'
对于未充分指定的字符串(缺少数据键),返回值是不同的
# str.format is missing a key and will throw an exception
try:
paths.plot.format(plot_name='f1_score')
assert False
except KeyError: # missing i_epoch
assert True
# str.format is missing a key so the missing key will be left in
assert (paths.plot.partial_format(plot_name='f1_score') ==
'./logs/12345/plots/epoch_{i_epoch:04d}/f1_score.png')
# str.format is missing a key so it will keep it as a Path object
# with the updated key `plot_name`.
# this retains the ability to use data updating functionality.
assert isinstance(
paths.plot.maybe_format(plot_name='f1_score'), pathtree.Path)
路径可以转换为字符串,这与 partial_format
相同。您还可以使用 Path().path
访问未格式化的路径。
assert str(paths.plot) == paths.plot.partial_format()
assert paths.plot.path == the_unformatted_plot_path
pathtree.Path
子类 os.PathLike
,这意味着 os.path 函数知道如何自动将其转换为路径。
assert isinstance(paths.model, os.PathLike)
assert os.path.join(paths.model) == paths.model.format()
assert os.path.isfile(paths.model)
更新格式数据
您可以在沿途的各个位置添加路径特定性。当您需要根据某些循环或类似模式引用子目录时,这很有用。您可以通过几种方式来完成这项操作
# across the entire directory object
# update in place
paths.update(log_id=12345)
# or create a copy
paths2 = paths.specify(log_id=23456)
assert paths.data['log_id'] == 12345 and paths2.data['log_id'] = 23456
# reverse specify - remove a data key
paths2 = paths2.unspecify('log_id')
assert 'log_id' not in paths2.data
# or for a single path
# in place
plot_file = paths.plot
plot_file.update(plot_name='f1_score')
# create a copy
plot_file = paths.plot.specify(plot_name='f1_score')
assert 'plot_name' not in paths.data
assert plot_file.data['plot_name'] == 'f1_score'
# reverse specify - remove a data key
plot_file = plot_file.unspecify('plot_name')
assert 'plot_name' not in plot_file.data
附加功能
您可以进行自动的 glob 搜索。任何缺失的字段都将用 glob 通配符(星号)填充。请注意,这可能会使用普通字符串格式失败,因为前导零格式化器(:04d
)如果尝试插入 '*'
将引发错误(因为它是一个字符串)。
plot_file = paths.plot.specify(plot_name='f1_score')
assert (plot_file.partial_format() ==
'./logs/12345/plots/epoch_{i_epoch:04d}/f1_score.png')
assert (plot_file.glob_pattern ==
'./logs/12345/plots/epoch_*/f1_score.png')
assert (plot_file.glob() ==
glob.glob('./logs/12345/plots/epoch_*/f1_score.png'))
您还可以在格式化的字符串中解析出数据。请注意,这可能并不总是正确,因为有时解析是模糊的。请参阅 https://github.com/r1chardj0n3s/parse#potential-gotchas
plot_file = paths.plot.specify(root='some/logs')
assert (plot_file.partial_format() ==
'some/logs/12345/plots/epoch_{i_epoch:04d}/{plot_name}.png')
expected = {
'root': 'some/logs'
'log_id': '12345',
'i_epoch': '0002',
'plot_name': 'f1_score.png',
}
plot_data = plot_file.parse('./logs/12345/plots/0002/f1_score.png')
assert set(plot_data.items()) == set(expected.items())
项目详情
path-tree-0.0.18.tar.gz 的哈希值
算法 | 哈希摘要 | |
---|---|---|
SHA256 | 6bf10d82fc4ceb1e4c5e2cce2a7f2cc45533a32492231a54235261a56082364c |
|
MD5 | c10398714eb27ef6cadb316e7a7ab807 |
|
BLAKE2b-256 | 9a4de9419acc4ad46c00c52fe72e90a577696be7fae078dd4db980f92c97622f |