oafuncs 0.0.97.13__py3-none-any.whl → 0.0.97.14__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.
@@ -1,34 +1,191 @@
1
- # progressbar.py
1
+ import random
2
2
  import re
3
3
  import shutil
4
4
  import sys
5
+ import threading
5
6
  import time
6
7
  import warnings
7
- from typing import Any, Iterable, List, Optional, Union
8
+ from collections import deque
9
+ from typing import Any, Callable, Deque, Iterable, List, NamedTuple, Optional, Union
8
10
 
9
11
  import numpy as np
12
+ from oafuncs.oa_cmap import get as get_cmap
10
13
 
11
14
  try:
12
- import matplotlib
15
+ # import matplotlib
13
16
  from matplotlib.colors import LinearSegmentedColormap, to_hex, to_rgb
14
17
  except ImportError:
15
18
  raise ImportError("This module requires matplotlib. Install with: pip install matplotlib")
16
19
 
17
20
 
21
+ class ProgressSample(NamedTuple):
22
+ """进度采样点"""
23
+
24
+ timestamp: float
25
+ """采样时间戳"""
26
+ completed: float
27
+ """已完成步数"""
28
+
29
+
30
+ class Task:
31
+ """进度任务信息类"""
32
+
33
+ def __init__(
34
+ self,
35
+ description: str,
36
+ total: Optional[float],
37
+ completed: float = 0,
38
+ visible: bool = True,
39
+ color: Any = "cyan",
40
+ get_time: Callable[[], float] = time.time,
41
+ ):
42
+ self.description = description
43
+ self.total = total
44
+ self.completed = completed
45
+ self.visible = visible
46
+ self.color = color
47
+ self._get_time = get_time
48
+
49
+ self.start_time: Optional[float] = None
50
+ self.stop_time: Optional[float] = None
51
+ self.finished_time: Optional[float] = None
52
+ self.finished_speed: Optional[float] = None
53
+ self._progress: Deque[ProgressSample] = deque(maxlen=1000)
54
+
55
+ def get_time(self) -> float:
56
+ """获取当前时间"""
57
+ return self._get_time()
58
+
59
+ def start(self) -> None:
60
+ """开始任务"""
61
+ if self.start_time is None:
62
+ self.start_time = self.get_time()
63
+
64
+ def stop(self) -> None:
65
+ """停止任务"""
66
+ if self.stop_time is None:
67
+ self.stop_time = self.get_time()
68
+
69
+ @property
70
+ def started(self) -> bool:
71
+ """任务是否已开始"""
72
+ return self.start_time is not None
73
+
74
+ @property
75
+ def finished(self) -> bool:
76
+ """任务是否已完成"""
77
+ return self.finished_time is not None
78
+
79
+ @property
80
+ def percentage(self) -> float:
81
+ """完成百分比"""
82
+ if not self.total:
83
+ return 0.0
84
+ completed = (self.completed / self.total) * 100.0
85
+ return min(100.0, max(0.0, completed))
86
+
87
+ @property
88
+ def elapsed(self) -> Optional[float]:
89
+ """已用时间"""
90
+ if self.start_time is None:
91
+ return None
92
+ if self.stop_time is not None:
93
+ return self.stop_time - self.start_time
94
+ return self.get_time() - self.start_time
95
+
96
+ @property
97
+ def remaining(self) -> Optional[float]:
98
+ """剩余步数"""
99
+ if self.total is None:
100
+ return None
101
+ return self.total - self.completed
102
+
103
+ @property
104
+ def speed(self) -> Optional[float]:
105
+ """估计速度(步数/秒)"""
106
+ if self.start_time is None:
107
+ return None
108
+
109
+ progress = self._progress
110
+ if not progress:
111
+ return None
112
+
113
+ total_time = progress[-1].timestamp - progress[0].timestamp
114
+ if total_time < 0.001:
115
+ return None
116
+
117
+ iter_progress = iter(progress)
118
+ next(iter_progress)
119
+ total_completed = sum(sample.completed for sample in iter_progress)
120
+
121
+ speed = total_completed / total_time
122
+ return speed
123
+
124
+ @property
125
+ def time_remaining(self) -> Optional[float]:
126
+ """预估剩余时间"""
127
+ if self.finished:
128
+ return 0.0
129
+
130
+ speed = self.speed
131
+ if not speed:
132
+ return None
133
+
134
+ remaining = self.remaining
135
+ if remaining is None:
136
+ return None
137
+
138
+ estimate = remaining / speed
139
+ return estimate
140
+
141
+
18
142
  class ColorProgressBar:
