pyxllib 0.0.43__py3-none-any.whl → 0.3.197__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pyxllib/__init__.py +9 -2
- pyxllib/algo/__init__.py +8 -0
- pyxllib/algo/disjoint.py +54 -0
- pyxllib/algo/geo.py +541 -0
- pyxllib/{util/mathlib.py → algo/intervals.py} +172 -36
- pyxllib/algo/matcher.py +389 -0
- pyxllib/algo/newbie.py +166 -0
- pyxllib/algo/pupil.py +629 -0
- pyxllib/algo/shapelylib.py +67 -0
- pyxllib/algo/specialist.py +241 -0
- pyxllib/algo/stat.py +494 -0
- pyxllib/algo/treelib.py +149 -0
- pyxllib/algo/unitlib.py +66 -0
- pyxllib/autogui/__init__.py +5 -0
- pyxllib/autogui/activewin.py +246 -0
- pyxllib/autogui/all.py +9 -0
- pyxllib/autogui/autogui.py +852 -0
- pyxllib/autogui/uiautolib.py +362 -0
- pyxllib/autogui/virtualkey.py +102 -0
- pyxllib/autogui/wechat.py +827 -0
- pyxllib/autogui/wechat_msg.py +421 -0
- pyxllib/autogui/wxautolib.py +84 -0
- pyxllib/cv/__init__.py +1 -11
- pyxllib/cv/expert.py +267 -0
- pyxllib/cv/{imlib.py → imfile.py} +18 -83
- pyxllib/cv/imhash.py +39 -0
- pyxllib/cv/pupil.py +9 -0
- pyxllib/cv/rgbfmt.py +1525 -0
- pyxllib/cv/slidercaptcha.py +137 -0
- pyxllib/cv/trackbartools.py +163 -49
- pyxllib/cv/xlcvlib.py +1040 -0
- pyxllib/cv/xlpillib.py +423 -0
- pyxllib/data/__init__.py +0 -0
- pyxllib/data/echarts.py +240 -0
- pyxllib/data/jsonlib.py +89 -0
- pyxllib/{util/oss2_.py → data/oss.py} +11 -9
- pyxllib/data/pglib.py +1127 -0
- pyxllib/data/sqlite.py +568 -0
- pyxllib/{util → data}/sqllib.py +13 -31
- pyxllib/ext/JLineViewer.py +505 -0
- pyxllib/ext/__init__.py +6 -0
- pyxllib/{util → ext}/demolib.py +119 -35
- pyxllib/ext/drissionlib.py +277 -0
- pyxllib/ext/kq5034lib.py +12 -0
- pyxllib/{util/main.py → ext/old.py} +122 -284
- pyxllib/ext/qt.py +449 -0
- pyxllib/ext/robustprocfile.py +497 -0
- pyxllib/ext/seleniumlib.py +76 -0
- pyxllib/{util/tklib.py → ext/tk.py} +10 -11
- pyxllib/ext/unixlib.py +827 -0
- pyxllib/ext/utools.py +351 -0
- pyxllib/{util/webhooklib.py → ext/webhook.py} +45 -17
- pyxllib/ext/win32lib.py +40 -0
- pyxllib/ext/wjxlib.py +88 -0
- pyxllib/ext/wpsapi.py +124 -0
- pyxllib/ext/xlwork.py +9 -0
- pyxllib/ext/yuquelib.py +1105 -0
- pyxllib/file/__init__.py +17 -0
- pyxllib/file/docxlib.py +761 -0
- pyxllib/{util → file}/gitlib.py +40 -27
- pyxllib/file/libreoffice.py +165 -0
- pyxllib/file/movielib.py +148 -0
- pyxllib/file/newbie.py +10 -0
- pyxllib/file/onenotelib.py +1469 -0
- pyxllib/file/packlib/__init__.py +330 -0
- pyxllib/{util → file/packlib}/zipfile.py +598 -195
- pyxllib/file/pdflib.py +426 -0
- pyxllib/file/pupil.py +185 -0
- pyxllib/file/specialist/__init__.py +685 -0
- pyxllib/{basic/_5_dirlib.py → file/specialist/dirlib.py} +364 -93
- pyxllib/file/specialist/download.py +193 -0
- pyxllib/file/specialist/filelib.py +2829 -0
- pyxllib/file/xlsxlib.py +3131 -0
- pyxllib/file/xlsyncfile.py +341 -0
- pyxllib/prog/__init__.py +5 -0
- pyxllib/prog/cachetools.py +64 -0
- pyxllib/prog/deprecatedlib.py +233 -0
- pyxllib/prog/filelock.py +42 -0
- pyxllib/prog/ipyexec.py +253 -0
- pyxllib/prog/multiprogs.py +940 -0
- pyxllib/prog/newbie.py +451 -0
- pyxllib/prog/pupil.py +1197 -0
- pyxllib/{sitepackages.py → prog/sitepackages.py} +5 -3
- pyxllib/prog/specialist/__init__.py +391 -0
- pyxllib/prog/specialist/bc.py +203 -0
- pyxllib/prog/specialist/browser.py +497 -0
- pyxllib/prog/specialist/common.py +347 -0
- pyxllib/prog/specialist/datetime.py +199 -0
- pyxllib/prog/specialist/tictoc.py +240 -0
- pyxllib/prog/specialist/xllog.py +180 -0
- pyxllib/prog/xlosenv.py +108 -0
- pyxllib/stdlib/__init__.py +17 -0
- pyxllib/{util → stdlib}/tablepyxl/__init__.py +1 -3
- pyxllib/{util → stdlib}/tablepyxl/style.py +1 -1
- pyxllib/{util → stdlib}/tablepyxl/tablepyxl.py +2 -4
- pyxllib/text/__init__.py +8 -0
- pyxllib/text/ahocorasick.py +39 -0
- pyxllib/text/airscript.js +744 -0
- pyxllib/text/charclasslib.py +121 -0
- pyxllib/text/jiebalib.py +267 -0
- pyxllib/text/jinjalib.py +32 -0
- pyxllib/text/jsa_ai_prompt.md +271 -0
- pyxllib/text/jscode.py +922 -0
- pyxllib/text/latex/__init__.py +158 -0
- pyxllib/text/levenshtein.py +303 -0
- pyxllib/text/nestenv.py +1215 -0
- pyxllib/text/newbie.py +300 -0
- pyxllib/text/pupil/__init__.py +8 -0
- pyxllib/text/pupil/common.py +1121 -0
- pyxllib/text/pupil/xlalign.py +326 -0
- pyxllib/text/pycode.py +47 -0
- pyxllib/text/specialist/__init__.py +8 -0
- pyxllib/text/specialist/common.py +112 -0
- pyxllib/text/specialist/ptag.py +186 -0
- pyxllib/text/spellchecker.py +172 -0
- pyxllib/text/templates/echart_base.html +11 -0
- pyxllib/text/templates/highlight_code.html +17 -0
- pyxllib/text/templates/latex_editor.html +103 -0
- pyxllib/text/vbacode.py +17 -0
- pyxllib/text/xmllib.py +747 -0
- pyxllib/xl.py +39 -0
- pyxllib/xlcv.py +17 -0
- pyxllib-0.3.197.dist-info/METADATA +48 -0
- pyxllib-0.3.197.dist-info/RECORD +126 -0
- {pyxllib-0.0.43.dist-info → pyxllib-0.3.197.dist-info}/WHEEL +4 -5
- pyxllib/basic/_1_strlib.py +0 -945
- pyxllib/basic/_2_timelib.py +0 -488
- pyxllib/basic/_3_pathlib.py +0 -916
- pyxllib/basic/_4_loglib.py +0 -419
- pyxllib/basic/__init__.py +0 -54
- pyxllib/basic/arrow_.py +0 -250
- pyxllib/basic/chardet_.py +0 -66
- pyxllib/basic/dirlib.py +0 -529
- pyxllib/basic/dprint.py +0 -202
- pyxllib/basic/extension.py +0 -12
- pyxllib/basic/judge.py +0 -31
- pyxllib/basic/log.py +0 -204
- pyxllib/basic/pathlib_.py +0 -705
- pyxllib/basic/pytictoc.py +0 -102
- pyxllib/basic/qiniu_.py +0 -61
- pyxllib/basic/strlib.py +0 -761
- pyxllib/basic/timer.py +0 -132
- pyxllib/cv/cv.py +0 -834
- pyxllib/cv/cvlib/_1_geo.py +0 -543
- pyxllib/cv/cvlib/_2_cvprcs.py +0 -309
- pyxllib/cv/cvlib/_2_imgproc.py +0 -594
- pyxllib/cv/cvlib/_3_pilprcs.py +0 -80
- pyxllib/cv/cvlib/_4_cvimg.py +0 -211
- pyxllib/cv/cvlib/__init__.py +0 -10
- pyxllib/cv/debugtools.py +0 -82
- pyxllib/cv/fitz_.py +0 -300
- pyxllib/cv/installer.py +0 -42
- pyxllib/debug/_0_installer.py +0 -38
- pyxllib/debug/_1_typelib.py +0 -277
- pyxllib/debug/_2_chrome.py +0 -198
- pyxllib/debug/_3_showdir.py +0 -161
- pyxllib/debug/_4_bcompare.py +0 -140
- pyxllib/debug/__init__.py +0 -49
- pyxllib/debug/bcompare.py +0 -132
- pyxllib/debug/chrome.py +0 -198
- pyxllib/debug/installer.py +0 -38
- pyxllib/debug/showdir.py +0 -158
- pyxllib/debug/typelib.py +0 -278
- pyxllib/image/__init__.py +0 -12
- pyxllib/torch/__init__.py +0 -20
- pyxllib/torch/modellib.py +0 -37
- pyxllib/torch/trainlib.py +0 -344
- pyxllib/util/__init__.py +0 -20
- pyxllib/util/aip_.py +0 -141
- pyxllib/util/casiadb.py +0 -59
- pyxllib/util/excellib.py +0 -495
- pyxllib/util/filelib.py +0 -612
- pyxllib/util/jsondata.py +0 -27
- pyxllib/util/jsondata2.py +0 -92
- pyxllib/util/labelmelib.py +0 -139
- pyxllib/util/onepy/__init__.py +0 -29
- pyxllib/util/onepy/onepy.py +0 -574
- pyxllib/util/onepy/onmanager.py +0 -170
- pyxllib/util/pyautogui_.py +0 -219
- pyxllib/util/textlib.py +0 -1305
- pyxllib/util/unorder.py +0 -22
- pyxllib/util/xmllib.py +0 -639
- pyxllib-0.0.43.dist-info/METADATA +0 -39
- pyxllib-0.0.43.dist-info/RECORD +0 -80
- pyxllib-0.0.43.dist-info/top_level.txt +0 -1
- {pyxllib-0.0.43.dist-info → pyxllib-0.3.197.dist-info/licenses}/LICENSE +0 -0
pyxllib/basic/pathlib_.py
DELETED
@@ -1,705 +0,0 @@
|
|
1
|
-
#!/usr/bin/env python3
|
2
|
-
# -*- coding: utf-8 -*-
|
3
|
-
# @Author : 陈坤泽
|
4
|
-
# @Email : 877362867@qq.com
|
5
|
-
# @Data : 2020/05/30 20:37
|
6
|
-
|
7
|
-
|
8
|
-
import json
|
9
|
-
import os
|
10
|
-
import pathlib
|
11
|
-
import pickle
|
12
|
-
import re
|
13
|
-
import shutil
|
14
|
-
import subprocess
|
15
|
-
import tempfile
|
16
|
-
|
17
|
-
from .arrow_ import Datetime
|
18
|
-
from .chardet_ import get_encoding
|
19
|
-
|
20
|
-
|
21
|
-
class Path:
|
22
|
-
r""" 通用文件、路径处理类,也可以处理目录,可以把目录对象理解为特殊类型的文件
|
23
|
-
|
24
|
-
document: https://www.yuque.com/xlpr/python/pyxllib.debug.path
|
25
|
-
|
26
|
-
大部分基础功能是从pathlib.Path衍生过来的
|
27
|
-
但开发中该类不能直接从Path继承,会有很多问题
|
28
|
-
|
29
|
-
TODO 这里的doctest过于针对自己的电脑了,应该改成更具适用性测试代码
|
30
|
-
"""
|
31
|
-
__slots__ = ('_path', 'assume_dir')
|
32
|
-
|
33
|
-
# 零、常用的目录类
|
34
|
-
TEMP = tempfile.gettempdir()
|
35
|
-
if os.environ.get('Desktop', None): # 如果修改了win10默认的桌面路径,需要在环境变量添加一个正确的Desktop路径值
|
36
|
-
DESKTOP = os.environ['Desktop']
|
37
|
-
else:
|
38
|
-
DESKTOP = os.path.join(str(pathlib.Path.home()), 'Desktop') # 这个不一定准,桌面是有可能被移到D盘等的
|
39
|
-
|
40
|
-
# 一、基础功能
|
41
|
-
|
42
|
-
def __init__(self, path=None, suffix=None, root=None):
|
43
|
-
r""" 初始化参数含义详见 abspath 函数解释
|
44
|
-
|
45
|
-
TODO 这个初始化也有点过于灵活了,需要降低灵活性,增加使用清晰度
|
46
|
-
|
47
|
-
>>> Path('D:/pycode/code4101py')
|
48
|
-
Path('D:/pycode/code4101py')
|
49
|
-
>>> Path(Path('D:/pycode/code4101py'))
|
50
|
-
Path('D:/pycode/code4101py')
|
51
|
-
|
52
|
-
>> Path() # 不输入参数的时候,默认为当前工作目录
|
53
|
-
Path('D:/pycode/code4101py')
|
54
|
-
|
55
|
-
>>> Path('a.txt', root=Path.TEMP)
|
56
|
-
Path('F:/work/CreatorTemp/a.txt')
|
57
|
-
>>> Path('F:/work/CreatorTemp')
|
58
|
-
Path('F:/work/CreatorTemp')
|
59
|
-
|
60
|
-
注意!如果使用了符号链接(软链接),则路径是会解析转向实际位置的!例如
|
61
|
-
>> Path('D:/pycode/code4101py')
|
62
|
-
Path('D:/slns/pycode/code4101py')
|
63
|
-
"""
|
64
|
-
strpath = str(path)
|
65
|
-
self._path = None
|
66
|
-
self.assume_dir = False # 假设是一个目录
|
67
|
-
if len(strpath) and strpath[-1] in r'\/':
|
68
|
-
self.assume_dir = True
|
69
|
-
|
70
|
-
try:
|
71
|
-
self._path = pathlib.Path(self.abspath(path, suffix, root)).resolve()
|
72
|
-
# 有些问题上一步不一定测的出来,要再补一个测试
|
73
|
-
self._path.is_file()
|
74
|
-
except (ValueError, TypeError, OSError):
|
75
|
-
# ValueError:文件名过长,代表输入很可能是一段文本,根本不是路径
|
76
|
-
# TypeError:不是str等正常的参数
|
77
|
-
# OSError:非法路径名,例如有 *? 等
|
78
|
-
self._path = None
|
79
|
-
|
80
|
-
@staticmethod
|
81
|
-
def abspath(path=None, suffix=None, root=None) -> str:
|
82
|
-
r""" 根据各种不同的组合参数信息,推导出具体的路径位置
|
83
|
-
|
84
|
-
(即把人常识易理解的指定思维,转成计算机能清晰明白的具体指向对象)
|
85
|
-
:param path: 主要的参数,如果后面的参数有矛盾,以path为最高参考标准
|
86
|
-
'',可以输入空字符串,效果跟None是不一样的,意思是显示地指明要生成一个随机名称的文件名
|
87
|
-
:param suffix:
|
88
|
-
以 '.' 指明的扩展名,会强制替换
|
89
|
-
否则,只作为参考扩展名,只在原有path没有指明的时候才添加
|
90
|
-
:param root: 未输入的时候,则为当前工作目录
|
91
|
-
|
92
|
-
>> Path.abspath()
|
93
|
-
'C:\\pycode\\code4101py'
|
94
|
-
|
95
|
-
>> Path.abspath('a')
|
96
|
-
'C:\\pycode\\code4101py\\a'
|
97
|
-
|
98
|
-
>> Path.abspath('F:/work', '.txt') # F:/work是个实际存在的目录
|
99
|
-
'F:/work\\tmpahqm6nod.txt'
|
100
|
-
|
101
|
-
>>> Path.abspath('F:/work/a.txt', 'py') # 参考后缀不修改
|
102
|
-
'F:/work/a.txt'
|
103
|
-
>>> Path.abspath('F:/work/a.txt', '.py') # 强制后缀会修改
|
104
|
-
'F:/work/a.py'
|
105
|
-
|
106
|
-
>> Path.abspath(suffix='.tex', root=Path.TEMP)
|
107
|
-
'F:\\work\\CreatorTemp\\tmp5vo2lpqd.tex'
|
108
|
-
|
109
|
-
# F:/work/a.txt不存在,而且看起来像文件名,但是末尾再用/则显式指明这其实是一个目录
|
110
|
-
# 又用.py指明要添加一个随机名称的py文件
|
111
|
-
>> Path.abspath('F:/work/a.txt/', '.py')
|
112
|
-
'F:/work/a.txt/tmpg_q7a7ft.py'
|
113
|
-
|
114
|
-
>> Path.abspath('work/a.txt/', '.py', Path.TEMP) # 在临时文件夹下的work/a.txt目录新建一个随机名称的py文件
|
115
|
-
'F:\\work\\CreatorTemp\\work/a.txt/tmp2jn5cqkc.py'
|
116
|
-
|
117
|
-
>>> Path.abspath('C:/a.txt/') # 会保留最后的斜杠特殊标记
|
118
|
-
'C:/a.txt/'
|
119
|
-
>>> Path.abspath('C:/a.txt\\') # 会保留最后的斜杠特殊标记
|
120
|
-
'C:/a.txt\\'
|
121
|
-
"""
|
122
|
-
# 1 判断参考目录
|
123
|
-
if root:
|
124
|
-
root = str(root)
|
125
|
-
else:
|
126
|
-
root = os.getcwd()
|
127
|
-
|
128
|
-
# 2 判断主体文件名 path
|
129
|
-
if str(path) == '':
|
130
|
-
path = tempfile.mktemp(dir=root)
|
131
|
-
elif not path:
|
132
|
-
path = root
|
133
|
-
else:
|
134
|
-
path = os.path.join(root, str(path))
|
135
|
-
|
136
|
-
# 3 补充suffix
|
137
|
-
if suffix:
|
138
|
-
# 如果原来的path只是一个目录,则要新建一个文件
|
139
|
-
# if os.path.isdir(path) or path[-1] in ('\\', '/'):
|
140
|
-
if path[-1] in ('\\', '/'):
|
141
|
-
path = tempfile.mktemp(dir=path)
|
142
|
-
# 判断后缀
|
143
|
-
li = os.path.splitext(path)
|
144
|
-
if (not li[1]) or suffix[0] == '.':
|
145
|
-
ext = suffix if suffix[0] == '.' else ('.' + suffix)
|
146
|
-
path = li[0] + ext
|
147
|
-
|
148
|
-
return path
|
149
|
-
|
150
|
-
def __bool__(self):
|
151
|
-
return bool(self._path)
|
152
|
-
|
153
|
-
def exists(self):
|
154
|
-
r""" 判断文件是否存在
|
155
|
-
|
156
|
-
重置WindowsPath的bool逻辑,返回值变成存在True,不存在为False
|
157
|
-
|
158
|
-
>>> Path('D:/slns').exists()
|
159
|
-
True
|
160
|
-
>>> Path('D:/pycode/code4101').exists()
|
161
|
-
False
|
162
|
-
"""
|
163
|
-
return self._path and self._path.exists()
|
164
|
-
|
165
|
-
def __repr__(self):
|
166
|
-
return 'Path' + self._path.__repr__()[11:]
|
167
|
-
|
168
|
-
def __str__(self):
|
169
|
-
return str(self._path) + ('/' if self.assume_dir else '')
|
170
|
-
|
171
|
-
def __eq__(self, other):
|
172
|
-
if not isinstance(other, Path):
|
173
|
-
raise TypeError
|
174
|
-
return self._path == other._path
|
175
|
-
|
176
|
-
def __truediv__(self, key):
|
177
|
-
r""" 路径拼接功能
|
178
|
-
|
179
|
-
>>> Path('C:/a') / 'b.txt'
|
180
|
-
Path('C:/a/b.txt')
|
181
|
-
"""
|
182
|
-
return Path(self._path / str(key))
|
183
|
-
|
184
|
-
def resolve(self):
|
185
|
-
return self._path.resolve()
|
186
|
-
|
187
|
-
def glob(self, pattern):
|
188
|
-
return self._path.glob(pattern)
|
189
|
-
|
190
|
-
def match(self, path_pattern):
|
191
|
-
return self._path.match(path_pattern)
|
192
|
-
|
193
|
-
# 二、获取、修改路径中部分值的功能
|
194
|
-
|
195
|
-
@property
|
196
|
-
def fullpath(self) -> str:
|
197
|
-
return str(self._path)
|
198
|
-
|
199
|
-
@property
|
200
|
-
def drive(self) -> str:
|
201
|
-
return self._path.drive
|
202
|
-
|
203
|
-
@drive.setter
|
204
|
-
def drive(self, value):
|
205
|
-
"""修改磁盘位置"""
|
206
|
-
raise NotImplementedError
|
207
|
-
|
208
|
-
@property
|
209
|
-
def name(self) -> str:
|
210
|
-
r"""
|
211
|
-
>>> Path('D:/pycode/a.txt').name
|
212
|
-
'a.txt'
|
213
|
-
>>> Path('D:/pycode/code4101py').name
|
214
|
-
'code4101py'
|
215
|
-
>>> Path('D:/pycode/.gitignore').name
|
216
|
-
'.gitignore'
|
217
|
-
"""
|
218
|
-
return self._path.name
|
219
|
-
|
220
|
-
@name.setter
|
221
|
-
def name(self, value):
|
222
|
-
raise NotImplementedError
|
223
|
-
|
224
|
-
@property
|
225
|
-
def parent(self):
|
226
|
-
r"""
|
227
|
-
>>> Path('D:/pycode/code4101py').parent
|
228
|
-
Path('D:/pycode')
|
229
|
-
"""
|
230
|
-
return Path(self._path.parent) if self._path else None
|
231
|
-
|
232
|
-
@property
|
233
|
-
def dirname(self) -> str:
|
234
|
-
r"""
|
235
|
-
>>> Path('D:/pycode/code4101py').dirname
|
236
|
-
'D:\\pycode'
|
237
|
-
>>> Path(r'D:\toweb\a').dirname
|
238
|
-
'D:\\toweb'
|
239
|
-
"""
|
240
|
-
return str(self.parent)
|
241
|
-
|
242
|
-
def with_dirname(self, value):
|
243
|
-
return Path(self.name, root=value)
|
244
|
-
|
245
|
-
@property
|
246
|
-
def stem(self) -> str:
|
247
|
-
r"""
|
248
|
-
>>> Path('D:/pycode/code4101py/ckz.py').stem
|
249
|
-
'ckz'
|
250
|
-
>>> Path('D:/pycode/.gitignore').stem # os.path.splitext也是这种算法
|
251
|
-
'.gitignore'
|
252
|
-
>>> Path('D:/pycode/.123.45.6').stem
|
253
|
-
'.123.45'
|
254
|
-
"""
|
255
|
-
return self._path.stem
|
256
|
-
|
257
|
-
def with_stem(self, stem):
|
258
|
-
"""
|
259
|
-
注意不能用@stem.setter来做
|
260
|
-
如果setter要rename,那if_exists参数怎么控制?
|
261
|
-
如果setter不要rename,那和用with_stem实现有什么区别?
|
262
|
-
"""
|
263
|
-
return Path(stem, self.suffix, self.dirname)
|
264
|
-
|
265
|
-
@property
|
266
|
-
def parts(self) -> tuple:
|
267
|
-
r"""
|
268
|
-
>>> Path('D:/pycode/code4101py').parts
|
269
|
-
('D:\\', 'pycode', 'code4101py')
|
270
|
-
"""
|
271
|
-
return self._path.parts
|
272
|
-
|
273
|
-
@property
|
274
|
-
def suffix(self) -> str:
|
275
|
-
r"""
|
276
|
-
>>> Path('D:/pycode/code4101py/ckz.py').suffix
|
277
|
-
'.py'
|
278
|
-
>>> Path('D:/pycode/code4101py').suffix
|
279
|
-
''
|
280
|
-
>>> Path('D:/pycode/code4101py/ckz.').suffix
|
281
|
-
''
|
282
|
-
>>> Path('D:/pycode/code4101py/ckz.123.456').suffix
|
283
|
-
'.456'
|
284
|
-
>>> Path('D:/pycode/code4101py/ckz.123..456').suffix
|
285
|
-
'.456'
|
286
|
-
>>> Path('D:/pycode/.gitignore').suffix
|
287
|
-
''
|
288
|
-
"""
|
289
|
-
return self._path.suffix if self._path else ''
|
290
|
-
|
291
|
-
def with_suffix(self, suffix):
|
292
|
-
r""" with_suffix和suffix.setter区别是,前者是生成一个新指向的类,后者是重命名
|
293
|
-
|
294
|
-
>>> Path('a.txt').with_suffix('.py').fullpath.split('\\')[-1] # 强制替换
|
295
|
-
'a.py'
|
296
|
-
>>> Path('a.txt').with_suffix('py').fullpath.split('\\')[-1] # 参考替换
|
297
|
-
'a.txt'
|
298
|
-
>>> Path('a.txt').with_suffix('').fullpath.split('\\')[-1] # 删除
|
299
|
-
'a'
|
300
|
-
"""
|
301
|
-
if suffix and (suffix[0] == '.' or not self.suffix):
|
302
|
-
if suffix[0] != '.': suffix = '.' + suffix
|
303
|
-
return Path(self._path.with_suffix(suffix))
|
304
|
-
elif not suffix:
|
305
|
-
# suffix 为假值则删除扩展名
|
306
|
-
return Path(self.stem, '', self.dirname)
|
307
|
-
return self
|
308
|
-
|
309
|
-
@property
|
310
|
-
def backup_time(self):
|
311
|
-
r""" 返回文件的备份时间戳,如果并不是备份文件,则返回空字符串
|
312
|
-
|
313
|
-
备份文件都遵循特定的命名规范
|
314
|
-
如果是文件,是:'chePre 171020-153959.tex'
|
315
|
-
如果是目录,是:'figs 171020-153959'
|
316
|
-
通过后缀分析,可以判断这是不是一个备份文件
|
317
|
-
|
318
|
-
>>> Path('chePre 171020-153959.tex').backup_time
|
319
|
-
'171020-153959'
|
320
|
-
>>> Path('figs 171020-153959').backup_time
|
321
|
-
'171020-153959'
|
322
|
-
>>> Path('figs 171020').backup_time
|
323
|
-
''
|
324
|
-
"""
|
325
|
-
name = self.stem
|
326
|
-
if len(name) < 14:
|
327
|
-
return ''
|
328
|
-
g = re.match(r'(\d{6}-\d{6})', name[-13:])
|
329
|
-
return g.group(1) if g else ''
|
330
|
-
|
331
|
-
# 三、获取文件相关属性值功能
|
332
|
-
|
333
|
-
def is_dir(self):
|
334
|
-
return self._path and self._path.is_dir()
|
335
|
-
|
336
|
-
def is_file(self):
|
337
|
-
return self._path and self._path.is_file()
|
338
|
-
|
339
|
-
@property
|
340
|
-
def encoding(self):
|
341
|
-
""" 文件的编码
|
342
|
-
|
343
|
-
非文件、不存在时返回 None
|
344
|
-
"""
|
345
|
-
if self.is_file():
|
346
|
-
return get_encoding(self.fullpath)
|
347
|
-
return None
|
348
|
-
|
349
|
-
@property
|
350
|
-
def size(self) -> int:
|
351
|
-
""" 计算文件、目录的大小,对于目录,会递归目录计算总大小
|
352
|
-
|
353
|
-
https://stackoverflow.com/questions/1392413/calculating-a-directory-size-using-python
|
354
|
-
|
355
|
-
>> Path('D:/slns/pyxllib').size # 这个算的就是真实大小,不是占用空间
|
356
|
-
2939384
|
357
|
-
"""
|
358
|
-
path = str(self._path)
|
359
|
-
if self._path.is_file():
|
360
|
-
total_size = os.path.getsize(path)
|
361
|
-
elif self._path.is_dir():
|
362
|
-
total_size = 0
|
363
|
-
for dirpath, dirnames, Pathnames in os.walk(path):
|
364
|
-
for f in Pathnames:
|
365
|
-
fp = os.path.join(dirpath, f)
|
366
|
-
total_size += os.path.getsize(fp)
|
367
|
-
else: # 不存在的对象
|
368
|
-
total_size = 0
|
369
|
-
return total_size
|
370
|
-
|
371
|
-
@property
|
372
|
-
def mtime(self) -> Datetime:
|
373
|
-
r""" 文件的最近修改时间
|
374
|
-
|
375
|
-
>> Path(r"C:\pycode\code4101py").mtime
|
376
|
-
2020-03-10 17:32:37
|
377
|
-
"""
|
378
|
-
return Datetime(os.stat(self.fullpath).st_mtime)
|
379
|
-
|
380
|
-
@property
|
381
|
-
def ctime(self) -> Datetime:
|
382
|
-
r""" 文件的创建时间
|
383
|
-
|
384
|
-
>> Path(r"C:\pycode\code4101py").ctime
|
385
|
-
2018-05-25 10:46:37
|
386
|
-
"""
|
387
|
-
# 注意:st_ctime是平台相关的值,在windows是创建时间,但在Unix是metadate最近修改时间
|
388
|
-
return Datetime(os.stat(self.fullpath).st_ctime)
|
389
|
-
|
390
|
-
# 四、文件操作功能
|
391
|
-
|
392
|
-
def abs_dstpath(self, dst=None, suffix=None, root=None) -> str:
|
393
|
-
r""" 参照当前Path的父目录,来确定dst的具体路径
|
394
|
-
|
395
|
-
>>> f = Path('C:/Windows/System32/cmd.exe')
|
396
|
-
>>> f.abs_dstpath('chen.py')
|
397
|
-
'C:\\Windows\\System32\\chen.py'
|
398
|
-
>>> f.abs_dstpath('E:/') # 原始文件必须存在,否则因为无法判断实际类型,目标路径可能会错
|
399
|
-
'E:/cmd.exe'
|
400
|
-
>>> f.abs_dstpath('D:/aabbccdd.txt')
|
401
|
-
'D:/aabbccdd.txt'
|
402
|
-
>>> f.abs_dstpath('D:/aabbccdd.txt/') # 并不存在aabbccdd.txt这样的对象,但末尾有个/表明这是个目录
|
403
|
-
'D:/aabbccdd.txt/cmd.exe'
|
404
|
-
"""
|
405
|
-
if not root: root = self.dirname
|
406
|
-
dst = Path(dst, suffix, root)
|
407
|
-
# print(dst, dst.assume_dir)
|
408
|
-
|
409
|
-
if self.is_dir() or (self.assume_dir and not self.is_file()):
|
410
|
-
if dst.is_file():
|
411
|
-
raise ValueError(f'{dst}是已存在的文件类型,不能对{self}目录执行指定操作')
|
412
|
-
elif dst.assume_dir: # 明确要把目录放到另一个目录下
|
413
|
-
dst = dst / self.name
|
414
|
-
else: # 否则self是文件,或者不存在,均视为文件处理
|
415
|
-
if dst.is_dir() or dst.assume_dir:
|
416
|
-
dst = dst / self.name
|
417
|
-
# 否则dst是文件,或者不存在的路径,均视为文件类型处理
|
418
|
-
|
419
|
-
return dst.fullpath
|
420
|
-
|
421
|
-
def process(self, dst, func, if_exists='error', arg1=None, arg2=None):
|
422
|
-
r""" copy或move的本质底层实现
|
423
|
-
|
424
|
-
文件复制等操作中src、dst不同组合下的效果:https://www.yuque.com/xlpr/pyxllib/mgwe19
|
425
|
-
|
426
|
-
:param dst: 目标路径对象,注意如果使用相对路径,是相对于self的路径!
|
427
|
-
:param if_exists:
|
428
|
-
'error': (默认)如果要替换的目标文件已经存在,则报错
|
429
|
-
'replace': 替换
|
430
|
-
'ignore': 忽略、不处理
|
431
|
-
'backup': 备份后写入
|
432
|
-
:param func: 传入arg1和arg2参数,可以自定义
|
433
|
-
默认分别是self和dst的fullpath
|
434
|
-
:return : 返回dst
|
435
|
-
"""
|
436
|
-
# 1 分类处理,确定实际dst位置
|
437
|
-
dst = Path(self.abs_dstpath(dst))
|
438
|
-
|
439
|
-
# 2 判断目标是有已存在,进行不同的指定规则处理
|
440
|
-
need_run = True
|
441
|
-
if dst.exists():
|
442
|
-
if dst == self and arg1 is None and arg2 is None:
|
443
|
-
# 同一个文件,估计只是修改大小写名称,或者就是要原地操作
|
444
|
-
# 不做任何特殊处理,准备直接跑函数
|
445
|
-
# 200601周一19:23:要补arg1、arg2的判断,不然Path.write会出错
|
446
|
-
pass
|
447
|
-
elif if_exists == 'error':
|
448
|
-
raise FileExistsError(f'目标文件已存在: {self} — {func.__name__} —> {dst}')
|
449
|
-
elif if_exists == 'replace': # None的话相当于replace,但是不会事先delete,可能会报错
|
450
|
-
dst.delete()
|
451
|
-
elif if_exists == 'ignore':
|
452
|
-
need_run = False
|
453
|
-
elif if_exists == 'backup':
|
454
|
-
dst.backup(if_exists='backup')
|
455
|
-
dst.delete()
|
456
|
-
|
457
|
-
# 3 执行特定功能
|
458
|
-
if need_run:
|
459
|
-
# 此时dst已是具体路径,哪怕是"目录"也可以按照"文件"对象理解,避免目录会重复生成,多层嵌套
|
460
|
-
# 本来是 a --> C:/target/a ,避免 C:/target/a/a 的bug
|
461
|
-
dst.ensure_dir('file')
|
462
|
-
if arg1 is None: arg1 = self.fullpath
|
463
|
-
if arg2 is None: arg2 = dst.fullpath
|
464
|
-
func(arg1, arg2)
|
465
|
-
return dst
|
466
|
-
|
467
|
-
def ensure_dir(self, pathtype=None):
|
468
|
-
r""" 确保path中指定的dir都存在
|
469
|
-
|
470
|
-
:param pathtype: 如果self.path的对象不存在,根据self.path的类型不同,有不同的处理方案
|
471
|
-
'dir':则包括自身也会创建一个空目录
|
472
|
-
'file': 只会创建到dirname所在目录,并不会创建自身的Path
|
473
|
-
'None':通过名称智能判断,如果能读取到suffix,则代表是Path类型,否则是dir类型
|
474
|
-
:return:
|
475
|
-
|
476
|
-
>> Path(r'D:\toweb\a\b.txt').ensure_dir() # 只会创建toweb、a目录
|
477
|
-
|
478
|
-
# a和一个叫b.txt的目录都会创建
|
479
|
-
# 当然,如果b.txt是一个已经存在的文件对象,则该函数不会进行操作
|
480
|
-
>> Path(r'D:\toweb\a\b.txt').ensure_dir(pathtype='dir')
|
481
|
-
"""
|
482
|
-
if not self.exists():
|
483
|
-
if not pathtype:
|
484
|
-
pathtype = 'file' if (not self.assume_dir and self.suffix) else 'dir'
|
485
|
-
dirname = self.fullpath if pathtype == 'dir' else self.dirname
|
486
|
-
if not os.path.exists(dirname):
|
487
|
-
os.makedirs(dirname)
|
488
|
-
|
489
|
-
def copy(self, dst, if_exists='error'):
|
490
|
-
""" 复制文件
|
491
|
-
|
492
|
-
"""
|
493
|
-
if self.is_dir():
|
494
|
-
return self.process(dst, shutil.copytree, if_exists)
|
495
|
-
elif self.is_file():
|
496
|
-
return self.process(dst, shutil.copy2, if_exists)
|
497
|
-
|
498
|
-
def move(self, dst, if_exists='error'):
|
499
|
-
""" 移动文件
|
500
|
-
|
501
|
-
"""
|
502
|
-
if self.exists():
|
503
|
-
return self.process(dst, shutil.move, if_exists)
|
504
|
-
|
505
|
-
def rename(self, dst, if_exists='error'):
|
506
|
-
r""" 文件重命名,或者也可以理解成文件移动
|
507
|
-
|
508
|
-
:param dst: 如果使用相对目录,则是该类本身所在的目录作为工作环境
|
509
|
-
:param if_exists:
|
510
|
-
'error': 如果要替换的目标文件已经存在,则报错
|
511
|
-
'replace': 替换
|
512
|
-
'ignore': 忽略、不处理
|
513
|
-
'backup': 备份后替换
|
514
|
-
:return:
|
515
|
-
"""
|
516
|
-
# rename是move的一种特殊情况
|
517
|
-
return self.move(dst, if_exists)
|
518
|
-
|
519
|
-
def delete(self):
|
520
|
-
r""" 删除自身文件
|
521
|
-
|
522
|
-
"""
|
523
|
-
if self.is_file():
|
524
|
-
os.remove(self.fullpath)
|
525
|
-
elif self.is_dir():
|
526
|
-
shutil.rmtree(self.fullpath)
|
527
|
-
# TODO 确保删除后再执行后续代码 但是一直觉得这样写很别扭
|
528
|
-
while self.exists(): pass
|
529
|
-
|
530
|
-
def backup(self, tail=None, if_exists='replace', move=False):
|
531
|
-
r""" 对文件末尾添加时间戳备份,也可以使用自定义标记tail
|
532
|
-
|
533
|
-
:param tail: 自定义添加后缀
|
534
|
-
tail为None时,默认添加特定格式的时间戳
|
535
|
-
:param if_exists: 备份的目标文件名存在时的处理方案
|
536
|
-
:param move:
|
537
|
-
是否删除原始文件
|
538
|
-
|
539
|
-
# TODO:有个小bug,如果在不同时间实际都是相同一个文件,也会被不断反复备份
|
540
|
-
# 如果想解决这个,就要读取目录下最近的备份文件对比内容了
|
541
|
-
"""
|
542
|
-
# 1 判断自身文件是否存在
|
543
|
-
if not self.exists():
|
544
|
-
return None
|
545
|
-
|
546
|
-
# 2 计算出新名称
|
547
|
-
if not tail:
|
548
|
-
tail = self.mtime.strftime(' %y%m%d-%H%M%S') # 时间戳
|
549
|
-
name, ext = os.path.splitext(self.fullpath)
|
550
|
-
dst = name + tail + ext
|
551
|
-
|
552
|
-
# 3 备份就是特殊的copy操作
|
553
|
-
if move:
|
554
|
-
return self.move(dst, if_exists)
|
555
|
-
else:
|
556
|
-
return self.copy(dst, if_exists)
|
557
|
-
|
558
|
-
# 五、其他综合性功能
|
559
|
-
|
560
|
-
def read(self, *, encoding=None, mode=None):
|
561
|
-
""" 读取文件
|
562
|
-
|
563
|
-
:param encoding: 文件编码
|
564
|
-
默认None,则在需要使用encoding参数的场合,会使用self.encoding自动判断编码
|
565
|
-
:param mode: 读取模式(例如 '.json'),默认从扩展名识别,也可以强制指定
|
566
|
-
:return:
|
567
|
-
"""
|
568
|
-
if self.is_file(): # 如果存在这样的文件,那就读取文件内容
|
569
|
-
# 获得文件扩展名,并统一转成小写
|
570
|
-
name, suffix = self.fullpath, self.suffix
|
571
|
-
if not mode: mode = suffix
|
572
|
-
mode = mode.lower()
|
573
|
-
if mode == 'bytes':
|
574
|
-
with open(name, 'rb') as f:
|
575
|
-
return f.read()
|
576
|
-
elif mode == '.pkl': # pickle库
|
577
|
-
with open(name, 'rb') as f:
|
578
|
-
return pickle.load(f)
|
579
|
-
elif mode == '.json':
|
580
|
-
import json
|
581
|
-
# 先读成字符串,再解析,会比rb鲁棒性更强,能自动过滤掉开头可能非正文特殊标记的字节
|
582
|
-
if not encoding: encoding = self.encoding
|
583
|
-
with open(name, 'r', encoding=encoding) as f:
|
584
|
-
return json.loads(f.read())
|
585
|
-
elif mode == '.yaml':
|
586
|
-
import yaml
|
587
|
-
with open(name, 'r', encoding=encoding) as f:
|
588
|
-
return yaml.safe_load(f.read())
|
589
|
-
elif mode in ('.jpg', '.jpeg', '.png', '.bmp'):
|
590
|
-
with open(name, 'rb') as fp:
|
591
|
-
return fp.read()
|
592
|
-
else:
|
593
|
-
with open(name, 'rb') as f:
|
594
|
-
bstr = f.read()
|
595
|
-
if not encoding: encoding = self.encoding
|
596
|
-
s = bstr.decode(encoding=encoding, errors='ignore')
|
597
|
-
if '\r' in s: s = s.replace('\r\n', '\n') # 如果用\r\n作为换行符会有一些意外不好处理
|
598
|
-
return s
|
599
|
-
else: # 非文件对象
|
600
|
-
raise FileNotFoundError('文件不存在,无法读取。')
|
601
|
-
|
602
|
-
def write(self, ob, *, encoding='utf8', if_exists='error', etag=False, mode=None):
|
603
|
-
""" 保存为文件
|
604
|
-
|
605
|
-
:param ob: 写入的内容
|
606
|
-
如果要写txt文本文件且ob不是文本对象,只会进行简单的字符串化
|
607
|
-
:param encoding: 强制写入的编码
|
608
|
-
:param if_exists: 如果文件已存在,要进行的操作
|
609
|
-
:param etag: 创建的文件,是否需要再进一步重命名为etag名称
|
610
|
-
:param mode: 写入模式(例如 '.json'),默认从扩展名识别,也可以强制指定
|
611
|
-
:return: 返回写入的文件名,这个主要是在写临时文件时有用
|
612
|
-
"""
|
613
|
-
|
614
|
-
# 1 核心写入功能
|
615
|
-
def data2file(ob, path):
|
616
|
-
"""将ob写入文件path,如果path已存在,也会被直接覆盖"""
|
617
|
-
nonlocal mode
|
618
|
-
path.ensure_dir(pathtype='file')
|
619
|
-
name, suffix = path.fullpath, path.suffix
|
620
|
-
if not mode: mode = suffix
|
621
|
-
mode = mode.lower()
|
622
|
-
if mode == '.pkl':
|
623
|
-
with open(name, 'wb') as f:
|
624
|
-
pickle.dump(ob, f)
|
625
|
-
elif mode == '.json':
|
626
|
-
with open(name, 'w', encoding=encoding) as f:
|
627
|
-
json.dump(ob, f, ensure_ascii=False, indent=2)
|
628
|
-
elif mode == '.yaml':
|
629
|
-
import yaml
|
630
|
-
with open(name, 'w', encoding=encoding) as f:
|
631
|
-
yaml.dump(ob, f)
|
632
|
-
elif isinstance(ob, bytes):
|
633
|
-
# print(name) # TODO 本少发现个bug:导出平台讲义到本地(r'https://tr.histudy.com/#/dtextbook/202005159c00ce57bdd342c3a879ebb1a2e5ed21', path=r'R:/平台挑题', download_pictures=True)
|
634
|
-
# 老师们从其它网站上抄来的题 可能是 \href{/tr/item/202006038365bfee56b244b2bfbd02e2b25b1500/image_41825807541591152423871.png}{\includegraphics{菁优网:http://www.jyeoo.com}}
|
635
|
-
# 而在这里 name = "菁优网:http://www.jyeoo.com" 含有 "/" 不能作为文件名,这应该是平台的锅
|
636
|
-
with open(name, 'wb') as f:
|
637
|
-
f.write(ob)
|
638
|
-
else: # 其他类型认为是文本类型
|
639
|
-
with open(name, 'w', errors='ignore', encoding=encoding) as f:
|
640
|
-
f.write(str(ob))
|
641
|
-
|
642
|
-
# 2 推导出目标文件的完整名,并判断是否存在,进行不同处理
|
643
|
-
self.process(self, data2file, if_exists, arg1=ob, arg2=self)
|
644
|
-
if etag:
|
645
|
-
from .qiniu_ import get_etag
|
646
|
-
return self.rename(get_etag(self.fullpath) + self.suffix, if_exists='ignore')
|
647
|
-
else:
|
648
|
-
return self
|
649
|
-
|
650
|
-
def explorer(self, proc='explorer'):
|
651
|
-
""" 使用windows的explorer命令打开文件
|
652
|
-
|
653
|
-
还有个类似的万能打开命令 start
|
654
|
-
|
655
|
-
:param proc: 可以自定义要执行的主程序
|
656
|
-
"""
|
657
|
-
subprocess.run([proc, self.fullpath])
|
658
|
-
|
659
|
-
|
660
|
-
def demo_path():
|
661
|
-
"""Path类的综合测试"""
|
662
|
-
# 切换工作目录到临时文件夹
|
663
|
-
os.chdir(Path.TEMP)
|
664
|
-
|
665
|
-
p = Path('demo_path', root=Path.TEMP)
|
666
|
-
p.delete() # 如果存在先删除
|
667
|
-
p.ensure_dir() # 然后再创建一个空目录
|
668
|
-
|
669
|
-
print(Path('demo_path', '.py'))
|
670
|
-
# F:\work\CreatorTemp\demo_path.py
|
671
|
-
|
672
|
-
print(Path('demo_path/', '.py'))
|
673
|
-
# F:\work\CreatorTemp\demo_path\tmp65m8mc0b.py
|
674
|
-
|
675
|
-
# 空字符串区别于None,会随机生成一个文件名
|
676
|
-
print(Path('', root=Path.TEMP))
|
677
|
-
# F:\work\CreatorTemp\tmpwp4g1692
|
678
|
-
|
679
|
-
# 可以在随机名称基础上,再指定文件扩展名
|
680
|
-
print(Path('', '.txt', root=Path.TEMP))
|
681
|
-
# F:\work\CreatorTemp\tmpimusjtu1.txt
|
682
|
-
|
683
|
-
|
684
|
-
def demo_path_rename():
|
685
|
-
# 初始化一个空的测试目录
|
686
|
-
f = Path('temp/', root=Path.TEMP)
|
687
|
-
f.delete()
|
688
|
-
f.ensure_dir()
|
689
|
-
|
690
|
-
# 建一个空文件
|
691
|
-
f1 = f / 'a.txt'
|
692
|
-
# Path('F:/work/CreatorTemp/temp/a.txt')
|
693
|
-
with open(f1.fullpath, 'wb') as p: pass # 写一个空文件
|
694
|
-
|
695
|
-
f1.rename('A.tXt') # 重命名
|
696
|
-
# Path('F:/work/CreatorTemp/temp/a.txt')
|
697
|
-
|
698
|
-
f1.rename('figs/b') # 放到一个新的子目录里,并再次重命名
|
699
|
-
# Path('F:/work/CreatorTemp/temp/figs/b')
|
700
|
-
|
701
|
-
# TODO 把目录下的1 2 3 4 5重命名为5 4 3 2 1时要怎么搞?
|
702
|
-
|
703
|
-
|
704
|
-
if __name__ == '__main__':
|
705
|
-
demo_path_rename()
|