tretool 0.2.1__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.
- tretool/__init__.py +27 -0
- tretool/config.py +262 -0
- tretool/encoding.py +92 -0
- tretool/jsonlib.py +299 -0
- tretool/markfunc.py +152 -0
- tretool/mathlib.py +620 -0
- tretool/memorizeTools.py +24 -0
- tretool/path.py +1139 -0
- tretool/platformlib.py +332 -0
- tretool/plugin/plu.py +0 -0
- tretool/plugin.py +348 -0
- tretool/timelib.py +518 -0
- tretool/transform/__init__.py +1 -0
- tretool/transform/pdf.py +396 -0
- tretool/writeLog.py +69 -0
- tretool-0.2.1.dist-info/METADATA +28 -0
- tretool-0.2.1.dist-info/RECORD +20 -0
- tretool-0.2.1.dist-info/WHEEL +5 -0
- tretool-0.2.1.dist-info/licenses/LICENSE +19 -0
- tretool-0.2.1.dist-info/top_level.txt +1 -0
tretool/path.py
ADDED
@@ -0,0 +1,1139 @@
|
|
1
|
+
import os
|
2
|
+
import sys
|
3
|
+
import stat
|
4
|
+
import shutil
|
5
|
+
import fnmatch
|
6
|
+
import tempfile
|
7
|
+
import hashlib
|
8
|
+
import filecmp
|
9
|
+
from abc import ABC, abstractmethod
|
10
|
+
from datetime import datetime
|
11
|
+
from typing import (
|
12
|
+
Union, List, Optional, Iterator, Tuple, Dict, Any, overload, IO
|
13
|
+
)
|
14
|
+
|
15
|
+
PathLike = Union[str, bytes, os.PathLike]
|
16
|
+
|
17
|
+
class PurePath(ABC):
|
18
|
+
"""
|
19
|
+
纯路径基类,提供与操作系统无关的路径操作
|
20
|
+
|
21
|
+
这是一个抽象基类,提供不依赖实际文件系统的路径操作。
|
22
|
+
子类应实现特定平台的行为。
|
23
|
+
|
24
|
+
参数:
|
25
|
+
*pathsegments: 路径组成部分,可以是字符串、字节或os.PathLike对象
|
26
|
+
|
27
|
+
示例:
|
28
|
+
>>> path = PurePath('foo', 'bar', 'baz.txt')
|
29
|
+
>>> str(path)
|
30
|
+
'foo/bar/baz.txt'
|
31
|
+
"""
|
32
|
+
|
33
|
+
def __init__(self, *pathsegments: PathLike):
|
34
|
+
"""初始化一个新的PurePath实例"""
|
35
|
+
self._parts = self._parse_args(pathsegments)
|
36
|
+
self._drv = self._parse_drive(self._parts[0]) if self._parts else ''
|
37
|
+
self._root = self._parse_root(self._parts[0]) if self._parts else ''
|
38
|
+
|
39
|
+
def _parse_args(self, args) -> List[str]:
|
40
|
+
"""
|
41
|
+
将路径参数解析为路径组件列表
|
42
|
+
|
43
|
+
参数:
|
44
|
+
args: 路径段序列
|
45
|
+
|
46
|
+
返回:
|
47
|
+
清理后的路径组件列表
|
48
|
+
|
49
|
+
异常:
|
50
|
+
TypeError: 如果参数不是字符串、字节或os.PathLike对象
|
51
|
+
"""
|
52
|
+
parts = []
|
53
|
+
for arg in args:
|
54
|
+
if isinstance(arg, (str, bytes)):
|
55
|
+
if isinstance(arg, bytes):
|
56
|
+
arg = arg.decode('utf-8', 'surrogateescape')
|
57
|
+
parts.extend(arg.split(os.sep))
|
58
|
+
elif hasattr(arg, '__fspath__'):
|
59
|
+
parts.extend(str(arg).split(os.sep))
|
60
|
+
else:
|
61
|
+
raise TypeError(f"参数应为路径或字符串,不是 {type(arg)}")
|
62
|
+
return [p for p in parts if p] # 过滤空部分
|
63
|
+
|
64
|
+
def _parse_drive(self, part: str) -> str:
|
65
|
+
"""
|
66
|
+
从路径组件中提取驱动器号(Windows专用)
|
67
|
+
|
68
|
+
参数:
|
69
|
+
part: 第一个路径组件
|
70
|
+
|
71
|
+
返回:
|
72
|
+
驱动器号(如'C:')或空字符串
|
73
|
+
"""
|
74
|
+
if len(part) > 1 and part[1] == ':':
|
75
|
+
return part[:2]
|
76
|
+
return ''
|
77
|
+
|
78
|
+
def _parse_root(self, part: str) -> str:
|
79
|
+
"""
|
80
|
+
从路径组件中提取根目录
|
81
|
+
|
82
|
+
参数:
|
83
|
+
part: 第一个路径组件
|
84
|
+
|
85
|
+
返回:
|
86
|
+
根目录(如'/')或空字符串
|
87
|
+
"""
|
88
|
+
if part.startswith(os.sep):
|
89
|
+
return os.sep
|
90
|
+
return ''
|
91
|
+
|
92
|
+
@property
|
93
|
+
def parts(self) -> Tuple[str, ...]:
|
94
|
+
"""
|
95
|
+
以元组形式访问路径的各个组件
|
96
|
+
|
97
|
+
返回:
|
98
|
+
路径组件的元组
|
99
|
+
"""
|
100
|
+
return tuple(self._parts)
|
101
|
+
|
102
|
+
@property
|
103
|
+
def drive(self) -> str:
|
104
|
+
"""
|
105
|
+
驱动器字母或名称(仅Windows)
|
106
|
+
|
107
|
+
返回:
|
108
|
+
驱动器号(如'C:')或空字符串
|
109
|
+
"""
|
110
|
+
return self._drv
|
111
|
+
|
112
|
+
@property
|
113
|
+
def root(self) -> str:
|
114
|
+
"""
|
115
|
+
路径的根目录(如果有)
|
116
|
+
|
117
|
+
返回:
|
118
|
+
根目录(如'/')或空字符串
|
119
|
+
"""
|
120
|
+
return self._root
|
121
|
+
|
122
|
+
@property
|
123
|
+
def name(self) -> str:
|
124
|
+
"""
|
125
|
+
路径的最后一个组件(如果有)
|
126
|
+
|
127
|
+
返回:
|
128
|
+
最后一个路径组件或空字符串
|
129
|
+
"""
|
130
|
+
if not self._parts:
|
131
|
+
return ''
|
132
|
+
return self._parts[-1]
|
133
|
+
|
134
|
+
@property
|
135
|
+
def suffix(self) -> str:
|
136
|
+
"""
|
137
|
+
最后一个组件的文件扩展名
|
138
|
+
|
139
|
+
返回:
|
140
|
+
包含点的文件扩展名(如'.txt')或空字符串
|
141
|
+
"""
|
142
|
+
name = self.name
|
143
|
+
i = name.rfind('.')
|
144
|
+
if 0 < i < len(name) - 1:
|
145
|
+
return name[i:]
|
146
|
+
return ''
|
147
|
+
|
148
|
+
@property
|
149
|
+
def suffixes(self) -> List[str]:
|
150
|
+
"""
|
151
|
+
路径的所有文件扩展名列表
|
152
|
+
|
153
|
+
返回:
|
154
|
+
包含点的扩展名列表(如['.tar', '.gz'])
|
155
|
+
"""
|
156
|
+
name = self.name
|
157
|
+
if name.endswith('.'):
|
158
|
+
return []
|
159
|
+
return ['.' + ext for ext in name.split('.')[1:]]
|
160
|
+
|
161
|
+
@property
|
162
|
+
def stem(self) -> str:
|
163
|
+
"""
|
164
|
+
最后一个组件不带后缀
|
165
|
+
|
166
|
+
返回:
|
167
|
+
不带扩展名的名称
|
168
|
+
"""
|
169
|
+
name = self.name
|
170
|
+
i = name.rfind('.')
|
171
|
+
if 0 < i < len(name) - 1:
|
172
|
+
return name[:i]
|
173
|
+
return name
|
174
|
+
|
175
|
+
def joinpath(self, *args) -> 'PurePath':
|
176
|
+
"""
|
177
|
+
将此路径与一个或多个参数组合
|
178
|
+
|
179
|
+
参数:
|
180
|
+
*args: 要连接的路径段
|
181
|
+
|
182
|
+
返回:
|
183
|
+
新路径,组合了当前路径和参数
|
184
|
+
"""
|
185
|
+
return self.__class__(self, *args)
|
186
|
+
|
187
|
+
def __truediv__(self, other) -> 'PurePath':
|
188
|
+
"""
|
189
|
+
使用/运算符连接路径
|
190
|
+
|
191
|
+
参数:
|
192
|
+
other: 要追加的路径段
|
193
|
+
|
194
|
+
返回:
|
195
|
+
新组合的路径
|
196
|
+
"""
|
197
|
+
return self.joinpath(other)
|
198
|
+
|
199
|
+
def __str__(self) -> str:
|
200
|
+
"""返回路径的字符串表示"""
|
201
|
+
if self._drv and self._root:
|
202
|
+
return self._drv + self._root + os.sep.join(self._parts[1:])
|
203
|
+
elif self._root:
|
204
|
+
return self._root + os.sep.join(self._parts)
|
205
|
+
elif self._drv:
|
206
|
+
return self._drv + os.sep.join(self._parts)
|
207
|
+
return os.sep.join(self._parts)
|
208
|
+
|
209
|
+
def __repr__(self) -> str:
|
210
|
+
"""返回路径的正式字符串表示"""
|
211
|
+
return f"{self.__class__.__name__}('{self}')"
|
212
|
+
|
213
|
+
def __eq__(self, other) -> bool:
|
214
|
+
"""比较路径是否相等"""
|
215
|
+
if not isinstance(other, PurePath):
|
216
|
+
return NotImplemented
|
217
|
+
return str(self) == str(other)
|
218
|
+
|
219
|
+
def __hash__(self) -> int:
|
220
|
+
"""计算路径的哈希值"""
|
221
|
+
return hash(str(self))
|
222
|
+
|
223
|
+
|
224
|
+
class PurePosixPath(PurePath):
|
225
|
+
"""
|
226
|
+
非Windows系统的PurePath子类
|
227
|
+
|
228
|
+
此版本不支持驱动器号并使用正斜杠
|
229
|
+
"""
|
230
|
+
|
231
|
+
|
232
|
+
class PureWindowsPath(PurePath):
|
233
|
+
"""
|
234
|
+
Windows系统的PurePath子类
|
235
|
+
|
236
|
+
此版本支持驱动器号并使用反斜杠
|
237
|
+
"""
|
238
|
+
|
239
|
+
|
240
|
+
class Path(PurePath):
|
241
|
+
"""
|
242
|
+
具体路径类,提供文件系统操作
|
243
|
+
|
244
|
+
此类继承自PurePath并添加实际文件系统操作
|
245
|
+
"""
|
246
|
+
|
247
|
+
def stat(self) -> os.stat_result:
|
248
|
+
"""
|
249
|
+
返回此路径的stat信息
|
250
|
+
|
251
|
+
返回:
|
252
|
+
os.stat_result对象
|
253
|
+
|
254
|
+
异常:
|
255
|
+
OSError: 如果路径不存在
|
256
|
+
"""
|
257
|
+
return os.stat(str(self))
|
258
|
+
|
259
|
+
def exists(self) -> bool:
|
260
|
+
"""
|
261
|
+
检查路径是否存在
|
262
|
+
|
263
|
+
返回:
|
264
|
+
如果路径存在返回True,否则返回False
|
265
|
+
"""
|
266
|
+
return os.path.exists(str(self))
|
267
|
+
|
268
|
+
def is_file(self) -> bool:
|
269
|
+
"""
|
270
|
+
检查路径是否是普通文件
|
271
|
+
|
272
|
+
返回:
|
273
|
+
如果路径是文件返回True,否则返回False
|
274
|
+
"""
|
275
|
+
return os.path.isfile(str(self))
|
276
|
+
|
277
|
+
def is_dir(self) -> bool:
|
278
|
+
"""
|
279
|
+
检查路径是否是目录
|
280
|
+
|
281
|
+
返回:
|
282
|
+
如果路径是目录返回True,否则返回False
|
283
|
+
"""
|
284
|
+
return os.path.isdir(str(self))
|
285
|
+
|
286
|
+
def mkdir(self, mode=0o777, parents=False, exist_ok=False):
|
287
|
+
"""
|
288
|
+
创建新目录
|
289
|
+
|
290
|
+
参数:
|
291
|
+
mode: 权限(八进制数)
|
292
|
+
parents: 如果需要是否创建父目录
|
293
|
+
exist_ok: 如果目录已存在是否不报错
|
294
|
+
|
295
|
+
异常:
|
296
|
+
FileExistsError: 如果目录已存在且exist_ok为False
|
297
|
+
OSError: 如果目录无法创建
|
298
|
+
"""
|
299
|
+
path = str(self)
|
300
|
+
if parents:
|
301
|
+
os.makedirs(path, mode, exist_ok=exist_ok)
|
302
|
+
else:
|
303
|
+
try:
|
304
|
+
os.mkdir(path, mode)
|
305
|
+
except FileExistsError:
|
306
|
+
if not exist_ok:
|
307
|
+
raise
|
308
|
+
|
309
|
+
def open(self, mode='r', buffering=-1, encoding=None,
|
310
|
+
errors=None, newline=None) -> IO:
|
311
|
+
"""
|
312
|
+
打开此路径指向的文件
|
313
|
+
|
314
|
+
参数:
|
315
|
+
mode: 打开模式('r', 'w'等)
|
316
|
+
buffering: 缓冲策略
|
317
|
+
encoding: 文本编码
|
318
|
+
errors: 编码错误处理策略
|
319
|
+
newline: 换行控制
|
320
|
+
|
321
|
+
返回:
|
322
|
+
文件对象
|
323
|
+
|
324
|
+
异常:
|
325
|
+
OSError: 如果文件无法打开
|
326
|
+
"""
|
327
|
+
return open(str(self), mode, buffering, encoding, errors, newline)
|
328
|
+
|
329
|
+
def read_text(self, encoding=None, errors=None) -> str:
|
330
|
+
"""
|
331
|
+
以文本形式读取文件内容
|
332
|
+
|
333
|
+
参数:
|
334
|
+
encoding: 文本编码
|
335
|
+
errors: 编码错误处理策略
|
336
|
+
|
337
|
+
返回:
|
338
|
+
文件内容字符串
|
339
|
+
|
340
|
+
异常:
|
341
|
+
OSError: 如果文件无法读取
|
342
|
+
"""
|
343
|
+
with open(str(self), 'r', encoding=encoding, errors=errors) as f:
|
344
|
+
return f.read()
|
345
|
+
|
346
|
+
def write_text(self, data: str, encoding=None, errors=None):
|
347
|
+
"""
|
348
|
+
以文本形式写入文件内容
|
349
|
+
|
350
|
+
参数:
|
351
|
+
data: 要写入的字符串
|
352
|
+
encoding: 文本编码
|
353
|
+
errors: 编码错误处理策略
|
354
|
+
|
355
|
+
返回:
|
356
|
+
写入的字符数
|
357
|
+
|
358
|
+
异常:
|
359
|
+
OSError: 如果文件无法写入
|
360
|
+
"""
|
361
|
+
with open(str(self), 'w', encoding=encoding, errors=errors) as f:
|
362
|
+
return f.write(data)
|
363
|
+
|
364
|
+
def read_bytes(self) -> bytes:
|
365
|
+
"""
|
366
|
+
以二进制形式读取文件内容
|
367
|
+
|
368
|
+
返回:
|
369
|
+
文件内容的字节串
|
370
|
+
|
371
|
+
异常:
|
372
|
+
OSError: 如果文件无法读取
|
373
|
+
"""
|
374
|
+
with open(str(self), 'rb') as f:
|
375
|
+
return f.read()
|
376
|
+
|
377
|
+
def write_bytes(self, data: bytes):
|
378
|
+
"""
|
379
|
+
以二进制形式写入文件内容
|
380
|
+
|
381
|
+
参数:
|
382
|
+
data: 要写入的字节串
|
383
|
+
|
384
|
+
返回:
|
385
|
+
写入的字节数
|
386
|
+
|
387
|
+
异常:
|
388
|
+
OSError: 如果文件无法写入
|
389
|
+
"""
|
390
|
+
with open(str(self), 'wb') as f:
|
391
|
+
return f.write(data)
|
392
|
+
|
393
|
+
def unlink(self, missing_ok=False):
|
394
|
+
"""
|
395
|
+
删除文件
|
396
|
+
|
397
|
+
参数:
|
398
|
+
missing_ok: 如果文件不存在是否不报错
|
399
|
+
|
400
|
+
异常:
|
401
|
+
FileNotFoundError: 如果文件不存在且missing_ok为False
|
402
|
+
OSError: 如果文件无法删除
|
403
|
+
"""
|
404
|
+
try:
|
405
|
+
os.unlink(str(self))
|
406
|
+
except FileNotFoundError:
|
407
|
+
if not missing_ok:
|
408
|
+
raise
|
409
|
+
|
410
|
+
def rmdir(self):
|
411
|
+
"""
|
412
|
+
删除空目录
|
413
|
+
|
414
|
+
异常:
|
415
|
+
OSError: 如果目录不为空或无法删除
|
416
|
+
"""
|
417
|
+
os.rmdir(str(self))
|
418
|
+
|
419
|
+
def rename(self, target) -> 'Path':
|
420
|
+
"""
|
421
|
+
重命名文件或目录
|
422
|
+
|
423
|
+
参数:
|
424
|
+
target: 目标路径
|
425
|
+
|
426
|
+
返回:
|
427
|
+
新的Path对象
|
428
|
+
|
429
|
+
异常:
|
430
|
+
OSError: 如果重命名失败
|
431
|
+
"""
|
432
|
+
os.rename(str(self), str(target))
|
433
|
+
return self.__class__(target)
|
434
|
+
|
435
|
+
def resolve(self) -> 'Path':
|
436
|
+
"""
|
437
|
+
解析符号链接的绝对路径
|
438
|
+
|
439
|
+
返回:
|
440
|
+
解析后的新Path对象
|
441
|
+
"""
|
442
|
+
return self.__class__(os.path.realpath(str(self)))
|
443
|
+
|
444
|
+
def absolute(self) -> 'Path':
|
445
|
+
"""
|
446
|
+
获取绝对路径
|
447
|
+
|
448
|
+
返回:
|
449
|
+
绝对路径的新Path对象
|
450
|
+
"""
|
451
|
+
return self.__class__(os.path.abspath(str(self)))
|
452
|
+
|
453
|
+
def glob(self, pattern: str) -> Iterator['Path']:
|
454
|
+
"""
|
455
|
+
匹配文件模式
|
456
|
+
|
457
|
+
参数:
|
458
|
+
pattern: 要匹配的模式(如'*.txt')
|
459
|
+
|
460
|
+
返回:
|
461
|
+
匹配路径的生成器
|
462
|
+
"""
|
463
|
+
import glob
|
464
|
+
for p in glob.glob(str(self / pattern)):
|
465
|
+
yield self.__class__(p)
|
466
|
+
|
467
|
+
def rglob(self, pattern: str) -> Iterator['Path']:
|
468
|
+
"""
|
469
|
+
递归匹配文件模式
|
470
|
+
|
471
|
+
参数:
|
472
|
+
pattern: 要匹配的模式
|
473
|
+
|
474
|
+
返回:
|
475
|
+
匹配路径的生成器
|
476
|
+
"""
|
477
|
+
return self.glob('**' + os.sep + pattern)
|
478
|
+
|
479
|
+
def iterdir(self) -> Iterator['Path']:
|
480
|
+
"""
|
481
|
+
迭代目录内容
|
482
|
+
|
483
|
+
返回:
|
484
|
+
目录中子项的Path对象生成器
|
485
|
+
|
486
|
+
异常:
|
487
|
+
OSError: 如果不是目录或无法读取
|
488
|
+
"""
|
489
|
+
for name in os.listdir(str(self)):
|
490
|
+
yield self.__class__(self, name)
|
491
|
+
|
492
|
+
@property
|
493
|
+
def parent(self) -> 'Path':
|
494
|
+
"""
|
495
|
+
父目录
|
496
|
+
|
497
|
+
返回:
|
498
|
+
父目录的Path对象
|
499
|
+
"""
|
500
|
+
return self.__class__(os.path.dirname(str(self)))
|
501
|
+
|
502
|
+
@property
|
503
|
+
def parents(self) -> List['Path']:
|
504
|
+
"""
|
505
|
+
所有父目录列表
|
506
|
+
|
507
|
+
返回:
|
508
|
+
从近到远的所有父目录Path对象列表
|
509
|
+
"""
|
510
|
+
path = self
|
511
|
+
parents = []
|
512
|
+
while True:
|
513
|
+
parent = path.parent
|
514
|
+
if parent == path:
|
515
|
+
break
|
516
|
+
parents.append(parent)
|
517
|
+
path = parent
|
518
|
+
return parents
|
519
|
+
|
520
|
+
def with_name(self, name: str) -> 'Path':
|
521
|
+
"""
|
522
|
+
返回修改文件名后的新路径
|
523
|
+
|
524
|
+
参数:
|
525
|
+
name: 新文件名
|
526
|
+
|
527
|
+
返回:
|
528
|
+
新Path对象
|
529
|
+
|
530
|
+
异常:
|
531
|
+
ValueError: 如果当前路径没有名称
|
532
|
+
"""
|
533
|
+
if not self.name:
|
534
|
+
raise ValueError(f"{self} 没有名称")
|
535
|
+
return self.__class__(self.parent, name)
|
536
|
+
|
537
|
+
def with_suffix(self, suffix: str) -> 'Path':
|
538
|
+
"""
|
539
|
+
返回修改后缀后的新路径
|
540
|
+
|
541
|
+
参数:
|
542
|
+
suffix: 新后缀(必须以点开头)
|
543
|
+
|
544
|
+
返回:
|
545
|
+
新Path对象
|
546
|
+
|
547
|
+
异常:
|
548
|
+
ValueError: 如果后缀无效或路径没有名称
|
549
|
+
"""
|
550
|
+
if not suffix.startswith('.'):
|
551
|
+
raise ValueError("无效的后缀")
|
552
|
+
name = self.name
|
553
|
+
if not name:
|
554
|
+
raise ValueError(f"{self} 没有名称")
|
555
|
+
|
556
|
+
old_suffix = self.suffix
|
557
|
+
if old_suffix:
|
558
|
+
name = name[:-len(old_suffix)]
|
559
|
+
return self.__class__(self.parent, name + suffix)
|
560
|
+
|
561
|
+
# ===== 增强功能 =====
|
562
|
+
def touch(self, mode=0o666, exist_ok=True):
|
563
|
+
"""
|
564
|
+
创建空文件(类似Unix touch命令)
|
565
|
+
|
566
|
+
参数:
|
567
|
+
mode: 文件权限
|
568
|
+
exist_ok: 如果文件存在是否不报错
|
569
|
+
|
570
|
+
异常:
|
571
|
+
FileExistsError: 如果文件存在且exist_ok为False
|
572
|
+
OSError: 如果文件无法创建
|
573
|
+
"""
|
574
|
+
if self.exists():
|
575
|
+
if exist_ok:
|
576
|
+
os.utime(str(self), None)
|
577
|
+
else:
|
578
|
+
raise FileExistsError(str(self))
|
579
|
+
else:
|
580
|
+
open(str(self), 'a').close()
|
581
|
+
self.chmod(mode)
|
582
|
+
|
583
|
+
def copy(self, target, follow_symlinks=True):
|
584
|
+
"""
|
585
|
+
复制文件到目标路径
|
586
|
+
|
587
|
+
参数:
|
588
|
+
target: 目标路径
|
589
|
+
follow_symlinks: 是否跟随符号链接
|
590
|
+
|
591
|
+
返回:
|
592
|
+
目标路径的Path对象
|
593
|
+
|
594
|
+
异常:
|
595
|
+
OSError: 如果复制失败
|
596
|
+
"""
|
597
|
+
shutil.copy2(str(self), str(target), follow_symlinks=follow_symlinks)
|
598
|
+
return self.__class__(target)
|
599
|
+
|
600
|
+
def move(self, target):
|
601
|
+
"""
|
602
|
+
移动文件/目录到目标位置
|
603
|
+
|
604
|
+
参数:
|
605
|
+
target: 目标路径
|
606
|
+
|
607
|
+
返回:
|
608
|
+
目标路径的Path对象
|
609
|
+
|
610
|
+
异常:
|
611
|
+
OSError: 如果移动失败
|
612
|
+
"""
|
613
|
+
shutil.move(str(self), str(target))
|
614
|
+
return self.__class__(target)
|
615
|
+
|
616
|
+
def size(self) -> int:
|
617
|
+
"""
|
618
|
+
获取文件大小(字节)
|
619
|
+
|
620
|
+
返回:
|
621
|
+
文件大小(字节)
|
622
|
+
|
623
|
+
异常:
|
624
|
+
OSError: 如果无法获取大小
|
625
|
+
"""
|
626
|
+
return self.stat().st_size
|
627
|
+
|
628
|
+
def access_time(self) -> datetime:
|
629
|
+
"""
|
630
|
+
获取最后访问时间
|
631
|
+
|
632
|
+
返回:
|
633
|
+
最后访问时间的datetime对象
|
634
|
+
"""
|
635
|
+
return datetime.fromtimestamp(self.stat().st_atime)
|
636
|
+
|
637
|
+
def modify_time(self) -> datetime:
|
638
|
+
"""
|
639
|
+
获取最后修改时间
|
640
|
+
|
641
|
+
返回:
|
642
|
+
最后修改时间的datetime对象
|
643
|
+
"""
|
644
|
+
return datetime.fromtimestamp(self.stat().st_mtime)
|
645
|
+
|
646
|
+
def create_time(self) -> datetime:
|
647
|
+
"""
|
648
|
+
获取创建时间(Windows)或元数据修改时间(Unix)
|
649
|
+
|
650
|
+
返回:
|
651
|
+
创建时间的datetime对象
|
652
|
+
"""
|
653
|
+
if os.name == 'nt':
|
654
|
+
return datetime.fromtimestamp(self.stat().st_ctime)
|
655
|
+
return datetime.fromtimestamp(self.stat().st_mtime)
|
656
|
+
|
657
|
+
def rmtree(self, ignore_errors=False, onerror=None):
|
658
|
+
"""
|
659
|
+
递归删除目录树
|
660
|
+
|
661
|
+
参数:
|
662
|
+
ignore_errors: 是否忽略错误
|
663
|
+
onerror: 错误处理回调函数
|
664
|
+
|
665
|
+
异常:
|
666
|
+
OSError: 如果删除失败且ignore_errors为False
|
667
|
+
"""
|
668
|
+
shutil.rmtree(str(self), ignore_errors, onerror)
|
669
|
+
|
670
|
+
def find(self, pattern: str = "*") -> Iterator['Path']:
|
671
|
+
"""
|
672
|
+
递归查找匹配模式的文件
|
673
|
+
|
674
|
+
参数:
|
675
|
+
pattern: 要匹配的模式(如'*.py')
|
676
|
+
|
677
|
+
返回:
|
678
|
+
匹配路径的生成器
|
679
|
+
"""
|
680
|
+
for root, _, files in os.walk(str(self)):
|
681
|
+
for name in fnmatch.filter(files, pattern):
|
682
|
+
yield self.__class__(root, name)
|
683
|
+
|
684
|
+
def read_lines(self, encoding=None, errors=None) -> List[str]:
|
685
|
+
"""
|
686
|
+
按行读取文件内容
|
687
|
+
|
688
|
+
参数:
|
689
|
+
encoding: 文本编码
|
690
|
+
errors: 编码错误处理策略
|
691
|
+
|
692
|
+
返回:
|
693
|
+
行列表
|
694
|
+
|
695
|
+
异常:
|
696
|
+
OSError: 如果文件无法读取
|
697
|
+
"""
|
698
|
+
with self.open('r', encoding=encoding, errors=errors) as f:
|
699
|
+
return f.readlines()
|
700
|
+
|
701
|
+
def write_lines(self, lines: List[str], encoding=None, errors=None):
|
702
|
+
"""
|
703
|
+
按行写入文件内容
|
704
|
+
|
705
|
+
参数:
|
706
|
+
lines: 要写入的行列表
|
707
|
+
encoding: 文本编码
|
708
|
+
errors: 编码错误处理策略
|
709
|
+
|
710
|
+
异常:
|
711
|
+
OSError: 如果文件无法写入
|
712
|
+
"""
|
713
|
+
with self.open('w', encoding=encoding, errors=errors) as f:
|
714
|
+
f.writelines(lines)
|
715
|
+
|
716
|
+
def append_text(self, text: str, encoding=None, errors=None):
|
717
|
+
"""
|
718
|
+
追加文本内容
|
719
|
+
|
720
|
+
参数:
|
721
|
+
text: 要追加的文本
|
722
|
+
encoding: 文本编码
|
723
|
+
errors: 编码错误处理策略
|
724
|
+
|
725
|
+
异常:
|
726
|
+
OSError: 如果文件无法写入
|
727
|
+
"""
|
728
|
+
with self.open('a', encoding=encoding, errors=errors) as f:
|
729
|
+
f.write(text)
|
730
|
+
|
731
|
+
def md5(self, chunk_size=8192) -> str:
|
732
|
+
"""
|
733
|
+
计算文件的MD5哈希值
|
734
|
+
|
735
|
+
参数:
|
736
|
+
chunk_size: 读取块大小
|
737
|
+
|
738
|
+
返回:
|
739
|
+
MD5哈希字符串
|
740
|
+
"""
|
741
|
+
hash_md5 = hashlib.md5()
|
742
|
+
with self.open('rb') as f:
|
743
|
+
for chunk in iter(lambda: f.read(chunk_size), b""):
|
744
|
+
hash_md5.update(chunk)
|
745
|
+
return hash_md5.hexdigest()
|
746
|
+
|
747
|
+
def sha256(self, chunk_size=8192) -> str:
|
748
|
+
"""
|
749
|
+
计算文件的SHA256哈希值
|
750
|
+
|
751
|
+
参数:
|
752
|
+
chunk_size: 读取块大小
|
753
|
+
|
754
|
+
返回:
|
755
|
+
SHA256哈希字符串
|
756
|
+
"""
|
757
|
+
hash_sha = hashlib.sha256()
|
758
|
+
with self.open('rb') as f:
|
759
|
+
for chunk in iter(lambda: f.read(chunk_size), b""):
|
760
|
+
hash_sha.update(chunk)
|
761
|
+
return hash_sha.hexdigest()
|
762
|
+
|
763
|
+
def compare(self, other: 'Path', shallow=True) -> bool:
|
764
|
+
"""
|
765
|
+
比较文件内容是否相同
|
766
|
+
|
767
|
+
参数:
|
768
|
+
other: 要比较的另一路径
|
769
|
+
shallow: 是否只比较元数据
|
770
|
+
|
771
|
+
返回:
|
772
|
+
如果内容相同返回True,否则返回False
|
773
|
+
"""
|
774
|
+
if not isinstance(other, Path):
|
775
|
+
other = Path(other)
|
776
|
+
return filecmp.cmp(str(self), str(other), shallow=shallow)
|
777
|
+
|
778
|
+
@classmethod
|
779
|
+
def temp_file(cls, suffix=None, prefix=None, dir=None) -> 'Path':
|
780
|
+
"""
|
781
|
+
创建临时文件
|
782
|
+
|
783
|
+
参数:
|
784
|
+
suffix: 后缀名
|
785
|
+
prefix: 前缀名
|
786
|
+
dir: 目录路径
|
787
|
+
|
788
|
+
返回:
|
789
|
+
临时文件的Path对象
|
790
|
+
"""
|
791
|
+
fd, name = tempfile.mkstemp(suffix, prefix, dir)
|
792
|
+
os.close(fd)
|
793
|
+
return cls(name)
|
794
|
+
|
795
|
+
@classmethod
|
796
|
+
def temp_dir(cls, suffix=None, prefix=None, dir=None) -> 'Path':
|
797
|
+
"""
|
798
|
+
创建临时目录
|
799
|
+
|
800
|
+
参数:
|
801
|
+
suffix: 后缀名
|
802
|
+
prefix: 前缀名
|
803
|
+
dir: 目录路径
|
804
|
+
|
805
|
+
返回:
|
806
|
+
临时目录的Path对象
|
807
|
+
"""
|
808
|
+
return cls(tempfile.mkdtemp(suffix, prefix, dir))
|
809
|
+
|
810
|
+
def readlink(self) -> 'Path':
|
811
|
+
"""
|
812
|
+
读取符号链接指向的实际路径
|
813
|
+
|
814
|
+
返回:
|
815
|
+
实际路径的Path对象
|
816
|
+
|
817
|
+
异常:
|
818
|
+
OSError: 如果不是符号链接或无法读取
|
819
|
+
"""
|
820
|
+
if not self.is_symlink():
|
821
|
+
raise OSError(f"{self} 不是符号链接")
|
822
|
+
return self.__class__(os.readlink(str(self)))
|
823
|
+
|
824
|
+
def chmod(self, mode: int, *, follow_symlinks=True):
|
825
|
+
"""
|
826
|
+
修改文件权限
|
827
|
+
|
828
|
+
参数:
|
829
|
+
mode: 权限模式(八进制)
|
830
|
+
follow_symlinks: 是否跟随符号链接
|
831
|
+
|
832
|
+
异常:
|
833
|
+
OSError: 如果修改失败
|
834
|
+
"""
|
835
|
+
if follow_symlinks:
|
836
|
+
os.chmod(str(self), mode)
|
837
|
+
else:
|
838
|
+
if hasattr(os, 'lchmod'): # 仅Unix
|
839
|
+
os.lchmod(str(self), mode)
|
840
|
+
|
841
|
+
def is_executable(self) -> bool:
|
842
|
+
"""
|
843
|
+
检查文件是否可执行
|
844
|
+
|
845
|
+
返回:
|
846
|
+
如果可执行返回True,否则返回False
|
847
|
+
"""
|
848
|
+
return os.access(str(self), os.X_OK)
|
849
|
+
|
850
|
+
def relative_to(self, other: Union['Path', str]) -> 'Path':
|
851
|
+
"""
|
852
|
+
计算相对于另一路径的相对路径
|
853
|
+
|
854
|
+
参数:
|
855
|
+
other: 基准路径
|
856
|
+
|
857
|
+
返回:
|
858
|
+
相对路径的Path对象
|
859
|
+
|
860
|
+
异常:
|
861
|
+
ValueError: 如果路径没有共同前缀
|
862
|
+
"""
|
863
|
+
if not isinstance(other, Path):
|
864
|
+
other = Path(other)
|
865
|
+
return self.__class__(os.path.relpath(str(self), str(other)))
|
866
|
+
|
867
|
+
def matches(self, pattern: str) -> bool:
|
868
|
+
"""
|
869
|
+
检查路径是否匹配通配符模式
|
870
|
+
|
871
|
+
参数:
|
872
|
+
pattern: 要匹配的模式
|
873
|
+
|
874
|
+
返回:
|
875
|
+
如果匹配返回True,否则返回False
|
876
|
+
"""
|
877
|
+
return fnmatch.fnmatch(str(self), pattern)
|
878
|
+
|
879
|
+
def __enter__(self):
|
880
|
+
"""支持with语句,返回自身"""
|
881
|
+
return self
|
882
|
+
|
883
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
884
|
+
"""支持with语句,无操作"""
|
885
|
+
pass
|
886
|
+
|
887
|
+
|
888
|
+
class PosixPath(Path, PurePosixPath):
|
889
|
+
"""
|
890
|
+
POSIX系统专用路径实现
|
891
|
+
|
892
|
+
仅在POSIX兼容系统(如Linux, macOS)上可用
|
893
|
+
"""
|
894
|
+
|
895
|
+
def __new__(cls, *args, **kwargs):
|
896
|
+
if os.name != 'posix' and os.name != 'java' and not sys.platform.startswith('linux'):
|
897
|
+
raise NotImplementedError("PosixPath 仅支持POSIX系统")
|
898
|
+
return super().__new__(cls, *args, **kwargs)
|
899
|
+
|
900
|
+
def resolve(self, strict=False) -> 'PosixPath':
|
901
|
+
"""
|
902
|
+
解析符号链接的绝对路径
|
903
|
+
|
904
|
+
参数:
|
905
|
+
strict: 如果路径不存在是否抛出异常
|
906
|
+
|
907
|
+
返回:
|
908
|
+
解析后的新Path对象
|
909
|
+
|
910
|
+
异常:
|
911
|
+
FileNotFoundError: 如果路径不存在且strict为True
|
912
|
+
"""
|
913
|
+
path = os.path.realpath(str(self))
|
914
|
+
if strict and not os.path.exists(path):
|
915
|
+
raise FileNotFoundError(path)
|
916
|
+
return self.__class__(path)
|
917
|
+
|
918
|
+
def owner(self) -> str:
|
919
|
+
"""
|
920
|
+
获取文件所有者
|
921
|
+
|
922
|
+
返回:
|
923
|
+
所有者用户名
|
924
|
+
|
925
|
+
异常:
|
926
|
+
OSError: 如果无法获取所有者信息
|
927
|
+
"""
|
928
|
+
import pwd
|
929
|
+
return pwd.getpwuid(self.stat().st_uid).pw_name
|
930
|
+
|
931
|
+
def group(self) -> str:
|
932
|
+
"""
|
933
|
+
获取文件所属组
|
934
|
+
|
935
|
+
返回:
|
936
|
+
组名
|
937
|
+
|
938
|
+
异常:
|
939
|
+
OSError: 如果无法获取组信息
|
940
|
+
"""
|
941
|
+
import grp
|
942
|
+
return grp.getgrgid(self.stat().st_gid).gr_name
|
943
|
+
|
944
|
+
def symlink_to(self, target, target_is_directory=False):
|
945
|
+
"""
|
946
|
+
创建符号链接
|
947
|
+
|
948
|
+
参数:
|
949
|
+
target: 目标路径
|
950
|
+
target_is_directory: 目标是否是目录
|
951
|
+
|
952
|
+
异常:
|
953
|
+
OSError: 如果创建失败
|
954
|
+
"""
|
955
|
+
os.symlink(
|
956
|
+
str(target),
|
957
|
+
str(self),
|
958
|
+
target_is_directory=target_is_directory
|
959
|
+
)
|
960
|
+
|
961
|
+
def is_symlink(self) -> bool:
|
962
|
+
"""
|
963
|
+
检查是否是符号链接
|
964
|
+
|
965
|
+
返回:
|
966
|
+
如果是符号链接返回True,否则返回False
|
967
|
+
"""
|
968
|
+
return os.path.islink(str(self))
|
969
|
+
|
970
|
+
def is_socket(self) -> bool:
|
971
|
+
"""
|
972
|
+
检查是否是套接字文件
|
973
|
+
|
974
|
+
返回:
|
975
|
+
如果是套接字文件返回True,否则返回False
|
976
|
+
"""
|
977
|
+
return stat.S_ISSOCK(self.stat().st_mode)
|
978
|
+
|
979
|
+
def is_fifo(self) -> bool:
|
980
|
+
"""
|
981
|
+
检查是否是FIFO管道
|
982
|
+
|
983
|
+
返回:
|
984
|
+
如果是FIFO管道返回True,否则返回False
|
985
|
+
"""
|
986
|
+
return stat.S_ISFIFO(self.stat().st_mode)
|
987
|
+
|
988
|
+
def is_block_device(self) -> bool:
|
989
|
+
"""
|
990
|
+
检查是否是块设备
|
991
|
+
|
992
|
+
返回:
|
993
|
+
如果是块设备返回True,否则返回False
|
994
|
+
"""
|
995
|
+
return stat.S_ISBLK(self.stat().st_mode)
|
996
|
+
|
997
|
+
def is_char_device(self) -> bool:
|
998
|
+
"""
|
999
|
+
检查是否是字符设备
|
1000
|
+
|
1001
|
+
返回:
|
1002
|
+
如果是字符设备返回True,否则返回False
|
1003
|
+
"""
|
1004
|
+
return stat.S_ISCHR(self.stat().st_mode)
|
1005
|
+
|
1006
|
+
def expanduser(self) -> 'PosixPath':
|
1007
|
+
"""
|
1008
|
+
展开用户目录(~)
|
1009
|
+
|
1010
|
+
返回:
|
1011
|
+
展开后的新Path对象
|
1012
|
+
"""
|
1013
|
+
return self.__class__(os.path.expanduser(str(self)))
|
1014
|
+
|
1015
|
+
def is_hidden(self) -> bool:
|
1016
|
+
"""
|
1017
|
+
检查文件是否隐藏(以点开头)
|
1018
|
+
|
1019
|
+
返回:
|
1020
|
+
如果隐藏返回True,否则返回False
|
1021
|
+
"""
|
1022
|
+
return self.name.startswith('.')
|
1023
|
+
|
1024
|
+
|
1025
|
+
class WindowsPath(Path, PureWindowsPath):
|
1026
|
+
"""
|
1027
|
+
Windows系统专用路径实现
|
1028
|
+
|
1029
|
+
仅在Windows系统上可用
|
1030
|
+
"""
|
1031
|
+
|
1032
|
+
def __new__(cls, *args, **kwargs):
|
1033
|
+
if os.name != 'nt':
|
1034
|
+
raise NotImplementedError("WindowsPath 仅支持Windows系统")
|
1035
|
+
return super().__new__(cls, *args, **kwargs)
|
1036
|
+
|
1037
|
+
def resolve(self, strict=False) -> 'WindowsPath':
|
1038
|
+
"""
|
1039
|
+
解析绝对路径(Windows专用)
|
1040
|
+
|
1041
|
+
参数:
|
1042
|
+
strict: 如果路径不存在是否抛出异常
|
1043
|
+
|
1044
|
+
返回:
|
1045
|
+
解析后的新Path对象
|
1046
|
+
|
1047
|
+
异常:
|
1048
|
+
FileNotFoundError: 如果路径不存在且strict为True
|
1049
|
+
"""
|
1050
|
+
path = os.path.abspath(str(self))
|
1051
|
+
if strict and not os.path.exists(path):
|
1052
|
+
raise FileNotFoundError(path)
|
1053
|
+
return self.__class__(path)
|
1054
|
+
|
1055
|
+
def owner(self) -> str:
|
1056
|
+
"""
|
1057
|
+
获取文件所有者(需要pywin32)
|
1058
|
+
|
1059
|
+
返回:
|
1060
|
+
所有者字符串(格式: 'DOMAIN\\username')
|
1061
|
+
|
1062
|
+
异常:
|
1063
|
+
NotImplementedError: 如果没有安装pywin32
|
1064
|
+
OSError: 如果无法获取所有者信息
|
1065
|
+
"""
|
1066
|
+
try:
|
1067
|
+
import win32security
|
1068
|
+
sd = win32security.GetFileSecurity(
|
1069
|
+
str(self), win32security.OWNER_SECURITY_INFORMATION
|
1070
|
+
)
|
1071
|
+
sid = sd.GetSecurityDescriptorOwner()
|
1072
|
+
name, domain, _ = win32security.LookupAccountSid(None, sid)
|
1073
|
+
return f"{domain}\\{name}"
|
1074
|
+
except ImportError:
|
1075
|
+
raise NotImplementedError("需要win32security模块")
|
1076
|
+
|
1077
|
+
def is_junction(self) -> bool:
|
1078
|
+
"""
|
1079
|
+
检查是否是junction点
|
1080
|
+
|
1081
|
+
返回:
|
1082
|
+
如果是junction点返回True,否则返回False
|
1083
|
+
"""
|
1084
|
+
try:
|
1085
|
+
return os.path.isjunction(str(self))
|
1086
|
+
except AttributeError: # Python < 3.12 兼容
|
1087
|
+
try:
|
1088
|
+
import win32file
|
1089
|
+
return win32file.GetFileAttributes(str(self)) & win32file.FILE_ATTRIBUTE_REPARSE_POINT
|
1090
|
+
except ImportError:
|
1091
|
+
return False
|
1092
|
+
|
1093
|
+
def is_mount(self) -> bool:
|
1094
|
+
"""
|
1095
|
+
检查是否是挂载点(网络驱动器)
|
1096
|
+
|
1097
|
+
返回:
|
1098
|
+
如果是挂载点返回True,否则返回False
|
1099
|
+
"""
|
1100
|
+
try:
|
1101
|
+
import win32file
|
1102
|
+
drive = os.path.splitdrive(str(self))[0]
|
1103
|
+
if not drive:
|
1104
|
+
return False
|
1105
|
+
return win32file.GetDriveType(drive) == win32file.DRIVE_REMOTE
|
1106
|
+
except ImportError:
|
1107
|
+
return False
|
1108
|
+
|
1109
|
+
def expanduser(self) -> 'WindowsPath':
|
1110
|
+
"""
|
1111
|
+
Windows用户目录展开
|
1112
|
+
|
1113
|
+
返回:
|
1114
|
+
展开后的新Path对象
|
1115
|
+
"""
|
1116
|
+
if self.drive or self.root:
|
1117
|
+
return self
|
1118
|
+
home = os.path.expanduser("~")
|
1119
|
+
return self.__class__(home) / self
|
1120
|
+
|
1121
|
+
def is_hidden(self) -> bool:
|
1122
|
+
"""
|
1123
|
+
检查文件是否隐藏(系统隐藏属性或以点开头)
|
1124
|
+
|
1125
|
+
返回:
|
1126
|
+
如果隐藏返回True,否则返回False
|
1127
|
+
"""
|
1128
|
+
try:
|
1129
|
+
import win32api
|
1130
|
+
return win32api.GetFileAttributes(str(self)) & win32api.FILE_ATTRIBUTE_HIDDEN
|
1131
|
+
except ImportError:
|
1132
|
+
return self.name.startswith('.') or super().is_hidden()
|
1133
|
+
|
1134
|
+
|
1135
|
+
# 根据系统选择默认Path实现
|
1136
|
+
if os.name == 'nt':
|
1137
|
+
Path = WindowsPath
|
1138
|
+
else:
|
1139
|
+
Path = PosixPath
|