19
- def __init__(self, iterable: Iterable, prefix: str = "", color: Any = "cyan", cmap: Union[str, List[str]] = None, update_interval: float = 0.1, bar_length: int = None):
143
+ """彩色进度条,支持多种终端环境和颜色渐变效果"""
144
+
145
+ def __init__(
146
+ self,
147
+ iterable: Iterable,
148
+ description: str = "Working ...",
149
+ total: Optional[float] = None,
150
+ completed: float = 0,
151
+ color: Any = "cyan",
152
+ cmap: Union[str, List[str]] = None,
153
+ update_interval: float = 0.1,
154
+ bar_length: int = None,
155
+ speed_estimate_period: float = 30.0,
156
+ ):
20
157
  self.iterable = iterable
21
- self.prefix = prefix
22
- self.base_color = color
158
+ self.description = description
159
+ self.color = color
23
160
  self.cmap = cmap
24
161
  self.update_interval = update_interval
25
162
  self.bar_length = bar_length
163
+ self.speed_estimate_period = speed_estimate_period
164
+ self.mark_num = random.randint(0, 13) # 随机选择填充字符的数量
26
165
 
27
- self._start_time = None
28
- self._last_update = 0
29
- self._count = len(iterable) if hasattr(iterable, "__len__") else None
166
+ # 线程安全锁
167
+ self._lock = threading.RLock()
168
+
169
+ # 尝试获取总数
170
+ if total is None and hasattr(iterable, "__len__"):
171
+ total = len(iterable)
172
+
173
+ # 创建任务
174
+ self.task = Task(
175
+ description=description,
176
+ total=total,
177
+ completed=completed,
178
+ color=color,
179
+ )
180
+
181
+ # 输出和渲染相关
30
182
  self._file = sys.stdout
31
- self._gradient_colors = self._generate_gradient() if cmap and self._count else None
183
+ self._gradient_colors = self._generate_gradient() if cmap and self.task.total else None
184
+ self._last_update_time = 0
185
+
186
+ # 检测终端环境
187
+ self._is_terminal = hasattr(self._file, "isatty") and self._file.isatty()
188
+ self._is_jupyter = "ipykernel" in sys.modules
32
189
 
33
190
  def _generate_gradient(self) -> Optional[List[str]]:
34
191
  """生成渐变色列表(修复内置colormap支持)"""
@@ -39,15 +196,9 @@ class ColorProgressBar:
39
196
  # 直接处理已经是colormap对象的情况
40
197
  cmap = self.cmap
41
198
  else:
42
- # 兼容不同版本的matplotlib
43
- try:
44
- # 新版本matplotlib (>=3.6)
45
- cmap = matplotlib.colormaps[self.cmap]
46
- except (AttributeError, KeyError):
47
- # 旧版本matplotlib
48
- cmap = matplotlib.cm.get_cmap(self.cmap)
49
-
50
- return [to_hex(cmap(i)) for i in np.linspace(0, 1, self._count)]
199
+ cmap = get_cmap(self.cmap) # 使用oafuncs.oa_cmap.get获取内置colormap;也可获取原本自定义的colormap
200
+
201
+ return [to_hex(cmap(i)) for i in np.linspace(0, 1, int(self.task.total))]
51
202
  except Exception as e:
52
203
  warnings.warn(f"Colormap generation failed: {str(e)}. cmap type: {type(self.cmap)}")
53
204
  return None
@@ -63,13 +214,15 @@ class ColorProgressBar:
63
214
 
64
215
  def _resolve_color(self, index: int) -> str:
65
216
  """解析当前应使用的颜色"""
66
- if self._gradient_colors and 0 <= index < len(self._gradient_colors):
217
+ if self._gradient_colors:
218
+ # 确保索引不超过颜色数
219
+ index = min(index, len(self._gradient_colors) - 1)
67
220
  try:
68
221
  return self._hex_to_ansi(self._gradient_colors[index])
69
222
  except (IndexError, ValueError):
70
223
  pass
71
224
 
72
- return self._process_color_value(self.base_color)
225
+ return self._process_color_value(self.task.color)
73
226
 
