一个包含简单工具、类似stdlib的感觉和额外电池的Python实用库
项目描述
Ubelt是一个具有类似stdlib感觉的Python实用库。
电梯简述
Python标准库好吗?是的。它的简洁性可以改进吗?当然可以。Ubelt旨在提供一种更快速地表达标准库中可以执行的事情的方法。进展如何?请参阅ub.ProgIter。哈希?请参阅ub.hash_data / ub.hash_file。缓存?请参阅ub.Cacher / ub.CacheStamp。shell命令?请参阅ub.cmd。还有类似的功能用于数据下载、基于future的并行(或串行)作业执行、美观的reprs、路径管理、迭代,以及我最喜欢的之一:集操作增强的字典:ub.udict。
有大约120个函数和类可以帮助使您的代码更短,更易于简洁地表达。该库安装和导入速度快,所有依赖项都是可选的。截至2023年,它已经6岁了,定期维护,并且已经成熟。它经过了良好的测试,并且使用适中。
要了解更多信息,功能有用性图表是一个不错的开始。这显示了特定函数的使用频率,虽然一些不太常用的函数可能是删除的候选,但其中一些仍然值得一看。为了更慢地开始,请阅读简介
简介
Ubelt是一个轻量级的库,它提供了一系列稳健、经过测试、文档齐全且简单的函数,这些函数扩展了Python标准库。它有一个扁平的API,在Windows、Mac和Linux(直到一些不可避免的小差异)上表现相似。几乎在ubelt中的每个函数都是用doctest编写的。这提供了有用的文档和示例用法,同时有助于实现100%的测试覆盖率(在Windows上有一些小的例外)。
目标:提供简单的函数,以完成尚未被Python标准库处理的常见任务。
约束:必须是低影响的纯Python;它应该易于安装和使用。
方法:所有函数都是用docstrings和doctests编写的,以确保始终存在基本级别的文档和测试(即使函数被复制/粘贴到其他库中)
座右铭:好的工具使所有代码都提升。
在此处阅读文档:http://ubelt.readthedocs.io/en/latest/auto/
以下是ubelt API能够执行的一些任务
扩展pathlib的expand、ensuredir、endswith、augment、delete(ub.Path)
获取跨平台数据/缓存/配置目录的路径(ub.Path.appdir,…)
在字典上执行集操作(SetDict)
具有扩展辅助方法如subdict、take、peek_value、invert、sorted_keys、sorted_vals的字典(UDict)
哈希常见的数据结构,如list、dict、int、str等。(hash_data)
哈希文件(hash_file)
缓存代码块(Cacher、CacheStamp)
测量代码块的时间(Timer)
使用比tqdm更少的开销显示循环进度(ProgIter)
下载文件,带有可选的缓存和哈希验证(download、grabdata)
运行shell命令(cmd)
在候选位置中查找文件或目录(find_path、find_exe)
嵌套数据结构的字符串-repr(urepr)
使用ANSI标签着色文本(color_text)
水平连接多行字符串(hzcat)
创建跨平台的符号链接(symlink)
使用该模块的路径导入模块(import_module_from_path)
检查命令行上的特定标志或值是否开启(argflag、argval)
缓存函数(memoize、memoize_method、memoize_property)
构建有序集合(oset)
在列表和字典上进行argmax/min/sort(argmin、argsort)
获取项目直方图或在列表中查找重复项(dict_hist、find_duplicates)
根据某些标准对项目序列进行分组(group_items)
Ubelt很小。其顶级API定义大约使用了40行代码
from ubelt.util_arg import (argflag, argval,)
from ubelt.util_cache import (CacheStamp, Cacher,)
from ubelt.util_colors import (NO_COLOR, color_text, highlight_code,)
from ubelt.util_const import (NoParam,)
from ubelt.util_cmd import (cmd,)
from ubelt.util_dict import (AutoDict, AutoOrderedDict, SetDict, UDict, ddict,
dict_diff, dict_hist, dict_isect, dict_subset,
dict_union, dzip, find_duplicates, group_items,
invert_dict, map_keys, map_vals, map_values,
named_product, odict, sdict, sorted_keys,
sorted_vals, sorted_values, udict, varied_values,)
from ubelt.util_deprecate import (schedule_deprecation,)
from ubelt.util_download import (download, grabdata,)
from ubelt.util_download_manager import (DownloadManager,)
from ubelt.util_func import (compatible, identity, inject_method,)
from ubelt.util_repr import (ReprExtensions, urepr,)
from ubelt.util_futures import (Executor, JobPool,)
from ubelt.util_io import (delete, touch,)
from ubelt.util_links import (symlink,)
from ubelt.util_list import (allsame, argmax, argmin, argsort, argunique,
boolmask, chunks, compress, flatten, iter_window,
iterable, peek, take, unique, unique_flags,)
from ubelt.util_hash import (hash_data, hash_file,)
from ubelt.util_import import (import_module_from_name,
import_module_from_path, modname_to_modpath,
modpath_to_modname, split_modpath,)
from ubelt.util_indexable import (IndexableWalker, indexable_allclose,)
from ubelt.util_memoize import (memoize, memoize_method, memoize_property,)
from ubelt.util_mixins import (NiceRepr,)
from ubelt.util_path import (ChDir, Path, TempDir, augpath, ensuredir,
expandpath, shrinkuser, userhome,)
from ubelt.util_platform import (DARWIN, LINUX, POSIX, WIN32, find_exe,
find_path, platform_cache_dir,
platform_config_dir, platform_data_dir,)
from ubelt.util_str import (codeblock, hzcat, indent, paragraph,)
from ubelt.util_stream import (CaptureStdout, CaptureStream, TeeStringIO,)
from ubelt.util_time import (Timer, timeparse, timestamp,)
from ubelt.util_zip import (split_archive, zopen,)
from ubelt.orderedset import (OrderedSet, oset,)
from ubelt.progiter import (ProgIter,)
安装
Ubelt 以通用 wheel 的形式在 PyPI 上分发,并可在 Python 3.6+ 上使用 pip 安装。安装已在 CPython 和 PyPy 实现 上进行测试。对于 Python 2.7 和 3.5,最后支持的版本是 0.11.1。
pip install ubelt
请注意,我们发布的 PyPI 版本都使用 GPG 签名。签名公钥为 D297D757;这应该与 dev/public_gpg_key 中的值一致。
函数实用性
当需要手动选择一组我认为最有用的函数时,我选择了这些并提供了一些关于为什么选择它们的评论
import ubelt as ub
ub.Path # inherits from pathlib.Path with quality of life improvements
ub.UDict # inherits from dict with keywise set operations and quality of life improvements
ub.Cacher # configuration based on-disk cachine
ub.CacheStamp # indirect caching with corruption detection
ub.hash_data # hash mutable python containers, useful with Cacher to config strings
ub.cmd # combines the best of subprocess.Popen and os.system
ub.download # download a file with a single command. Also see grabdata for the same thing, but caching from CacheStamp.
ub.JobPool # easy multi-threading / multi-procesing / or single-threaded processing
ub.ProgIter # a minimal progress iterator. It's single threaded, informative, and faster than tqdm.
ub.memoize # like ``functools.cache``, but uses ub.hash_data if the args are not hashable.
ub.urepr # readable representations of nested data structures
但更好的方法可能是客观地衡量使用频率,并构建一个实用性直方图。我使用 python dev/maintain/gen_api_for_docs.py 生成此直方图,该脚本大致统计我在另一个项目中使用 ubelt 函数的次数。注意:这个衡量标准对旧函数有偏见。
函数名称 |
实用性 |
---|---|
4327 |
|
2125 |
|
1349 |
|
747 |
|
657 |
|
611 |
|
603 |
|
508 |
|
462 |
|
342 |
|
341 |
|
313 |
|
303 |
|
287 |
|
270 |
|
267 |
|
265 |
|
262 |
|
248 |
|
239 |
|
236 |
|
200 |
|
184 |
|
161 |
|
156 |
|
156 |
|
152 |
|
145 |
|
142 |
|
134 |
|
133 |
|
129 |
|
123 |
|
120 |
|
117 |
|
116 |
|
116 |
|
107 |
|
104 |
|
90 |
|
88 |
|
84 |
|
78 |
|
76 |
|
76 |
|
69 |
|
67 |
|
62 |
|
58 |
|
57 |
|
56 |
|
55 |
|
54 |
|
53 |
|
53 |
|
51 |
|
50 |
|
50 |
|
50 |
|
49 |
|
47 |
|
40 |
|
38 |
|
37 |
|
36 |
|
36 |
|
32 |
|
30 |
|
29 |
|
24 |
|
24 |
|
23 |
|
23 |
|
17 |
|
17 |
|
17 |
|
14 |
|
13 |
|
11 |
|
11 |
|
10 |
|
9 |
|
8 |
|
8 |
|
7 |
|
7 |
|
6 |
|
6 |
|
6 |
|
5 |
|
4 |
|
4 |
|
4 |
|
3 |
|
3 |
|
2 |
|
2 |
|
2 |
|
1 |
|
1 |
|
0 |
|
0 |
|
0 |
|
0 |
|
0 |
|
0 |
|
0 |
|
0 |
|
0 |
|
0 |
|
0 |
|
0 |
|
0 |
|
0 |
|
0 |
|
0 |
|
0 |
示例
最新示例是 doctests。我们还有一个 Jupyter 笔记本:https://github.com/Erotemic/ubelt/blob/main/docs/notebooks/Ubelt%20Demo.ipynb
以下是 ubelt 中一些功能的示例
路径
Ubelt 通过添加几个新方法(通常是链式方法)扩展了 pathlib.Path。具体来说,是 augment、delete、expand、ensuredir、shrinkuser。它还修改了 touch 的行为,使其可链式调用。(自 1.0.0 版本起新增)
>>> # Ubelt extends pathlib functionality
>>> import ubelt as ub
>>> dpath = ub.Path('~/.cache/ubelt/demo_path').expand().ensuredir()
>>> fpath = dpath / 'text_file.txt'
>>> aug_fpath = fpath.augment(suffix='.aux', ext='.jpg').touch()
>>> aug_dpath = dpath.augment('demo_path2')
>>> assert aug_fpath.read_text() == ''
>>> fpath.write_text('text data')
>>> assert aug_fpath.exists()
>>> assert not aug_fpath.delete().exists()
>>> assert dpath.exists()
>>> assert not dpath.delete().exists()
>>> print(f'{fpath.shrinkuser()}')
>>> print(f'{dpath.shrinkuser()}')
>>> print(f'{aug_fpath.shrinkuser()}')
>>> print(f'{aug_dpath.shrinkuser()}')
~/.cache/ubelt/demo_path/text_file.txt
~/.cache/ubelt/demo_path
~/.cache/ubelt/demo_path/text_file.aux.jpg
~/.cache/ubelt/demo_pathdemo_path2
哈希
ub.hash_data 构建用于常见 Python 嵌套数据结构的哈希值。可以通过注册扩展来允许它哈希自定义类型。默认情况下,它处理列表、字典、集合、切片、uuid 和 NumPy 数组。
>>> import ubelt as ub
>>> data = [('arg1', 5), ('lr', .01), ('augmenters', ['flip', 'translate'])]
>>> ub.hash_data(data, hasher='sha256')
0d95771ff684756d7be7895b5594b8f8484adecef03b46002f97ebeb1155fb15
还包括对 torch 张量和 pandas 数据框的支持,但需要显式启用。还存在一个非公开的插件架构,可以将此功能扩展到任意类型。虽然官方不支持,但它是可用的,并且在未来将更好地集成。有关详细信息,请参阅 ubelt/util_hash.py。
缓存
使用最少的样板代码或对原始代码的修改,在脚本内部代码块中缓存中间结果。
对于直接缓存数据,请使用 Cacher 类。默认情况下,结果将写入 ubelt 的 appdir 缓存,但可以通过 dpath 或 appname 参数指定确切位置。此外,可以通过 depends 参数指定处理依赖关系,允许隐式缓存失效。据我所知,这是使用现有 Python 语法(截至 2022-06-03)缓存代码块的最简洁方式(4 行样板代码)。
>>> import ubelt as ub
>>> depends = ['config', {'of': 'params'}, 'that-uniquely-determine-the-process']
>>> cacher = ub.Cacher('test_process', depends=depends, appname='myapp')
>>> # start fresh
>>> cacher.clear()
>>> for _ in range(2):
>>> data = cacher.tryload()
>>> if data is None:
>>> myvar1 = 'result of expensive process'
>>> myvar2 = 'another result'
>>> data = myvar1, myvar2
>>> cacher.save(data)
>>> myvar1, myvar2 = data
对于间接缓存,请使用 CacheStamp 类。这简单地写入一个标记文件,标记一个进程已完成。此外,您可以指定当标记应该过期时的标准。如果您让 CacheStamp 了解预期的“产品”,它将在该文件更改时过期标记,这在缓存可能损坏或需要失效的情况下很有用。
>>> import ubelt as ub
>>> dpath = ub.Path.appdir('ubelt/demo/cache').delete().ensuredir()
>>> params = {'params1': 1, 'param2': 2}
>>> expected_fpath = dpath / 'file.txt'
>>> stamp = ub.CacheStamp('name', dpath=dpath, depends=params,
>>> hasher='sha256', product=expected_fpath,
>>> expires='2101-01-01T000000Z', verbose=3)
>>> # Start fresh
>>> stamp.clear()
>>>
>>> for _ in range(2):
>>> if stamp.expired():
>>> expected_fpath.write_text('expensive process')
>>> stamp.renew()
有关 Cacher 和 CacheStamp 的更多详细信息,请参阅 https://ubelt.readthedocs.io/en/latest/auto/ubelt.util_cache.html。
循环进度
ProgIter 是一个不附加线程的进度计,它将写入 stdout。它基本上是 tqdm 的替代品。**ProgIter 的优点是它不使用任何 Python 线程**,因此在与使用多进程的代码一起使用时更安全。
注意:ProgIter 还定义在一个独立的模块中:pip install progiter)
>>> import ubelt as ub
>>> def is_prime(n):
... return n >= 2 and not any(n % i == 0 for i in range(2, n))
>>> for n in ub.ProgIter(range(1000), verbose=2):
>>> # do some work
>>> is_prime(n)
0/1000... rate=0.00 Hz, eta=?, total=0:00:00, wall=14:05 EST
1/1000... rate=82241.25 Hz, eta=0:00:00, total=0:00:00, wall=14:05 EST
257/1000... rate=177204.69 Hz, eta=0:00:00, total=0:00:00, wall=14:05 EST
642/1000... rate=94099.22 Hz, eta=0:00:00, total=0:00:00, wall=14:05 EST
1000/1000... rate=71886.74 Hz, eta=0:00:00, total=0:00:00, wall=14:05 EST
命令行交互
内置的 Python subprocess.Popen 模块很棒,但有时可能有点笨拙。`os.system` 命令易于使用,但灵活性不大。《ub.cmd》函数旨在解决这个问题。它运行起来与 `os.system` 一样简单,但它返回一个包含退出代码、标准输出、标准错误和底层的 Popen 对象的字典。
这个实用程序旨在在不同平台上提供尽可能一致的行为。我们旨在支持 Windows、Linux 和 OSX。
>>> import ubelt as ub
>>> info = ub.cmd('gcc --version')
>>> print(ub.urepr(info))
{
'command': 'gcc --version',
'err': '',
'out': 'gcc (Ubuntu 5.4.0-6ubuntu1~16.04.9) 5.4.0 20160609\nCopyright (C) 2015 Free Software Foundation, Inc.\nThis is free software; see the source for copying conditions. There is NO\nwarranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\n',
'proc': <subprocess.Popen object at 0x7ff98b310390>,
'ret': 0,
}
此外,请注意使用 ub.urepr(以前为 ub.repr2)来很好地格式化输出字典。
此外,如果您指定 verbose=True,则 ub.cmd 将同时捕获标准输出并在实时显示它(即,它将“tee”输出)。
>>> import ubelt as ub
>>> info = ub.cmd('gcc --version', verbose=True)
gcc (Ubuntu 5.4.0-6ubuntu1~16.04.9) 5.4.0 20160609
Copyright (C) 2015 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
ub.cmd 的常见用例是解析程序版本号
>>> import ubelt as ub
>>> cmake_version = ub.cmd('cmake --version')['out'].splitlines()[0].split()[-1]
>>> print('cmake_version = {!r}'.format(cmake_version))
cmake_version = 3.11.0-rc2
这允许您轻松地将命令行可执行文件作为 Python 进程的一部分运行,查看它在做什么,然后根据其输出执行某些操作,就像您在与命令行本身交互一样。
这个想法是 ub.cmd 移除了您是否需要传递一个参数列表或字符串的需要思考。《ub.cmd》两种方式都适用。
在版本 1.0.0 中新增,具有不同后果的执行 shell 命令的第三种变体。使用 system=True 关键字参数将直接使用 os.system 而不是完全使用 Popen。在这种模式下,由于程序直接在前台执行,因此无法 tee 输出。这对于执行诸如启动 vim 会话并在用户成功退出 vim 时返回此类操作非常有用。
下载文件
函数 ub.download 提供了一个简单的接口,用于下载 URL 并将其数据保存到文件中。
>>> import ubelt as ub
>>> url = 'http://i.imgur.com/rqwaDag.png'
>>> fpath = ub.download(url, verbose=0)
>>> print(ub.shrinkuser(fpath))
~/.cache/ubelt/rqwaDag.png
函数 ub.grabdata 与 ub.download 类似,但与 ub.download 总是重新下载文件不同,ub.grabdata 将检查文件是否存在,并且仅在需要时才重新下载。
>>> import ubelt as ub
>>> url = 'http://i.imgur.com/rqwaDag.png'
>>> fpath = ub.grabdata(url, verbose=0, hash_prefix='944389a39')
>>> print(ub.shrinkuser(fpath))
~/.cache/ubelt/rqwaDag.png
版本 0.4.0 新增:这两个函数现在都接受 hash_prefix 关键字参数,如果指定,将检查文件的哈希值是否与提供的值匹配。可以使用 hasher 关键字参数来更改使用的哈希算法(默认为 "sha512")。
字典集合操作
类似于集合操作的字典操作。请参阅每个函数的文档以获取有关值行为的更多详细信息。通常,最后看到的值具有优先权。
希望 Python 有一天决定将这些添加到 stdlib 中。
ubelt.dict_union 对应于 set.union。
ubelt.dict_isect 对应于 set.intersection。
ubelt.dict_diff 对应于 set.difference。
>>> d1 = {'a': 1, 'b': 2, 'c': 3}
>>> d2 = {'c': 10, 'e': 20, 'f': 30}
>>> d3 = {'e': 10, 'f': 20, 'g': 30, 'a': 40}
>>> ub.dict_union(d1, d2, d3)
{'a': 40, 'b': 2, 'c': 10, 'e': 10, 'f': 20, 'g': 30}
>>> ub.dict_isect(d1, d2)
{'c': 3}
>>> ub.dict_diff(d1, d2)
{'a': 1, 'b': 2}
版本 1.2.0 新增:Ubelt 现在包含一个具有集合操作的字典子类,可以调用为 ubelt.SetDict 或 ub.sdict。请注意,支持 n 元操作。
>>> d1 = ub.sdict({'a': 1, 'b': 2, 'c': 3})
>>> d2 = {'c': 10, 'e': 20, 'f': 30}
>>> d3 = {'e': 10, 'f': 20, 'g': 30, 'a': 40}
>>> d1 | d2 | d3
{'a': 40, 'b': 2, 'c': 10, 'e': 10, 'f': 20, 'g': 30}
>>> d1 & d2
{'c': 3}
>>> d1 - d2
{'a': 1, 'b': 2}
>>> ub.sdict.intersection({'a': 1, 'b': 2, 'c': 3}, ['b', 'c'], ['c', 'e'])
{'c': 3}
请注意,此功能以及更多功能可在 ubelt.UDict 或 ub.udict 中使用。
分组项目
给定一个包含项及其 id 的列表,创建一个将每个 id 映射到其对应项列表的字典。换句话说,给定一个函数或相应列表中由函数或相应列表提供的类型为 VT 的项的序列和类型为 KT 的相应键,将它们分组到一个 Dict[KT, List[VT] 中,使得每个键映射到与键相关联的值列表。这类似于 pandas.DataFrame.groupby。
可以通过包含每个相应项的 id 的第二个列表来指定分组 id。
>>> import ubelt as ub
>>> # Group via a corresonding list
>>> item_list = ['ham', 'jam', 'spam', 'eggs', 'cheese', 'bannana']
>>> groupid_list = ['protein', 'fruit', 'protein', 'protein', 'dairy', 'fruit']
>>> dict(ub.group_items(item_list, groupid_list))
{'dairy': ['cheese'], 'fruit': ['jam', 'bannana'], 'protein': ['ham', 'spam', 'eggs']}
它们还可以通过在列表中的每个项上执行函数来提供。
>>> import ubelt as ub
>>> # Group via a function
>>> item_list = ['ham', 'jam', 'spam', 'eggs', 'cheese', 'bannana']
>>> def grouper(item):
... return item.count('a')
>>> dict(ub.group_items(item_list, grouper))
{1: ['ham', 'jam', 'spam'], 0: ['eggs', 'cheese'], 3: ['bannana']}
字典直方图
找到序列中项的频率。给定一个包含项的列表或序列,该函数返回一个将序列中每个唯一值映射到其在序列中出现的次数的字典。这类似于 pandas.DataFrame.value_counts。
>>> import ubelt as ub
>>> item_list = [1, 2, 39, 900, 1232, 900, 1232, 2, 2, 2, 900]
>>> ub.dict_hist(item_list)
{1232: 2, 1: 1, 2: 4, 900: 3, 39: 1}
每个项也可以给出一个权重
>>> import ubelt as ub
>>> item_list = [1, 2, 39, 900, 1232, 900, 1232, 2, 2, 2, 900]
>>> weights = [1, 1, 0, 0, 0, 0, 0.5, 0, 1, 1, 0.3]
>>> ub.dict_hist(item_list, weights=weights)
{1: 1, 2: 3, 39: 0, 900: 0.3, 1232: 0.5}
字典操作
将函数映射到字典中,以转换字典中的键或值。函数 ubelt.map_keys 将函数应用于字典中的每个键,并返回转换后的字典副本。当前的键冲突行为会引发错误,但未来可能可配置。函数 ubelt.map_vals 与之相同,除了函数应用于每个值。如果这些函数足够有用,可以移植到 Python 本身。
>>> import ubelt as ub
>>> dict_ = {'a': [1, 2, 3], 'bb': [], 'ccc': [2,]}
>>> dict_keymod = ub.map_keys(len, dict_)
>>> dict_valmod = ub.map_vals(len, dict_)
>>> print(dict_keymod)
>>> print(dict_valmod)
{1: [1, 2, 3], 2: [], 3: [2]}
{'a': 3, 'bb': 0, 'ccc': 1}
从字典中获取子集。注意,这与 ub.dict_isect 类似,但如果没有在字典中给定键,则会引发错误。
>>> import ubelt as ub
>>> dict_ = {'K': 3, 'dcvs_clip_max': 0.2, 'p': 0.1}
>>> subdict_ = ub.dict_subset(dict_, ['K', 'dcvs_clip_max'])
>>> print(subdict_)
{'K': 3, 'dcvs_clip_max': 0.2}
ubelt.take 函数对字典(和列表)有效。它与 ubelt.dict_subset 类似,除了只返回值的列表,并丢弃有关键的信息。也可以指定一个默认值。
>>> import ubelt as ub
>>> dict_ = {1: 'a', 2: 'b', 3: 'c'}
>>> print(list(ub.take(dict_, [1, 3, 4, 5], default=None)))
['a', 'c', None, None]
反转由字典定义的映射。默认情况下,invert_dict 假设所有字典值都是不同的(即映射是一对一/注入的)。
>>> import ubelt as ub
>>> mapping = {0: 'a', 1: 'b', 2: 'c', 3: 'd'}
>>> ub.invert_dict(mapping)
{'a': 0, 'b': 1, 'c': 2, 'd': 3}
然而,通过指定 unique_vals=False,反转的字典将构建一个与每个值关联的键集合。
>>> import ubelt as ub
>>> mapping = {'a': 0, 'A': 0, 'b': 1, 'c': 2, 'C': 2, 'd': 3}
>>> ub.invert_dict(mapping, unique_vals=False)
{0: {'A', 'a'}, 1: {'b'}, 2: {'C', 'c'}, 3: {'d'}}
新版本 1.2.0:Ubelt 现在包含一个具有这些生活品质操作的字典子类 ubelt.UDict(并且还继承自 ubelt.SetDict)。别名 ubelt.udict 可用于更快地访问。
>>> import ubelt as ub
>>> d1 = ub.udict({'a': 1, 'b': 2, 'c': 3})
>>> d1 & {'a', 'c'}
{'a': 1, 'c': 3}
>>> d1.map_keys(ord)
{97: 1, 98: 2, 99: 3}
>>> d1.invert()
{1: 'a', 2: 'b', 3: 'c'}
>>> d1.subdict(['b', 'c', 'e'], default=None)
{'b': 2, 'c': 3, 'e': None}
>>> d1.sorted_keys()
OrderedDict([('a', 1), ('b', 2), ('c', 3)])
>>> d1.peek_key()
'a'
>>> d1.peek_value()
1
下次你有默认配置字典,并允许开发人员传递关键字参数以修改这些行为时,请考虑使用字典交集(&)来分离出相关部分,并使用字典并集(|)来更新相关部分。如果你需要检查未使用的参数,也可以使用字典差异(-)。
import ubelt as ub
def run_multiple_algos(**kwargs):
algo1_defaults = {'opt1': 10, 'opt2': 11}
algo2_defaults = {'src': './here/', 'dst': './there'}
kwargs = ub.udict(kwargs)
algo1_specified = kwargs & algo1_defaults
algo2_specified = kwargs & algo2_defaults
algo1_config = algo1_defaults | algo1_specified
algo2_config = algo2_defaults | algo2_specified
unused_kwargs = kwargs - (algo1_defaults | algo2_defaults)
print('algo1_specified = {}'.format(ub.urepr(algo1_specified, nl=1)))
print('algo2_specified = {}'.format(ub.urepr(algo2_specified, nl=1)))
print(f'algo1_config={algo1_config}')
print(f'algo2_config={algo2_config}')
print(f'The following kwargs were unused {unused_kwargs}')
print(chr(10))
print('-- Run with some specified --')
run_multiple_algos(src='box', opt2='fox')
print(chr(10))
print('-- Run with extra unspecified --')
run_multiple_algos(a=1, b=2)
输出
-- Run with some specified --
algo1_specified = {
'opt2': 'fox',
}
algo2_specified = {
'src': 'box',
}
algo1_config={'opt1': 10, 'opt2': 'fox'}
algo2_config={'src': 'box', 'dst': './there'}
The following kwargs were unused {}
-- Run with extra unspecified --
algo1_specified = {}
algo2_specified = {}
algo1_config={'opt1': 10, 'opt2': 11}
algo2_config={'src': './here/', 'dst': './there'}
The following kwargs were unused {'a': 1, 'b': 2}
查找重复项
在列表中查找所有重复项。更具体地说,ub.find_duplicates 搜索出现超过 k 次的项,并返回每个重复项及其出现位置的映射。
>>> import ubelt as ub
>>> items = [0, 0, 1, 2, 3, 3, 0, 12, 2, 9]
>>> ub.find_duplicates(items, k=2)
{0: [0, 1, 6], 2: [3, 8], 3: [4, 5]}
跨平台配置和缓存目录
如果你有一个写入配置或缓存文件的程序,标准存储这些文件的位置因你是在 Windows、Linux 还是 Mac 而异。Ubelt 提供了统一的函数来确定这些路径。
新版本 1.0.0:ub.Path.appdir 类方法提供了一种使用链式对象接口实现上述功能的方法。
函数 ub.Path.appdir(..., type='cache')、ub.Path.appdir(..., type='config') 和 ub.Path.appdir(..., type='data') 查找这些文件的正确平台特定位置,并调用 ensuredir 确保目录存在。
配置根目录在 Windows 上是 ~/AppData/Roaming,在 Linux 上是 ~/.config,在 Mac 上是 ~/Library/Application Support。缓存根目录在 Windows 上是 ~/AppData/Local,在 Linux 上是 ~/.config,在 Mac 上是 ~/Library/Caches。
以下是在 Linux 上的示例用法
>>> import ubelt as ub
>>> print(ub.Path.appdir('my_app').ensuredir().shrinkuser()) # default is cache
~/.cache/my_app
>>> print(ub.Path.appdir('my_app', type='config').ensuredir().shrinkuser())
~/.config/my_app
符号链接
ub.symlink 函数将创建类似于 os.symlink 的符号链接。主要区别在于 1) 如果符号链接已存在并指向正确的位置,它不会出错。2) 它在 Windows 上也有效(如果实际符号链接不可用,则使用硬链接和符号链接)
>>> import ubelt as ub
>>> dpath = ub.Path('ubelt', 'demo_symlink')
>>> real_path = dpath / 'real_file.txt'
>>> link_path = dpath / 'link_file.txt'
>>> real_path.write_text('foo')
>>> ub.symlink(real_path, link_path)
AutoDict - 自动初始化
尽管 collections.defaultdict 很好,但有时更方便有一个无限嵌套的字典。
>>> import ubelt as ub
>>> auto = ub.AutoDict()
>>> print('auto = {!r}'.format(auto))
auto = {}
>>> auto[0][10][100] = None
>>> print('auto = {!r}'.format(auto))
auto = {0: {10: {100: None}}}
>>> auto[0][1] = 'hello'
>>> print('auto = {!r}'.format(auto))
auto = {0: {1: 'hello', 10: {100: None}}}
基于字符串的导入
Ubelt 包含动态导入模块的功能,无需使用 Python 的 import 语句。虽然存在 importlib,但 ubelt 的实现更简单,且不会破坏 pytest。
注意 ubelt 只提供此功能的接口,核心实现位于 xdoctest 中(截至版本 0.7.0,代码被静态复制到一个自动生成的文件中,这样 ubelt 在运行时实际上不依赖于 xdoctest)。
>>> import ubelt as ub
>>> try:
>>> # This is where I keep ubelt on my machine, so it is not expected to work elsewhere.
>>> module = ub.import_module_from_path(ub.expandpath('~/code/ubelt/ubelt'))
>>> print('module = {!r}'.format(module))
>>> except OSError:
>>> pass
>>>
>>> module = ub.import_module_from_name('ubelt')
>>> print('module = {!r}'.format(module))
>>> #
>>> try:
>>> module = ub.import_module_from_name('does-not-exist')
>>> raise AssertionError
>>> except ModuleNotFoundError:
>>> pass
>>> #
>>> modpath = ub.Path(ub.util_import.__file__)
>>> print(ub.modpath_to_modname(modpath))
>>> modname = ub.util_import.__name__
>>> assert ub.Path(ub.modname_to_modpath(modname)).resolve() == modpath.resolve()
module = <module 'ubelt' from '/home/joncrall/code/ubelt/ubelt/__init__.py'>
>>> module = ub.import_module_from_name('ubelt')
>>> print('module = {!r}'.format(module))
module = <module 'ubelt' from '/home/joncrall/code/ubelt/ubelt/__init__.py'>
与此功能相关的函数有 ub.modpath_to_modname 和 ub.modname_to_modpath,它们可以(静态地)在模块名(例如 ubelt.util_import)和模块路径(例如 ~/.local/conda/envs/cenv3/lib/python3.5/site-packages/ubelt/util_import.py)之间转换。
>>> import ubelt as ub
>>> modpath = ub.util_import.__file__
>>> print(ub.modpath_to_modname(modpath))
ubelt.util_import
>>> modname = ub.util_import.__name__
>>> assert ub.modname_to_modpath(modname) == modpath
水平字符串连接
有时水平连接两个文本块看起来更美观。
>>> import ubelt as ub
>>> B = ub.urepr([[1, 2], [3, 4]], nl=1, cbr=True, trailsep=False)
>>> C = ub.urepr([[5, 6], [7, 8]], nl=1, cbr=True, trailsep=False)
>>> print(ub.hzcat(['A = ', B, ' * ', C]))
A = [[1, 2], * [[5, 6],
[3, 4]] [7, 8]]
计时
快速计时一行。
>>> import math
>>> import ubelt as ub
>>> timer = ub.Timer('Timer demo!', verbose=1)
>>> with timer:
>>> math.factorial(100000)
tic('Timer demo!')
...toc('Timer demo!')=0.1453s
外部工具
Ubelt 中的一些工具也作为独立的模块存在。我还没有决定最好是将它们静态复制到 Ubelt 中还是要求通过 pypi 满足依赖。有一些工具默认不使用,除非您明确允许使用。
目前静态包含的代码
ProgIter - https://github.com/Erotemic/progiter
OrderedSet - https://github.com/LuminosoInsight/ordered-set
完全可选的代码,仅在特定情况下使用
Numpy - ub.urepr 默认会格式化 Numpy 数组
xxhash - 这可以作为哈希器用于 ub.hash_data
Pygments - 由 util_color 模块使用。
dateutil - 由 util_time 模块使用。
类似工具
UBelt 是众多 Python 工具库之一。这里列出了其中一些类似库。
包含广泛范围实用工具的库
Boltons: https://github.com/mahmoud/boltons
CyToolz: https://github.com/pytoolz/cytoolz/
UnStdLib: https://github.com/shazow/unstdlib.py
包含特定范围实用工具的库
More-Itertools: 迭代工具:https://pypi.ac.cn/project/more-itertools/
Funcy: 函数式工具:https://github.com/Suor/funcy
Rich: 美观的 CLI 显示 - https://github.com/willmcgugan/rich
tempora: 时间相关工具 - https://github.com/jaraco/tempora
包含特定数据结构或实用工具的库
Benedict: 字典工具 - https://pypi.ac.cn/project/python-benedict/
tqdm: 进度条 - https://pypi.ac.cn/project/tqdm/
pooch: 数据下载 - https://pypi.ac.cn/project/pooch/
timerit: 基准测试的代码片段计时 - https://github.com/Erotemic/timerit
Jaraco(即 Jason R. Coombs)拥有一个广泛的工具库
jaraco.classes - https://github.com/jaraco/jaraco.classes
jaraco.collections - https://github.com/jaraco/jaraco.collections
jaraco.context - https://github.com/jaraco/jaraco.context
jaraco.crypto - https://github.com/jaraco/jaraco.crypto
jaraco.functools - https://github.com/jaraco/jaraco.functools
jaraco.geo - https://github.com/jaraco/jaraco.geo
jaraco.imaging - https://github.com/jaraco/jaraco.imaging
jaraco.itertools - https://github.com/jaraco/jaraco.itertools
jaraco.logging - https://github.com/jaraco/jaraco.logging
jaraco.media - https://github.com/jaraco/jaraco.media
jaraco.path - https://github.com/jaraco/jaraco.path
jaraco.text - https://github.com/jaraco/jaraco.text
jaraco.util - https://github.com/jaraco/jaraco.util
jaraco.windows - https://github.com/jaraco/jaraco.windows
以及此处未列出的许多其他库。查看:https://github.com/jaraco?tab=repositories&q=jaraco。
Ubelt 包含在 [bestof-python 列表](https://github.com/ml-tooling/best-of-python) 中,其中包含许多其他你应该查看的工具。
历史
Ubelt 是将 utool(https://github.com/Erotemic/utool) 中最有用的部分迁移到一个具有最小依赖项的独立模块。
utool 库包含许多有用的实用函数,但也包含一些无用的函数,以及一些厨房用具。其中一些函数过于具体或未很好地记录。《ubelt》是 utool 最简单和最有用的部分的移植。
请注意,utool 中还有其他一些很酷的东西不在 ubelt 中。值得注意的是,doctest harness 最终变成了 xdoctest。代码内省和动态分析工具被移植到 xinspect。更多类似 IPython 的工具被移植到 xdev。其中一部分进入了 scriptconfig。初始化文件生成已移动到 mkinit。一些 vim 和系统相关的功能可以在 vimtk 中找到。
Ubelt 的开发始于 2017-01-30,而 uTool 的开发在那年的晚些时候基本停止,但直到大约 2020 年才接受补丁。Ubelt 在 2022-01-07 达到了 1.0.0 版本,并移除了对 Python 2.7 和 3.5 的支持。
注意。
欢迎提交 PR。
还可以查看由 ubelt 驱动的其他项目
xinspect https://github.com/Erotemic/xinspect
以及与 ubelt 相关的项目
ProgIter https://github.com/Erotemic/progiter
xdoctest https://github.com/Erotemic/xdoctest
项目详情
下载文件
下载适用于您平台文件的文件。如果您不确定选择哪一个,请了解有关安装包的更多信息。