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/_3_pathlib.py
DELETED
@@ -1,916 +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
|
-
from urllib.parse import urlparse
|
9
|
-
import io
|
10
|
-
import json
|
11
|
-
import os
|
12
|
-
import pathlib
|
13
|
-
import pickle
|
14
|
-
import re
|
15
|
-
import shutil
|
16
|
-
import subprocess
|
17
|
-
import tempfile
|
18
|
-
|
19
|
-
import chardet
|
20
|
-
import qiniu
|
21
|
-
import requests
|
22
|
-
import yaml
|
23
|
-
|
24
|
-
from pyxllib.basic._1_strlib import struct_unpack
|
25
|
-
from pyxllib.basic._2_timelib import Datetime
|
26
|
-
|
27
|
-
____judge = """
|
28
|
-
"""
|
29
|
-
|
30
|
-
|
31
|
-
def is_url(arg):
|
32
|
-
"""输入是一个字符串,且值是一个合法的url"""
|
33
|
-
if not isinstance(arg, str): return False
|
34
|
-
try:
|
35
|
-
result = urlparse(arg)
|
36
|
-
return all([result.scheme, result.netloc])
|
37
|
-
except ValueError:
|
38
|
-
return False
|
39
|
-
|
40
|
-
|
41
|
-
def is_url_connect(url, timeout=5):
|
42
|
-
try:
|
43
|
-
_ = requests.head(url, timeout=timeout)
|
44
|
-
return True
|
45
|
-
except requests.ConnectionError:
|
46
|
-
pass
|
47
|
-
return False
|
48
|
-
|
49
|
-
|
50
|
-
def is_file(arg, exists=True):
|
51
|
-
"""相较于标准库的os.path.isfile,对各种其他错误类型也会判False
|
52
|
-
|
53
|
-
:param exists: arg不仅需要是一个合法的文件名,还要求其实际存在
|
54
|
-
设为False,则只判断文件名合法性,不要求其一定要存在
|
55
|
-
"""
|
56
|
-
if not isinstance(arg, str): return False
|
57
|
-
if not exists:
|
58
|
-
raise NotImplementedError
|
59
|
-
return os.path.isfile(arg)
|
60
|
-
|
61
|
-
|
62
|
-
____qiniu = """
|
63
|
-
"""
|
64
|
-
|
65
|
-
|
66
|
-
def get_etag(arg):
|
67
|
-
"""七牛原有etag功能基础上做封装
|
68
|
-
:param arg: 支持bytes二进制、文件、url地址
|
69
|
-
"""
|
70
|
-
if isinstance(arg, bytes): # 二进制数据
|
71
|
-
return qiniu.utils.etag_stream(io.BytesIO(arg))
|
72
|
-
elif is_file(arg): # 输入是一个文件
|
73
|
-
return qiniu.etag(arg)
|
74
|
-
elif is_url(arg): # 输入是一个网页上的数据源
|
75
|
-
return get_etag(requests.get(arg).content)
|
76
|
-
elif isinstance(arg, str): # 明文字符串转二进制
|
77
|
-
return get_etag(arg.encode('utf8'))
|
78
|
-
else:
|
79
|
-
raise TypeError('不识别的数据类型')
|
80
|
-
|
81
|
-
|
82
|
-
def is_etag(s):
|
83
|
-
"""字母、数字和-、_共64种字符构成的长度28的字符串"""
|
84
|
-
return re.match(r'[a-zA-Z0-9\-_]{28}$', s)
|
85
|
-
|
86
|
-
|
87
|
-
def test_etag():
|
88
|
-
print(get_etag(r'\chematom{+8}{2}{8}{}'))
|
89
|
-
# Fjnu-ZXyDxrqLoZmNJ2Kj8FcZGR-
|
90
|
-
|
91
|
-
print(get_etag(__file__))
|
92
|
-
# 每次代码改了这段输出都是不一样的
|
93
|
-
|
94
|
-
|
95
|
-
def test_etag2():
|
96
|
-
""" 字符串值和写到文件判断的etag,是一样的
|
97
|
-
"""
|
98
|
-
s = 'code4101'
|
99
|
-
print(get_etag(s))
|
100
|
-
# FkAD2McB6ugxTiniE8ebhlNHdHh9
|
101
|
-
|
102
|
-
f = Path('1.tex', root=Path.TEMP).write(s, if_exists='replace').fullpath
|
103
|
-
print(get_etag(f))
|
104
|
-
# FkAD2McB6ugxTiniE8ebhlNHdHh9
|
105
|
-
|
106
|
-
|
107
|
-
____chardet = """
|
108
|
-
"""
|
109
|
-
|
110
|
-
|
111
|
-
def get_encoding(bstr, maxn=1024):
|
112
|
-
""" 输入二进制字符串或文本文件,返回字符编码
|
113
|
-
|
114
|
-
https://www.yuque.com/xlpr/pyxllib/get_encoding
|
115
|
-
|
116
|
-
:param bstr: 二进制字符串、文本文件
|
117
|
-
:param maxn: 分析字节数上限,越小速度越快,但也会降低精准度
|
118
|
-
:return: utf8, utf-8-sig, gbk, utf16
|
119
|
-
"""
|
120
|
-
# 1 读取编码
|
121
|
-
if isinstance(bstr, bytes): # 如果输入是一个二进制字符串流则直接识别
|
122
|
-
encoding = chardet.detect(bstr[:maxn])['encoding'] # 截断一下,不然太长了,太影响速度
|
123
|
-
elif is_file(bstr): # 如果是文件,则按二进制打开
|
124
|
-
# 如果输入是一个文件名则进行读取
|
125
|
-
if bstr.endswith('.pdf'):
|
126
|
-
print('二进制文件,不应该进行编码分析,暂且默认返回utf8', bstr)
|
127
|
-
return 'utf8'
|
128
|
-
with open(bstr, 'rb') as f: # 以二进制读取文件,注意二进制没有\r\n的值
|
129
|
-
bstr = f.read()
|
130
|
-
encoding = chardet.detect(bstr[:maxn])['encoding']
|
131
|
-
else: # 其他类型不支持
|
132
|
-
return 'utf8'
|
133
|
-
# 检测结果存储在encoding
|
134
|
-
|
135
|
-
# 2 智能适应优化,最终应该只能是gbk、utf8两种结果中的一种
|
136
|
-
if encoding in ('ascii', 'utf-8', 'ISO-8859-1'):
|
137
|
-
# 对ascii类编码,理解成是utf-8编码;ISO-8859-1跟ASCII差不多
|
138
|
-
encoding = 'utf8'
|
139
|
-
elif encoding in ('GBK', 'GB2312'):
|
140
|
-
encoding = 'gbk'
|
141
|
-
elif encoding == 'UTF-16':
|
142
|
-
encoding = 'utf16'
|
143
|
-
elif encoding == 'UTF-8-SIG':
|
144
|
-
# 保留原值的一些正常识别结果
|
145
|
-
encoding = 'utf-8-sig'
|
146
|
-
elif bstr.strip(): # 如果不在预期结果内,且bstr非空,则用常见的几种编码尝试
|
147
|
-
# dprint(encoding)
|
148
|
-
type_ = ('utf8', 'gbk', 'utf16')
|
149
|
-
|
150
|
-
def try_encoding(bstr, encoding):
|
151
|
-
try:
|
152
|
-
bstr.decode(encoding)
|
153
|
-
return encoding
|
154
|
-
except UnicodeDecodeError:
|
155
|
-
return None
|
156
|
-
|
157
|
-
for t in type_:
|
158
|
-
encoding = try_encoding(bstr, t)
|
159
|
-
if encoding: break
|
160
|
-
else:
|
161
|
-
encoding = 'utf8'
|
162
|
-
|
163
|
-
return encoding
|
164
|
-
|
165
|
-
|
166
|
-
____path = """
|
167
|
-
"""
|
168
|
-
|
169
|
-
|
170
|
-
class Path:
|
171
|
-
r""" 通用文件、路径处理类,也可以处理目录,可以把目录对象理解为特殊类型的文件
|
172
|
-
|
173
|
-
document: https://www.yuque.com/xlpr/python/pyxllib.debug.path
|
174
|
-
|
175
|
-
大部分基础功能是从pathlib.Path衍生过来的
|
176
|
-
但开发中该类不能直接从Path继承,会有很多问题
|
177
|
-
|
178
|
-
TODO 这里的doctest过于针对自己的电脑了,应该改成更具适用性测试代码
|
179
|
-
"""
|
180
|
-
__slots__ = ('_path', 'assume_dir')
|
181
|
-
|
182
|
-
# 零、常用的目录类
|
183
|
-
TEMP = tempfile.gettempdir()
|
184
|
-
if os.environ.get('Desktop', None): # 如果修改了win10默认的桌面路径,需要在环境变量添加一个正确的Desktop路径值
|
185
|
-
DESKTOP = os.environ['Desktop']
|
186
|
-
else:
|
187
|
-
DESKTOP = os.path.join(str(pathlib.Path.home()), 'Desktop') # 这个不一定准,桌面是有可能被移到D盘等的
|
188
|
-
|
189
|
-
# 一、基础功能
|
190
|
-
|
191
|
-
def __init__(self, path=None, suffix=None, root=None):
|
192
|
-
r""" 初始化参数含义详见 abspath 函数解释
|
193
|
-
|
194
|
-
TODO 这个初始化也有点过于灵活了,需要降低灵活性,增加使用清晰度
|
195
|
-
或者看下性能速度,可以考虑加速
|
196
|
-
特别是用Path作为参数拷贝的情况,应该可以加速减少分析、拷贝
|
197
|
-
|
198
|
-
>>> Path('D:/pycode/code4101py')
|
199
|
-
Path('D:/pycode/code4101py')
|
200
|
-
>>> Path(Path('D:/pycode/code4101py'))
|
201
|
-
Path('D:/pycode/code4101py')
|
202
|
-
|
203
|
-
>> Path() # 不输入参数的时候,默认为当前工作目录
|
204
|
-
Path('D:/pycode/code4101py')
|
205
|
-
|
206
|
-
>> Path('a.txt', root=Path.TEMP)
|
207
|
-
Path('D:/Temp/a.txt')
|
208
|
-
>>> Path('F:/work/CreatorTemp')
|
209
|
-
Path('F:/work/CreatorTemp')
|
210
|
-
|
211
|
-
注意!如果使用了符号链接(软链接),则路径是会解析转向实际位置的!例如
|
212
|
-
>> Path('D:/pycode/code4101py')
|
213
|
-
Path('D:/slns/pycode/code4101py')
|
214
|
-
"""
|
215
|
-
strpath = str(path)
|
216
|
-
self._path = None
|
217
|
-
self.assume_dir = False # 假设是一个目录
|
218
|
-
if len(strpath) and strpath[-1] in r'\/':
|
219
|
-
self.assume_dir = True
|
220
|
-
|
221
|
-
try:
|
222
|
-
self._path = pathlib.Path(self.abspath(path, suffix, root)).resolve()
|
223
|
-
# 有些问题上一步不一定测的出来,要再补一个测试
|
224
|
-
self._path.is_file()
|
225
|
-
except (ValueError, TypeError, OSError):
|
226
|
-
# ValueError:文件名过长,代表输入很可能是一段文本,根本不是路径
|
227
|
-
# TypeError:不是str等正常的参数
|
228
|
-
# OSError:非法路径名,例如有 *? 等
|
229
|
-
self._path = None
|
230
|
-
|
231
|
-
@staticmethod
|
232
|
-
def abspath(path=None, suffix=None, root=None) -> str:
|
233
|
-
r""" 根据各种不同的组合参数信息,推导出具体的路径位置
|
234
|
-
|
235
|
-
(即把人常识易理解的指定思维,转成计算机能清晰明白的具体指向对象)
|
236
|
-
:param path: 主要的参数,如果后面的参数有矛盾,以path为最高参考标准
|
237
|
-
'',可以输入空字符串,效果跟None是不一样的,意思是显示地指明要生成一个随机名称的文件名
|
238
|
-
:param suffix:
|
239
|
-
以 '.' 指明的扩展名,会强制替换
|
240
|
-
否则,只作为参考扩展名,只在原有path没有指明的时候才添加
|
241
|
-
:param root: 未输入的时候,则为当前工作目录
|
242
|
-
|
243
|
-
>> Path.abspath()
|
244
|
-
'C:\\pycode\\code4101py'
|
245
|
-
|
246
|
-
>> Path.abspath('a')
|
247
|
-
'C:\\pycode\\code4101py\\a'
|
248
|
-
|
249
|
-
>> Path.abspath('F:/work', '.txt') # F:/work是个实际存在的目录
|
250
|
-
'F:/work\\tmpahqm6nod.txt'
|
251
|
-
|
252
|
-
>> Path.abspath('F:/work/a.txt', 'py') # 参考后缀不修改
|
253
|
-
'F:/work/a.txt'
|
254
|
-
>> Path.abspath('F:/work/a.txt', '.py') # 强制后缀会修改
|
255
|
-
'F:/work/a.py'
|
256
|
-
|
257
|
-
>> Path.abspath(suffix='.tex', root=Path.TEMP)
|
258
|
-
'F:\\work\\CreatorTemp\\tmp5vo2lpqd.tex'
|
259
|
-
|
260
|
-
# F:/work/a.txt不存在,而且看起来像文件名,但是末尾再用/则显式指明这其实是一个目录
|
261
|
-
# 又用.py指明要添加一个随机名称的py文件
|
262
|
-
>> Path.abspath('F:/work/a.txt/', '.py')
|
263
|
-
'F:/work/a.txt/tmpg_q7a7ft.py'
|
264
|
-
|
265
|
-
>> Path.abspath('work/a.txt/', '.py', Path.TEMP) # 在临时文件夹下的work/a.txt目录新建一个随机名称的py文件
|
266
|
-
'F:\\work\\CreatorTemp\\work/a.txt/tmp2jn5cqkc.py'
|
267
|
-
|
268
|
-
>> Path.abspath('C:/a.txt/') # 会保留最后的斜杠特殊标记
|
269
|
-
'C:/a.txt/'
|
270
|
-
>> Path.abspath('C:/a.txt\\') # 会保留最后的斜杠特殊标记
|
271
|
-
'C:/a.txt\\'
|
272
|
-
"""
|
273
|
-
# 1 判断参考目录
|
274
|
-
if root:
|
275
|
-
root = str(root)
|
276
|
-
else:
|
277
|
-
root = os.getcwd()
|
278
|
-
|
279
|
-
# 2 判断主体文件名 path
|
280
|
-
if str(path) == '':
|
281
|
-
path = tempfile.mktemp(dir=root)
|
282
|
-
elif not path:
|
283
|
-
path = root
|
284
|
-
else:
|
285
|
-
path = os.path.join(root, str(path))
|
286
|
-
|
287
|
-
# 3 补充suffix
|
288
|
-
if suffix:
|
289
|
-
# 如果原来的path只是一个目录,则要新建一个文件
|
290
|
-
# if os.path.isdir(path) or path[-1] in ('\\', '/'):
|
291
|
-
if path[-1] in ('\\', '/'):
|
292
|
-
path = tempfile.mktemp(dir=path)
|
293
|
-
# 判断后缀
|
294
|
-
li = os.path.splitext(path)
|
295
|
-
if (not li[1]) or suffix[0] == '.':
|
296
|
-
ext = suffix if suffix[0] == '.' else ('.' + suffix)
|
297
|
-
path = li[0] + ext
|
298
|
-
|
299
|
-
return path
|
300
|
-
|
301
|
-
def __bool__(self):
|
302
|
-
return bool(self._path)
|
303
|
-
|
304
|
-
def exists(self):
|
305
|
-
r""" 判断文件是否存在
|
306
|
-
|
307
|
-
重置WindowsPath的bool逻辑,返回值变成存在True,不存在为False
|
308
|
-
|
309
|
-
>>> Path('D:/slns').exists()
|
310
|
-
True
|
311
|
-
>>> Path('D:/pycode/code4101').exists()
|
312
|
-
False
|
313
|
-
"""
|
314
|
-
return self._path and self._path.exists()
|
315
|
-
|
316
|
-
def __repr__(self):
|
317
|
-
return 'Path' + self._path.__repr__()[11:]
|
318
|
-
|
319
|
-
def __str__(self):
|
320
|
-
return str(self._path).replace('\\', '/') + ('/' if self.assume_dir else '')
|
321
|
-
|
322
|
-
def __eq__(self, other):
|
323
|
-
""" pathlib.Path内置了windows和linux的区别
|
324
|
-
在windows是不区分大小写的,在linux则区分大小写
|
325
|
-
"""
|
326
|
-
if not isinstance(other, Path):
|
327
|
-
raise TypeError
|
328
|
-
return self._path == other._path
|
329
|
-
|
330
|
-
def __truediv__(self, key):
|
331
|
-
r""" 路径拼接功能
|
332
|
-
|
333
|
-
>>> Path('C:/a') / 'b.txt'
|
334
|
-
Path('C:/a/b.txt')
|
335
|
-
"""
|
336
|
-
return Path(self._path / str(key))
|
337
|
-
|
338
|
-
def resolve(self):
|
339
|
-
return self._path.resolve()
|
340
|
-
|
341
|
-
def glob(self, pattern):
|
342
|
-
return self._path.glob(pattern)
|
343
|
-
|
344
|
-
def match(self, path_pattern):
|
345
|
-
return self._path.match(path_pattern)
|
346
|
-
|
347
|
-
# 二、获取、修改路径中部分值的功能
|
348
|
-
|
349
|
-
@property
|
350
|
-
def fullpath(self) -> str:
|
351
|
-
return str(self._path)
|
352
|
-
|
353
|
-
@property
|
354
|
-
def drive(self) -> str:
|
355
|
-
return self._path.drive
|
356
|
-
|
357
|
-
@drive.setter
|
358
|
-
def drive(self, value):
|
359
|
-
"""修改磁盘位置"""
|
360
|
-
raise NotImplementedError
|
361
|
-
|
362
|
-
@property
|
363
|
-
def name(self) -> str:
|
364
|
-
r"""
|
365
|
-
>>> Path('D:/pycode/a.txt').name
|
366
|
-
'a.txt'
|
367
|
-
>>> Path('D:/pycode/code4101py').name
|
368
|
-
'code4101py'
|
369
|
-
>>> Path('D:/pycode/.gitignore').name
|
370
|
-
'.gitignore'
|
371
|
-
"""
|
372
|
-
return self._path.name
|
373
|
-
|
374
|
-
@name.setter
|
375
|
-
def name(self, value):
|
376
|
-
raise NotImplementedError
|
377
|
-
|
378
|
-
@property
|
379
|
-
def parent(self):
|
380
|
-
r"""
|
381
|
-
>>> Path('D:/pycode/code4101py').parent
|
382
|
-
Path('D:/pycode')
|
383
|
-
"""
|
384
|
-
return Path(self._path.parent) if self._path else None
|
385
|
-
|
386
|
-
@property
|
387
|
-
def dirname(self) -> str:
|
388
|
-
r"""
|
389
|
-
>>> Path('D:/pycode/code4101py').dirname
|
390
|
-
'D:/pycode'
|
391
|
-
>>> Path(r'D:\toweb\a').dirname
|
392
|
-
'D:/toweb'
|
393
|
-
"""
|
394
|
-
return str(self.parent)
|
395
|
-
|
396
|
-
def with_dirname(self, value):
|
397
|
-
return Path(self.name, root=value)
|
398
|
-
|
399
|
-
@property
|
400
|
-
def stem(self) -> str:
|
401
|
-
r"""
|
402
|
-
>>> Path('D:/pycode/code4101py/ckz.py').stem
|
403
|
-
'ckz'
|
404
|
-
>>> Path('D:/pycode/.gitignore').stem # os.path.splitext也是这种算法
|
405
|
-
'.gitignore'
|
406
|
-
>>> Path('D:/pycode/.123.45.6').stem
|
407
|
-
'.123.45'
|
408
|
-
"""
|
409
|
-
return self._path.stem
|
410
|
-
|
411
|
-
def with_stem(self, stem):
|
412
|
-
"""
|
413
|
-
注意不能用@stem.setter来做
|
414
|
-
如果setter要rename,那if_exists参数怎么控制?
|
415
|
-
如果setter不要rename,那和用with_stem实现有什么区别?
|
416
|
-
"""
|
417
|
-
return Path(stem, self.suffix, self.dirname)
|
418
|
-
|
419
|
-
@property
|
420
|
-
def parts(self) -> tuple:
|
421
|
-
r"""
|
422
|
-
>>> Path('D:/pycode/code4101py').parts
|
423
|
-
('D:\\', 'pycode', 'code4101py')
|
424
|
-
"""
|
425
|
-
return self._path.parts
|
426
|
-
|
427
|
-
@property
|
428
|
-
def suffix(self) -> str:
|
429
|
-
r"""
|
430
|
-
>>> Path('D:/pycode/code4101py/ckz.py').suffix
|
431
|
-
'.py'
|
432
|
-
>>> Path('D:/pycode/code4101py').suffix
|
433
|
-
''
|
434
|
-
>>> Path('D:/pycode/code4101py/ckz.').suffix
|
435
|
-
''
|
436
|
-
>>> Path('D:/pycode/code4101py/ckz.123.456').suffix
|
437
|
-
'.456'
|
438
|
-
>>> Path('D:/pycode/code4101py/ckz.123..456').suffix
|
439
|
-
'.456'
|
440
|
-
>>> Path('D:/pycode/.gitignore').suffix
|
441
|
-
''
|
442
|
-
"""
|
443
|
-
return self._path.suffix if self._path else ''
|
444
|
-
|
445
|
-
def with_suffix(self, suffix):
|
446
|
-
r""" 指向同目录下后缀为suffix的文件
|
447
|
-
|
448
|
-
>>> Path('a.txt').with_suffix('.py').fullpath.split('\\')[-1] # 强制替换
|
449
|
-
'a.py'
|
450
|
-
>>> Path('a.txt').with_suffix('py').fullpath.split('\\')[-1] # 参考替换
|
451
|
-
'a.txt'
|
452
|
-
>>> Path('a.txt').with_suffix('').fullpath.split('\\')[-1] # 删除
|
453
|
-
'a'
|
454
|
-
"""
|
455
|
-
if suffix and (suffix[0] == '.' or not self.suffix):
|
456
|
-
if suffix[0] != '.': suffix = '.' + suffix
|
457
|
-
return Path(self._path.with_suffix(suffix))
|
458
|
-
elif not suffix:
|
459
|
-
# suffix 为假值则删除扩展名
|
460
|
-
return Path(self.stem, '', self.dirname)
|
461
|
-
return self
|
462
|
-
|
463
|
-
def joinpath(self, *args):
|
464
|
-
return Path(self._path.joinpath(*args))
|
465
|
-
|
466
|
-
@property
|
467
|
-
def backup_time(self):
|
468
|
-
r""" 返回文件的备份时间戳,如果并不是备份文件,则返回空字符串
|
469
|
-
|
470
|
-
备份文件都遵循特定的命名规范
|
471
|
-
如果是文件,是:'chePre 171020-153959.tex'
|
472
|
-
如果是目录,是:'figs 171020-153959'
|
473
|
-
通过后缀分析,可以判断这是不是一个备份文件
|
474
|
-
|
475
|
-
>>> Path('chePre 171020-153959.tex').backup_time
|
476
|
-
'171020-153959'
|
477
|
-
>>> Path('figs 171020-153959').backup_time
|
478
|
-
'171020-153959'
|
479
|
-
>>> Path('figs 171020').backup_time
|
480
|
-
''
|
481
|
-
"""
|
482
|
-
name = self.stem
|
483
|
-
if len(name) < 14:
|
484
|
-
return ''
|
485
|
-
g = re.match(r'(\d{6}-\d{6})', name[-13:])
|
486
|
-
return g.group(1) if g else ''
|
487
|
-
|
488
|
-
# 三、获取文件相关属性值功能
|
489
|
-
|
490
|
-
def is_dir(self):
|
491
|
-
return self._path and self._path.is_dir()
|
492
|
-
|
493
|
-
def is_file(self):
|
494
|
-
return self._path and self._path.is_file()
|
495
|
-
|
496
|
-
@property
|
497
|
-
def encoding(self):
|
498
|
-
""" 文件的编码
|
499
|
-
|
500
|
-
非文件、不存在时返回 None
|
501
|
-
"""
|
502
|
-
if self.is_file():
|
503
|
-
return get_encoding(self.fullpath)
|
504
|
-
return None
|
505
|
-
|
506
|
-
@property
|
507
|
-
def size(self) -> int:
|
508
|
-
""" 计算文件、目录的大小,对于目录,会递归目录计算总大小
|
509
|
-
|
510
|
-
https://stackoverflow.com/questions/1392413/calculating-a-directory-size-using-python
|
511
|
-
|
512
|
-
>> Path('D:/slns/pyxllib').size # 这个算的就是真实大小,不是占用空间
|
513
|
-
2939384
|
514
|
-
"""
|
515
|
-
path = str(self._path)
|
516
|
-
if self._path.is_file():
|
517
|
-
total_size = os.path.getsize(path)
|
518
|
-
elif self._path.is_dir():
|
519
|
-
total_size = 0
|
520
|
-
for dirpath, dirnames, Pathnames in os.walk(path):
|
521
|
-
for f in Pathnames:
|
522
|
-
fp = os.path.join(dirpath, f)
|
523
|
-
total_size += os.path.getsize(fp)
|
524
|
-
else: # 不存在的对象
|
525
|
-
total_size = 0
|
526
|
-
return total_size
|
527
|
-
|
528
|
-
@property
|
529
|
-
def mtime(self) -> Datetime:
|
530
|
-
r""" 文件的最近修改时间
|
531
|
-
|
532
|
-
>> Path(r"C:\pycode\code4101py").mtime
|
533
|
-
2020-03-10 17:32:37
|
534
|
-
"""
|
535
|
-
return Datetime(os.stat(self.fullpath).st_mtime)
|
536
|
-
|
537
|
-
@property
|
538
|
-
def ctime(self) -> Datetime:
|
539
|
-
r""" 文件的创建时间
|
540
|
-
|
541
|
-
>> Path(r"C:\pycode\code4101py").ctime
|
542
|
-
2018-05-25 10:46:37
|
543
|
-
"""
|
544
|
-
# 注意:st_ctime是平台相关的值,在windows是创建时间,但在Unix是metadate最近修改时间
|
545
|
-
return Datetime(os.stat(self.fullpath).st_ctime)
|
546
|
-
|
547
|
-
# 四、文件操作功能
|
548
|
-
|
549
|
-
def abs_dstpath(self, dst=None, suffix=None, root=None):
|
550
|
-
r""" 参照当前Path的父目录,来确定dst的具体路径
|
551
|
-
|
552
|
-
>>> f = Path('C:/Windows/System32/cmd.exe')
|
553
|
-
>>> f.abs_dstpath('chen.py')
|
554
|
-
Path('C:/Windows/System32/chen.py')
|
555
|
-
>>> f.abs_dstpath('E:/') # 原始文件必须存在,否则因为无法判断实际类型,目标路径可能会错
|
556
|
-
Path('E:/cmd.exe')
|
557
|
-
>>> f.abs_dstpath('D:/aabbccdd.txt')
|
558
|
-
Path('D:/aabbccdd.txt')
|
559
|
-
>>> f.abs_dstpath('D:/aabbccdd.txt/') # 并不存在aabbccdd.txt这样的对象,但末尾有个/表明这是个目录
|
560
|
-
Path('D:/aabbccdd.txt/cmd.exe')
|
561
|
-
"""
|
562
|
-
if not root: root = self.dirname
|
563
|
-
dst = Path(dst, suffix, root)
|
564
|
-
# print(dst, dst.assume_dir)
|
565
|
-
|
566
|
-
if self.is_dir() or (self.assume_dir and not self.is_file()):
|
567
|
-
if dst.is_file():
|
568
|
-
raise ValueError(f'{dst}是已存在的文件类型,不能对{self}目录执行指定操作')
|
569
|
-
elif dst.assume_dir: # 明确要把目录放到另一个目录下
|
570
|
-
dst = dst / self.name
|
571
|
-
else: # 否则self是文件,或者不存在,均视为文件处理
|
572
|
-
if dst.is_dir() or dst.assume_dir:
|
573
|
-
dst = dst / self.name
|
574
|
-
# 否则dst是文件,或者不存在的路径,均视为文件类型处理
|
575
|
-
|
576
|
-
return dst
|
577
|
-
|
578
|
-
def relative_path(self, ref_dir) -> str:
|
579
|
-
r""" 当前路径,相对于ref_dir的路径位置
|
580
|
-
|
581
|
-
>>> Path('C:/a/b/c.txt').relative_path('C:/a/')
|
582
|
-
'b/c.txt'
|
583
|
-
>>> Path('C:/a/b\\c.txt').relative_path('C:\\a/')
|
584
|
-
'b/c.txt'
|
585
|
-
"""
|
586
|
-
if not isinstance(ref_dir, Path):
|
587
|
-
ref_dir = Path(ref_dir)
|
588
|
-
s1, s2 = str(self), str(ref_dir)
|
589
|
-
if s1.startswith(s2):
|
590
|
-
s1 = s1[len(s2):]
|
591
|
-
return s1
|
592
|
-
|
593
|
-
def preprocess(self, if_exists='error', exclude=None):
|
594
|
-
""" 这个功能要结合参数一起理解,不是简单的理解成“预处理”
|
595
|
-
这个实际上是在做copy等操作前,如果目标文件已存在,需要预先删除等的预处理
|
596
|
-
并返回判断,是否需要执行下一步操作
|
597
|
-
|
598
|
-
有时候情况比较复杂,process无法满足需求时,可以用preprocess这个底层函数协助
|
599
|
-
|
600
|
-
:param if_exists:
|
601
|
-
'error': (默认)如果要替换的目标文件已经存在,则报错
|
602
|
-
'overwrite', 'replace': 替换 (提前把已存在的目标文件删除)
|
603
|
-
'skip', 'ignore': 忽略、不处理 (不用执行后续的功能)
|
604
|
-
'backup': 备份后写入 (对原文件先做一个备份)
|
605
|
-
:param exclude: 排除掉不分析的目录,用于有些重命名等自身可能操作自身的情况
|
606
|
-
如果self是exclude这个路径,默认直接need_run=True
|
607
|
-
"""
|
608
|
-
# 1 如果src和dst是同一个文件,因为重命名等特殊功能,可以直接执行,不用管提前存在目标文件的问题
|
609
|
-
if exclude and self == Path(exclude):
|
610
|
-
return True
|
611
|
-
|
612
|
-
# 2
|
613
|
-
need_run = True
|
614
|
-
if self.exists():
|
615
|
-
if if_exists == 'error':
|
616
|
-
raise FileExistsError(f'目标文件已存在: {self}')
|
617
|
-
elif if_exists in ('replace', 'overwrite'): # None的话相当于replace,但是不会事先delete,可能会报错
|
618
|
-
self.delete()
|
619
|
-
elif if_exists in ('ignore', 'skip'):
|
620
|
-
need_run = False
|
621
|
-
elif if_exists == 'backup':
|
622
|
-
self.backup(if_exists='backup')
|
623
|
-
self.delete()
|
624
|
-
return need_run
|
625
|
-
|
626
|
-
def process(self, dst, func, if_exists='error'):
|
627
|
-
r""" copy或move的本质底层实现
|
628
|
-
|
629
|
-
文件复制等操作中src、dst不同组合下的效果:https://www.yuque.com/xlpr/pyxllib/mgwe19
|
630
|
-
|
631
|
-
:param dst: 目标路径对象,注意如果使用相对路径,是相对于self的路径!
|
632
|
-
:param func: 传入arg1和arg2参数,可以自定义
|
633
|
-
默认分别是self和dst的fullpath
|
634
|
-
:return : 返回dst
|
635
|
-
"""
|
636
|
-
# 1 判断目标是有已存在,进行不同的指定规则处理
|
637
|
-
dst0 = self.abs_dstpath(dst)
|
638
|
-
|
639
|
-
# 2 执行特定功能
|
640
|
-
if dst0.preprocess(if_exists, self):
|
641
|
-
# 此时dst已是具体路径,哪怕是"目录"也可以按照"文件"对象理解,避免目录会重复生成,多层嵌套
|
642
|
-
# 本来是 a --> C:/target/a ,避免 C:/target/a/a 的bug
|
643
|
-
dst0.ensure_dir('file')
|
644
|
-
|
645
|
-
if dst0 == self: # 重命名自身的操作比较特别,需要打补丁
|
646
|
-
func(self.fullpath, os.path.join(dst0.dirname, pathlib.Path(str(dst)).name))
|
647
|
-
dst0 = self.abs_dstpath(dst)
|
648
|
-
else:
|
649
|
-
func(self.fullpath, dst0)
|
650
|
-
|
651
|
-
return dst0
|
652
|
-
|
653
|
-
def ensure_dir(self, pathtype=None):
|
654
|
-
r""" 确保path中指定的dir都存在
|
655
|
-
|
656
|
-
:param pathtype: 如果self.path的对象不存在,根据self.path的类型不同,有不同的处理方案
|
657
|
-
'dir':则包括自身也会创建一个空目录
|
658
|
-
'file': 只会创建到dirname所在目录,并不会创建自身的Path
|
659
|
-
'None':通过名称智能判断,如果能读取到suffix,则代表是Path类型,否则是dir类型
|
660
|
-
:return:
|
661
|
-
|
662
|
-
>> Path(r'D:\toweb\a\b.txt').ensure_dir() # 只会创建toweb、a目录
|
663
|
-
|
664
|
-
# a和一个叫b.txt的目录都会创建
|
665
|
-
# 当然,如果b.txt是一个已经存在的文件对象,则该函数不会进行操作
|
666
|
-
>> Path(r'D:\toweb\a\b.txt').ensure_dir(pathtype='dir')
|
667
|
-
"""
|
668
|
-
if not self.exists():
|
669
|
-
if not pathtype:
|
670
|
-
pathtype = 'file' if (not self.assume_dir and self.suffix) else 'dir'
|
671
|
-
dirname = self.fullpath if pathtype == 'dir' else self.dirname
|
672
|
-
if not os.path.exists(dirname):
|
673
|
-
os.makedirs(dirname)
|
674
|
-
|
675
|
-
def copy(self, dst, if_exists='error'):
|
676
|
-
""" 复制文件
|
677
|
-
|
678
|
-
"""
|
679
|
-
if self.is_dir():
|
680
|
-
return self.process(dst, shutil.copytree, if_exists)
|
681
|
-
elif self.is_file():
|
682
|
-
return self.process(dst, shutil.copy2, if_exists)
|
683
|
-
|
684
|
-
def move(self, dst, if_exists='error'):
|
685
|
-
""" 移动文件
|
686
|
-
|
687
|
-
"""
|
688
|
-
if self.exists():
|
689
|
-
return self.process(dst, shutil.move, if_exists)
|
690
|
-
|
691
|
-
def rename(self, dst, if_exists='error'):
|
692
|
-
r""" 文件重命名,或者也可以理解成文件移动
|
693
|
-
|
694
|
-
:param dst: 如果使用相对目录,则是该类本身所在的目录作为工作环境
|
695
|
-
:param if_exists:
|
696
|
-
'error': 如果要替换的目标文件已经存在,则报错
|
697
|
-
'replace': 替换
|
698
|
-
'ignore': 忽略、不处理
|
699
|
-
'backup': 备份后替换
|
700
|
-
:return:
|
701
|
-
"""
|
702
|
-
# rename是move的一种特殊情况
|
703
|
-
return self.move(dst, if_exists)
|
704
|
-
|
705
|
-
def delete(self):
|
706
|
-
r""" 删除自身文件
|
707
|
-
|
708
|
-
"""
|
709
|
-
if self.is_file():
|
710
|
-
os.remove(self.fullpath)
|
711
|
-
elif self.is_dir():
|
712
|
-
shutil.rmtree(self.fullpath)
|
713
|
-
# TODO 确保删除后再执行后续代码 但是一直觉得这样写很别扭
|
714
|
-
while self.exists(): pass
|
715
|
-
|
716
|
-
def backup(self, tail=None, if_exists='replace', move=False):
|
717
|
-
r""" 对文件末尾添加时间戳备份,也可以使用自定义标记tail
|
718
|
-
|
719
|
-
:param tail: 自定义添加后缀
|
720
|
-
tail为None时,默认添加特定格式的时间戳
|
721
|
-
:param if_exists: 备份的目标文件名存在时的处理方案
|
722
|
-
:param move:
|
723
|
-
是否删除原始文件
|
724
|
-
|
725
|
-
# TODO:有个小bug,如果在不同时间实际都是相同一个文件,也会被不断反复备份
|
726
|
-
# 如果想解决这个,就要读取目录下最近的备份文件对比内容了
|
727
|
-
"""
|
728
|
-
# 1 判断自身文件是否存在
|
729
|
-
if not self.exists():
|
730
|
-
return None
|
731
|
-
|
732
|
-
# 2 计算出新名称
|
733
|
-
if not tail:
|
734
|
-
tail = self.mtime.strftime(' %y%m%d-%H%M%S') # 时间戳
|
735
|
-
name, ext = os.path.splitext(self.fullpath)
|
736
|
-
dst = name + tail + ext
|
737
|
-
|
738
|
-
# 3 备份就是特殊的copy操作
|
739
|
-
if move:
|
740
|
-
return self.move(dst, if_exists)
|
741
|
-
else:
|
742
|
-
return self.copy(dst, if_exists)
|
743
|
-
|
744
|
-
# 五、其他综合性功能
|
745
|
-
|
746
|
-
def read(self, *, encoding=None, mode=None):
|
747
|
-
""" 读取文件
|
748
|
-
|
749
|
-
:param encoding: 文件编码
|
750
|
-
默认None,则在需要使用encoding参数的场合,会使用self.encoding自动判断编码
|
751
|
-
:param mode: 读取模式(例如 '.json'),默认从扩展名识别,也可以强制指定
|
752
|
-
'b': 特殊标记,表示按二进制读取文件内容
|
753
|
-
:return:
|
754
|
-
"""
|
755
|
-
if self.is_file(): # 如果存在这样的文件,那就读取文件内容
|
756
|
-
# 获得文件扩展名,并统一转成小写
|
757
|
-
name, suffix = self.fullpath, self.suffix
|
758
|
-
if not mode: mode = suffix
|
759
|
-
mode = mode.lower()
|
760
|
-
if mode == 'bytes':
|
761
|
-
with open(name, 'rb') as f:
|
762
|
-
return f.read()
|
763
|
-
elif mode == '.pkl': # pickle库
|
764
|
-
with open(name, 'rb') as f:
|
765
|
-
return pickle.load(f)
|
766
|
-
elif mode == '.json':
|
767
|
-
# 先读成字符串,再解析,会比rb鲁棒性更强,能自动过滤掉开头可能非正文特殊标记的字节
|
768
|
-
if not encoding: encoding = self.encoding
|
769
|
-
with open(name, 'r', encoding=encoding) as f:
|
770
|
-
return json.loads(f.read())
|
771
|
-
elif mode == '.yaml':
|
772
|
-
with open(name, 'r', encoding=encoding) as f:
|
773
|
-
return yaml.safe_load(f.read())
|
774
|
-
elif mode in ('.jpg', '.jpeg', '.png', '.bmp', 'b'):
|
775
|
-
# 二进制读取
|
776
|
-
with open(name, 'rb') as fp:
|
777
|
-
return fp.read()
|
778
|
-
else:
|
779
|
-
with open(name, 'rb') as f:
|
780
|
-
bstr = f.read()
|
781
|
-
if not encoding:
|
782
|
-
encoding = self.encoding
|
783
|
-
if not encoding:
|
784
|
-
raise ValueError(f'{self} 自动识别编码失败,请手动指定文件编码')
|
785
|
-
s = bstr.decode(encoding=encoding, errors='ignore')
|
786
|
-
if '\r' in s: s = s.replace('\r\n', '\n') # 如果用\r\n作为换行符会有一些意外不好处理
|
787
|
-
return s
|
788
|
-
else: # 非文件对象
|
789
|
-
raise FileNotFoundError(f'{self} 文件不存在,无法读取。')
|
790
|
-
|
791
|
-
def write(self, ob, *, encoding='utf8', if_exists='error', etag=False, mode=None):
|
792
|
-
""" 保存为文件
|
793
|
-
|
794
|
-
:param ob: 写入的内容
|
795
|
-
如果要写txt文本文件且ob不是文本对象,只会进行简单的字符串化
|
796
|
-
:param encoding: 强制写入的编码
|
797
|
-
:param if_exists: 如果文件已存在,要进行的操作
|
798
|
-
:param etag: 创建的文件,是否需要再进一步重命名为etag名称
|
799
|
-
:param mode: 写入模式(例如 '.json'),默认从扩展名识别,也可以强制指定
|
800
|
-
:return: 返回写入的文件名,这个主要是在写临时文件时有用
|
801
|
-
"""
|
802
|
-
|
803
|
-
# 1 核心功能:将ob写入文件path
|
804
|
-
if self.preprocess(if_exists):
|
805
|
-
self.ensure_dir(pathtype='file')
|
806
|
-
name, suffix = self.fullpath, self.suffix
|
807
|
-
if not mode: mode = suffix
|
808
|
-
mode = mode.lower()
|
809
|
-
if mode == '.pkl':
|
810
|
-
with open(name, 'wb') as f:
|
811
|
-
pickle.dump(ob, f)
|
812
|
-
elif mode == '.json':
|
813
|
-
with open(name, 'w', encoding=encoding) as f:
|
814
|
-
json.dump(ob, f, ensure_ascii=False, indent=2)
|
815
|
-
elif mode == '.yaml':
|
816
|
-
with open(name, 'w', encoding=encoding) as f:
|
817
|
-
yaml.dump(ob, f)
|
818
|
-
elif isinstance(ob, bytes):
|
819
|
-
with open(name, 'wb') as f:
|
820
|
-
f.write(ob)
|
821
|
-
else: # 其他类型认为是文本类型
|
822
|
-
with open(name, 'w', errors='ignore', encoding=encoding) as f:
|
823
|
-
f.write(str(ob))
|
824
|
-
|
825
|
-
# 2 如果使用了etag命名机制
|
826
|
-
if etag:
|
827
|
-
return self.rename(get_etag(self.fullpath) + self.suffix, if_exists='ignore')
|
828
|
-
else:
|
829
|
-
return self
|
830
|
-
|
831
|
-
def explorer(self, proc='explorer'):
|
832
|
-
""" 使用windows的explorer命令打开文件
|
833
|
-
|
834
|
-
还有个类似的万能打开命令 start
|
835
|
-
|
836
|
-
:param proc: 可以自定义要执行的主程序
|
837
|
-
"""
|
838
|
-
subprocess.run([proc, self.fullpath])
|
839
|
-
|
840
|
-
|
841
|
-
def demo_path():
|
842
|
-
"""Path类的综合测试"""
|
843
|
-
# 切换工作目录到临时文件夹
|
844
|
-
os.chdir(Path.TEMP)
|
845
|
-
|
846
|
-
p = Path('demo_path', root=Path.TEMP)
|
847
|
-
p.delete() # 如果存在先删除
|
848
|
-
p.ensure_dir() # 然后再创建一个空目录
|
849
|
-
|
850
|
-
print(Path('demo_path', '.py'))
|
851
|
-
# F:\work\CreatorTemp\demo_path.py
|
852
|
-
|
853
|
-
print(Path('demo_path/', '.py'))
|
854
|
-
# F:\work\CreatorTemp\demo_path\tmp65m8mc0b.py
|
855
|
-
|
856
|
-
# 空字符串区别于None,会随机生成一个文件名
|
857
|
-
print(Path('', root=Path.TEMP))
|
858
|
-
# F:\work\CreatorTemp\tmpwp4g1692
|
859
|
-
|
860
|
-
# 可以在随机名称基础上,再指定文件扩展名
|
861
|
-
print(Path('', '.txt', root=Path.TEMP))
|
862
|
-
# F:\work\CreatorTemp\tmpimusjtu1.txt
|
863
|
-
|
864
|
-
|
865
|
-
def demo_path_rename():
|
866
|
-
# 初始化一个空的测试目录
|
867
|
-
f = Path('temp/', root=Path.TEMP)
|
868
|
-
f.delete()
|
869
|
-
f.ensure_dir()
|
870
|
-
|
871
|
-
# 建一个空文件
|
872
|
-
f1 = f / 'a.txt'
|
873
|
-
# Path('F:/work/CreatorTemp/temp/a.txt')
|
874
|
-
with open(f1.fullpath, 'wb') as p: pass # 写一个空文件
|
875
|
-
|
876
|
-
f1.rename('A.tXt') # 重命名
|
877
|
-
# Path('F:/work/CreatorTemp/temp/a.txt')
|
878
|
-
|
879
|
-
f1.rename('figs/b') # 放到一个新的子目录里,并再次重命名
|
880
|
-
# Path('F:/work/CreatorTemp/temp/figs/b')
|
881
|
-
|
882
|
-
# TODO 把目录下的1 2 3 4 5重命名为5 4 3 2 1时要怎么搞?
|
883
|
-
|
884
|
-
|
885
|
-
class XlBytesIO(io.BytesIO):
|
886
|
-
""" 自定义的字节流类,封装了struct_unpack操作
|
887
|
-
|
888
|
-
https://www.yuque.com/xlpr/pyxllib/xlbytesio
|
889
|
-
|
890
|
-
"""
|
891
|
-
|
892
|
-
def __init__(self, init_bytes):
|
893
|
-
if isinstance(init_bytes, (Path, str)):
|
894
|
-
# with open的作用:可以用.read循序读入,而不是我的Path.read一口气读入。
|
895
|
-
# 这在只需要进行局部数据分析,f本身又非常大的时候很有用。
|
896
|
-
# 但是我这里操作不太方便等原因,还是先全部读入BytesIO流了
|
897
|
-
init_bytes = Path(init_bytes).read(mode='b')
|
898
|
-
super().__init__(init_bytes)
|
899
|
-
|
900
|
-
def unpack(self, fmt):
|
901
|
-
return struct_unpack(self, fmt)
|
902
|
-
|
903
|
-
def readtext(self, char_num, encoding='gbk', errors='ignore', code_length=2):
|
904
|
-
""" 读取二进制流,将其解析未文本内容
|
905
|
-
|
906
|
-
:param char_num: 字符数
|
907
|
-
:param encoding: 所用编码,一般是gbk,因为如果是utf8是字符是变长的,不好操作
|
908
|
-
:param errors: decode出现错误时的处理方式
|
909
|
-
:param code_length: 每个字符占的长度
|
910
|
-
:return: 文本内容
|
911
|
-
"""
|
912
|
-
return self.read(code_length * char_num).decode(encoding, errors)
|
913
|
-
|
914
|
-
|
915
|
-
if __name__ == '__main__':
|
916
|
-
demo_path_rename()
|