74
227
  def _process_color_value(self, color: Any) -> str:
75
228
  """处理颜色输入格式"""
@@ -78,6 +231,9 @@ class ColorProgressBar:
78
231
  "green": "\033[92m",
79
232
  "yellow": "\033[93m",
80
233
  "cyan": "\033[96m",
234
+ "blue": "\033[94m",
235
+ "magenta": "\033[95m",
236
+ "white": "\033[97m",
81
237
  }
82
238
 
83
239
  if color in preset_map:
@@ -97,79 +253,152 @@ class ColorProgressBar:
97
253
 
98
254
  def _format_bar(self, progress: float, width: int) -> str:
99
255
  """格式化进度条显示"""
100
- filled = "▊"
256
+ filled_list = ["▊", "█", "▓", "▒", "░", "#", "=", ">", "▌", "▍", "▎", "▏", "*"]
257
+ # filled = "░"
258
+ # filled = filled_list[2] # 选择中间的填充字符
259
+ # filled = random.choice(filled_list) # 随机选择填充字符
260
+ filled = filled_list[min(self.mark_num, len(filled_list) - 1)] # 随机选择填充字符
101
261
  empty = " "
102
262
  # 为其他信息保留更多空间
103
263
  max_width = max(10, width - 60) # 至少保留10个字符的进度条
104
264
  filled_length = int(round(max_width * progress))
105
265
  return filled * filled_length + empty * (max_width - filled_length)
106
266
 
107
- def _calculate_speed(self, index: int, elapsed: float) -> tuple:
108
- """计算速率和剩余时间"""
109
- # 防止除零错误
110
- if index == 0 or elapsed < 1e-6:
111
- return 0.0, 0.0
267
+ def update(self, advance: float = 1) -> None:
268
+ """更新进度"""
269
+ with self._lock:
270
+ current_time = time.time()
271
+ old_sample_time = current_time - self.speed_estimate_period
112
272
 
113
- rate = index / max(elapsed, 1e-6) # 确保分母不为零
114
- remaining = (self._count - index) / rate if self._count else 0
115
- return rate, remaining
273
+ completed_start = self.task.completed
274
+ self.task.completed += advance
275
+ update_completed = self.task.completed - completed_start
116
276
 
117
- def __iter__(self):
118
- self._start_time = time.time()
119
- self._last_update = self._start_time
120
- reset_code = "\033[0m"
277
+ # 更新速度采样
278
+ progress = self.task._progress
121
279
 
122
- # 判断是否在终端环境中
123
- is_terminal = hasattr(self._file, "isatty") and self._file.isatty()
280
+ # 清理旧的采样数据
281
+ while progress and progress[0].timestamp < old_sample_time:
282
+ progress.popleft()
124
283
 
125
- try:
126
- term_width = self.bar_length or (shutil.get_terminal_size().columns if is_terminal else 80)
127
- except (AttributeError, OSError):
128
- term_width = 80 # 默认终端宽度
284
+ # 添加新采样点
285
+ if update_completed > 0:
286
+ progress.append(ProgressSample(current_time, update_completed))
287
+
288
+ # 检查是否完成
289
+ if self.task.total is not None and self.task.completed >= self.task.total and self.task.finished_time is None:
290
+ self.task.finished_time = self.task.elapsed
291
+ self.task.finished_speed = self.task.speed
129
292
 
130
- for i, item in enumerate(self.iterable):
131
- now = time.time()
132
- elapsed = now - self._start_time
133
- yield item
293
+ def render(self) -> str:
294
+ """渲染进度条"""
295
+ with self._lock:
296
+ # 应该在锁保护下访问task对象
297
+ task = self.task
134
298
 
135
- # 非终端环境或更新间隔未到时跳过更新
136
- if not is_terminal or ((now - self._last_update) < self.update_interval and i + 1 != self._count):
137
- continue
299
+ # 获取终端宽度
300
+ try:
301
+ term_width = self.bar_length or (shutil.get_terminal_size().columns if self._is_terminal else 80)
302
+ except (AttributeError, OSError):
303
+ term_width = 80 # 默认终端宽度
304
+
305
+ # 确保有效宽度不小于最低限制
306
+ effective_width = max(15, term_width - 40)
307
+ if effective_width < 10:
308
+ warnings.warn("Terminal width is too small for proper progress bar rendering.")
309
+ effective_width = 10 # 设置最低宽度限制
310
+
311
+ # 计算进度信息
312
+ progress = task.completed / task.total if task.total else 0
313
+ index = int(task.completed)
314
+ current_color = self._resolve_color(index) if self._gradient_colors else self._resolve_color(0)
315
+ reset_code = "\033[0m"
316
+
317
+ # 计算时间和速度信息
318
+ elapsed = task.elapsed or 0
319
+ remaining = task.time_remaining
320
+ speed = task.speed or 0
321
+
322
+ # 调整时间单位
323
+ if elapsed >= 3600:
324
+ elapsed_info = f"Elapsed: {elapsed / 3600:.1f}h"
325
+ elif elapsed >= 60:
326
+ elapsed_info = f"Elapsed: {elapsed / 60:.1f}m"
327
+ else:
328
+ elapsed_info = f"Elapsed: {elapsed:.1f}s"
138
329
 
139
- progress = (i + 1) / self._count if self._count else 0
140
- current_color = self._resolve_color(i) if self._gradient_colors else self._resolve_color(0)
330
+ time_info = f"ETA: {remaining:.1f}s" if task.total and remaining and remaining > 0 else elapsed_info
141
331
 
142
- # 确保进度条至少有一个字符的宽度
143
- effective_width = max(15, term_width - 40) # 保留更多空间给信息显示
332
+ # 构建进度条视觉部分
144
333
  bar = self._format_bar(progress, effective_width)
145
334
 
146
- rate, remaining = self._calculate_speed(i + 1, elapsed)
147
-
148
- count_info = f"{i + 1}/{self._count}" if self._count else str(i + 1)
149
- percent = f"{progress:.1%}" if self._count else ""
150
- rate_info = f"{rate:.1f}it/s" if rate else ""
151
- time_info = f"ETA: {remaining:.1f}s" if self._count and remaining > 0 else f"Elapsed: {elapsed:.1f}s"
335
+ # 获取当前时间
336
+ current_time = time.strftime("%H:%M:%S", time.localtime())
152
337
 
153
- # 构建新的进度条行
154
- line = f"{self.prefix}{current_color}[{bar}]{reset_code} {count_info} {percent} [{time_info} | {rate_info}]"
338
+ # 组织显示文本
339
+ count_info = f"{int(task.completed)}/{int(task.total)}" if task.total else str(int(task.completed))
340
+ percent = f"{progress:.1%}" if task.total else ""
341
+ rate_info = f"{speed:.1f}it/s" if speed else ""
155
342
 
156
- # 清除之前的行并强制光标回到行首
157
- self._file.write("\r")
343
+ # 构建完整的进度条行
344
+ line = f"{current_time} {self.description} {current_color}[{bar}]{reset_code} {count_info} {percent} [{time_info} | {rate_info}]"
158
345
 
159
346
  # 确保不超出终端宽度
160
347
  if len(self._strip_ansi(line)) > term_width:
161
348
  line = line[: term_width - 3] + "..."
162
349
 
163
- # 输出并强制刷新
164
- self._file.write(line)
165
- self._file.flush()
166
- self._last_update = now
350
+ return line
167
351
 
168
- # 完成后添加换行符
169
- if is_terminal:
170
- self._file.write("\n")
352
+ def refresh(self) -> None:
353
+ """刷新显示进度条"""
354
+ if not self._is_terminal and not self._is_jupyter:
355
+ return
356
+
357
+ with self._lock:
358
+ line = self.render()
359
+
360
+ # 根据环境选择不同的输出方式
361
+ if self._is_jupyter:
362
+ # Jupyter环境,使用换行模式
363
+ self._file.write(f"{line}\n")
364
+ elif self._is_terminal:
365
+ # 标准终端环境,覆盖同一行
366
+ self._file.write(f"\r{line}")
367
+ else:
368
+ # 非交互式环境,仅在完成时输出
369
+ if self.task.finished:
370
+ self._file.write(f"{line}\n")
371
+
372
+ # 强制刷新输出
171
373
  self._file.flush()
172
374
 
375
+ def __iter__(self):
376
+ """迭代器实现,支持进度显示"""
377
+ self.task.start()
378
+ self._last_update_time = time.time()
379
+
380
+ try:
381
+ # 迭代原始可迭代对象
382
+ for i, item in enumerate(self.iterable):
383
+ yield item
384
+
385
+ # 更新进度
386
+ self.update(1)
387
+
388
+ # 判断是否需要刷新显示
389
+ now = time.time()
390
+ should_refresh = (now - self._last_update_time >= self.update_interval) or (self.task.total and self.task.completed >= self.task.total)
391
+
392
+ if should_refresh:
393
+ self.refresh()
394
+ self._last_update_time = now
395
+
396
+ finally:
397
+ # 完成后进行清理
398
+ if self._is_terminal and not self._is_jupyter:
399
+ self._file.write("\n")
400
+ self._file.flush()
401
+
173
402
  @classmethod
174
403
  def gradient_color(cls, colors: List[str], n: int) -> List[str]:
175
404
  """生成渐变色列表"""
@@ -179,18 +408,24 @@ class ColorProgressBar:
179
408
 
180
409
  # 验证示例
181
410
  if __name__ == "__main__":
182
- # 使用内置colormap示例
183
- import oafuncs
411
+ for _ in ColorProgressBar(range(100), description="Diverging:", cmap="diverging_1"):
412
+ # print("\rTesting...", end="")
413
+ time.sleep(0.1)
184
414
 
185
- cmap = oafuncs.oa_cmap.get("diverging_1")
186
- for _ in ColorProgressBar(range(100), cmap=cmap, prefix="Diverging: "):
415
+ for _ in ColorProgressBar(range(100), description="Colorful:", cmap="colorful_1"):
187
416
  time.sleep(0.1)
188
417
 
189
- for _ in ColorProgressBar(range(100), cmap="viridis", prefix="Viridis: "):
418
+ for _ in ColorProgressBar(range(100), description="Viridis:", cmap="viridis"):
190
419
  time.sleep(0.1)
191
420
 
192
421
  # 使用自定义渐变色
193
- for _ in ColorProgressBar(range(50), cmap=["#FF0000", "#0000FF"], prefix="Custom: "):
422
+ for _ in ColorProgressBar(range(50), description="Custom_cmap:", cmap=["#FF0000", "#0000FF"]):
423
+ time.sleep(0.1)
424
+
425
+ for _ in ColorProgressBar(range(50), description="Default:"):
426
+ time.sleep(0.1)
427
+
428
+ for _ in ColorProgressBar(range(50), description="Custom_color:", color="#FF00FF"):
194
429
  time.sleep(0.1)
195
430
 
196
431
  # 测试无法获取长度的迭代器
@@ -202,7 +437,7 @@ if __name__ == "__main__":
202
437
 
203
438
  # 限制为20个元素,但进度条不知道总长度
204
439
  gen = infinite_generator()
205
- for i, _ in enumerate(ColorProgressBar(gen, prefix="Unknown length: ")):
440
+ for i, _ in enumerate(ColorProgressBar(gen, description="Unknown_length:")):
206
441
  if i >= 20:
207
442
  break
208
443
  time.sleep(0.1)
@@ -1,17 +1,19 @@
1
1
  #!/usr/bin/env python
2
2
  # coding=utf-8
3
- '''
3
+ """
4
4
  Author: Liu Kun && 16031215@qq.com
5
- Date: 2024-11-21 09:47:41
5
+ Date: 2025-04-04 20:21:59
6
6
  LastEditors: Liu Kun && 16031215@qq.com
7
- LastEditTime: 2024-11-29 20:54:43
8
- FilePath: \\Python\\My_Funcs\\OAFuncs\\oafuncs\\oa_tool\\email.py
9
- Description:
7
+ LastEditTime: 2025-04-04 20:21:59
8
+ FilePath: \\Python\\My_Funcs\\OAFuncs\\oafuncs\\_script\\email.py
9
+ Description:
10
10
  EditPlatform: vscode
11
11
  ComputerInfo: XPS 15 9510
12
12
  SystemInfo: Windows 11
13
13
  Python Version: 3.12
14
- '''
14
+ """
15
+
16
+
15
17
 
16
18
 
17
19
  import random
@@ -22,29 +24,29 @@ from email.mime.text import MIMEText
22
24
 
23
25
  from rich import print
24
26
 
25
- __all__ = ['send']
27
+ __all__ = ["send"]
26
28
 
27
29
 
28
30
  def _email_info():
29
31
  email_dict = {
30
- 'liukun0312@vip.qq.com': [4, 13, -10, 2, -10, 4, -7, -8, 8, -1, 3, -2, -11, -6, -9, -7],
31
- '756866877@qq.com': [4, -2, -3, 13, 12, 8, -6, 9, -12, 13, -10, -12, -11, -12, -4, -11],
32
- '99138763@qq.com': [0, 6, 12, 2, 9, 9, 4, -1, 11, -7, -12, 6, -11, -3, -5, -11],
33
- '1031260@qq.com': [-1, -5, -9, 9, -3, 4, -8, -7, -12, -2, 0, -9, -11, -3, -7, -10],
32
+ "liukun0312@vip.qq.com": [4, 13, -10, 2, -10, 4, -7, -8, 8, -1, 3, -2, -11, -6, -9, -7],
33
+ "756866877@qq.com": [4, -2, -3, 13, 12, 8, -6, 9, -12, 13, -10, -12, -11, -12, -4, -11],
34
+ "99138763@qq.com": [0, 6, 12, 2, 9, 9, 4, -1, 11, -7, -12, 6, -11, -3, -5, -11],
35
+ "1031260@qq.com": [-1, -5, -9, 9, -3, 4, -8, -7, -12, -2, 0, -9, -11, -3, -7, -10],
34
36
  }
35
37
  keys = list(email_dict.keys())
36
- choose_email = random.randint(0, len(keys)-1)
38
+ choose_email = random.randint(0, len(keys) - 1)
37
39
  msg_from = keys[choose_email]
38
40
  return msg_from, email_dict[msg_from]
39
41
 
40
42
 
41
43
  def _decode_password(password):
42
- return ''.join([chr(i+109) for i in password])
44
+ return "".join([chr(i + 109) for i in password])
43
45
 
44
46
 
45
47
  def _send_message(title, content, msg_to):
46
48
  # 1. 连接邮箱服务器
47
- con = smtplib.SMTP_SSL('smtp.qq.com', 465)
49
+ con = smtplib.SMTP_SSL("smtp.qq.com", 465)
48
50
 
49
51
  # 2. 登录邮箱
50
52
  msg_from, password = _email_info()
@@ -55,14 +57,14 @@ def _send_message(title, content, msg_to):
55
57
  msg = MIMEMultipart()
56
58
 
57
59
  # 设置邮件主题
58
- subject = Header(title, 'utf-8').encode()
59
- msg['Subject'] = subject
60
+ subject = Header(title, "utf-8").encode()
61
+ msg["Subject"] = subject
60
62
 
61
63
  # 设置邮件发送者
62
- msg['From'] = msg_from
64
+ msg["From"] = msg_from
63
65
 
64
66
  # 设置邮件接受者
65
- msg['To'] = msg_to
67
+ msg["To"] = msg_to
66
68
 
67
69
  # # 添加html内容
68
70
  # content = """
@@ -78,18 +80,18 @@ def _send_message(title, content, msg_to):
78
80
 
79
81
  # or
80
82
  # content = '发送内容'
81
- msg.attach(MIMEText(content, 'plain', 'utf-8'))
83
+ msg.attach(MIMEText(content, "plain", "utf-8"))
82
84
 
83
85
  # 4.发送邮件
84
86
  con.sendmail(msg_from, msg_to, msg.as_string())
85
87
  con.quit()
86
88
 
87
- print(f'已通过{msg_from}成功向{msg_to}发送邮件!')
88
- print('发送内容为:\n{}\n\n'.format(content))
89
+ print(f"已通过{msg_from}成功向{msg_to}发送邮件!")
90
+ print("发送内容为:\n{}\n\n".format(content))
89
91
 
90
92
 
91
- def send(title='Title', content=None, send_to='16031215@qq.com'):
92
- '''
93
+ def send(title="Title", content=None, send_to="16031215@qq.com"):
94
+ """
93
95
  Description: 发送邮件
94
96
 
95
97
  Args:
@@ -102,7 +104,7 @@ def send(title='Title', content=None, send_to='16031215@qq.com'):
102
104
 
103
105
  Example:
104
106
  send(title='Title', content='Content', '123@qq.com')
105
- '''
107
+ """
106
108
  if content is None:
107
109
  # 避免发送空邮件,或有人误调用
108
110
  return