xlin 0.1.38__py2.py3-none-any.whl → 0.2.2__py2.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.
xlin/__init__.py CHANGED
@@ -1,10 +1,12 @@
1
- from .ischinese import *
2
- from .jsonl import *
1
+ from .dataframe_util import *
2
+ from .datetime_util import *
3
+ from .file_util import *
4
+ from .image_util import *
5
+ from .jsonlist_util import *
3
6
  from .metric import *
4
- from .multiprocess_mapping import *
5
- from .read_as_dataframe import *
7
+ from .multiprocess_util import *
6
8
  from .statistic import *
7
- from .timing import *
8
- from .util import *
9
- from .xls2xlsx import *
10
- from .yaml import *
9
+ from .text_util import *
10
+ from .timing_util import *
11
+ from .xlsx_util import *
12
+ from .yaml_util import *
@@ -1,3 +1,4 @@
1
+ import asyncio
1
2
  from collections import defaultdict
2
3
  import os
3
4
  from typing import Callable, Dict, List, Optional, Tuple, Union
@@ -7,9 +8,9 @@ from loguru import logger
7
8
  import pandas as pd
8
9
  import pyexcel
9
10
 
10
- from xlin.util import ls
11
- from xlin.jsonl import dataframe_to_json_list, load_json, load_json_list, save_json_list
12
- from xlin.xls2xlsx import is_xslx
11
+ from xlin.file_util import ls
12
+ from xlin.jsonlist_util import dataframe_to_json_list, load_json, load_json_list, save_json_list
13
+ from xlin.xlsx_util import is_xslx
13
14
 
14
15
 
15
16
  def read_as_dataframe(
@@ -269,3 +270,91 @@ def split_dataframe(
269
270
  df_i.to_excel(filepath, index=False)
270
271
  df_list.append(df_i)
271
272
  return df_list
273
+
274
+ def append_column(df: pd.DataFrame, query_column: str, output_column: str, transform):
275
+ query = df[query_column].tolist()
276
+ loop = asyncio.get_event_loop()
277
+ result = loop.run_until_complete(transform(query))
278
+ df[output_column] = [str(r) for r in result]
279
+ return df
280
+
281
+ def grouped_col_list(df: pd.DataFrame, key_col="query", value_col="output"):
282
+ grouped = defaultdict(list)
283
+ if key_col not in df.columns:
284
+ logger.warning(f"`{key_col}` not in columns: {list(df.columns)}")
285
+ return grouped
286
+ for i, row in df.iterrows():
287
+ grouped[row[key_col]].append(row[value_col])
288
+ return grouped
289
+
290
+
291
+ def grouped_col(df: pd.DataFrame, key_col="query", value_col="output"):
292
+ grouped = {}
293
+ if key_col not in df.columns:
294
+ logger.warning(f"`{key_col}` not in columns: {list(df.columns)}")
295
+ return grouped
296
+ for i, row in df.iterrows():
297
+ grouped[row[key_col]] = row[value_col]
298
+ return grouped
299
+
300
+
301
+ def grouped_row(df: pd.DataFrame, key_col="query"):
302
+ grouped = defaultdict(list)
303
+ if key_col not in df.columns:
304
+ logger.warning(f"`{key_col}` not in columns: {list(df.columns)}")
305
+ return grouped
306
+ for i, row in df.iterrows():
307
+ grouped[row[key_col]].append(row)
308
+ return grouped
309
+
310
+ def select_sub_df(
311
+ df: pd.DataFrame,
312
+ start_date: str,
313
+ end_date: str,
314
+ lookback_window: int = 0,
315
+ lookforward_window: int = 0,
316
+ include_end_date: bool = False,
317
+ ) -> pd.DataFrame:
318
+ """
319
+ 从DataFrame中选择指定日期范围内的子DataFrame。
320
+
321
+ Args:
322
+ df (pd.DataFrame): 带有日期索引的DataFrame,index是日期。
323
+ start_date (str): 起始日期,格式'YYYY-MM-DD'。
324
+ end_date (str): 结束日期,格式'YYYY-MM-DD'。
325
+ lookback_window (int): 向后查看的天数,默认为0。
326
+ lookforward_window (int): 向前查看的天数,默认为0。
327
+ include_end_date (bool): 是否包含结束日期,默认为False。
328
+
329
+ Returns:
330
+ pd.DataFrame: 指定日期范围内的子DataFrame。
331
+ """
332
+ # 确保索引是DatetimeIndex类型
333
+ if not isinstance(df.index, pd.DatetimeIndex):
334
+ df.index = pd.to_datetime(df.index)
335
+
336
+ # 确保索引是有序的
337
+ if not df.index.is_monotonic_increasing:
338
+ df = df.sort_index()
339
+
340
+ # 获取索引的时区信息
341
+ tz = df.index.tz
342
+
343
+ # 创建带时区的切片日期
344
+ start = pd.Timestamp(start_date, tz=tz)
345
+ end = pd.Timestamp(end_date, tz=tz)
346
+
347
+ # 选择子DataFrame
348
+ try:
349
+ if lookback_window > 0:
350
+ start = start - pd.Timedelta(days=lookback_window)
351
+ if lookforward_window > 0:
352
+ end = end + pd.Timedelta(days=lookforward_window)
353
+ if include_end_date:
354
+ end = end + pd.Timedelta(days=1)
355
+ sub_df = df[start:end]
356
+ except KeyError:
357
+ print(f"日期 {start_date} 或 {end_date} 不在索引范围内。")
358
+ sub_df = pd.DataFrame()
359
+
360
+ return sub_df
xlin/datetime_util.py ADDED
@@ -0,0 +1,175 @@
1
+
2
+
3
+ from typing import Literal, Optional, Union
4
+ import datetime
5
+ import random
6
+
7
+ import pandas as pd
8
+
9
+
10
+ date_str = datetime.datetime.now().strftime("%Y%m%d")
11
+ datetime_str = datetime.datetime.now().strftime("%Y%m%d_%Hh%Mm%Ss")
12
+
13
+
14
+ def random_timestamp(start_timestamp: Optional[float]=None, end_timestamp: Optional[float]=None):
15
+ if start_timestamp is None:
16
+ start_timestamp = datetime.datetime(2024, 1, 1).timestamp()
17
+ if end_timestamp is None:
18
+ end_timestamp = datetime.datetime.now().timestamp()
19
+ return random.uniform(start_timestamp, end_timestamp)
20
+
21
+
22
+ def random_datetime(
23
+ start_datetime: Optional[datetime.datetime] = None,
24
+ end_datetime: Optional[datetime.datetime] = None,
25
+ ) -> datetime.datetime:
26
+ """
27
+ 生成一个随机的 datetime 对象,范围在指定的开始和结束时间之间。
28
+ 如果未指定,则默认范围为 2024 年 1 月 1 日到当前时间。
29
+ """
30
+ if start_datetime is None:
31
+ start_datetime = datetime.datetime(2024, 1, 1)
32
+ if end_datetime is None:
33
+ end_datetime = datetime.datetime.now()
34
+
35
+ random_timestamp_value = random.uniform(start_datetime.timestamp(), end_datetime.timestamp())
36
+ return datetime.datetime.fromtimestamp(random_timestamp_value)
37
+
38
+
39
+
40
+ # 初始化中美节假日(可缓存)懒加载
41
+ us_holidays = None # US(categories=US.supported_categories)
42
+ cn_holidays = None # CN(categories=CN.supported_categories)
43
+
44
+
45
+ def format_datetime_with_holiday(
46
+ dt: Union[datetime.datetime, str, pd.Series, float],
47
+ language: Literal["zh", "en"] = "zh",
48
+ with_time: bool = True,
49
+ with_weekday: bool = True,
50
+ with_holiday: bool = True,
51
+ ) -> Union[str, pd.Series]:
52
+ """
53
+ 格式化时间为中文日期+英文星期几,附带中美节假日信息。
54
+ 如:2024年01月01日 10:00:00 星期一 [假期: 🇨🇳 元旦, 🇺🇸 New Year's Day]
55
+ 支持 datetime, str, pandas.Series 批处理。
56
+ Args:
57
+ dt: 待格式化的时间,可以是 datetime, str, pandas.Series 或 timestamp。
58
+ language: 语言选择,支持 "zh" 和 "en"
59
+ with_time: 是否包含时间
60
+ with_weekday: 是否包含星期几
61
+ with_holiday: 是否包含节假日信息
62
+ Returns:
63
+ 格式化后的字符串或 pandas.Series
64
+ Raises:
65
+ ValueError: 如果输入类型不正确
66
+ ImportError: 如果未安装 'holidays' 库
67
+ """
68
+ language_dict = {
69
+ "zh": {
70
+ "weekday": ["星期一", "星期二", "星期三", "星期四", "星期五", "星期六", "星期日"],
71
+ "holiday": "假期",
72
+ "date_format": "%Y年%m月%d日",
73
+ "time_format": "%H:%M:%S",
74
+ },
75
+ "en": {
76
+ "weekday": ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"],
77
+ "holiday": "Holiday",
78
+ "date_format": "%Y-%m-%d",
79
+ "time_format": "%H:%M:%S",
80
+ },
81
+ }
82
+
83
+ def _format_one(d: Union[datetime.datetime, str]) -> str:
84
+ if isinstance(d, str):
85
+ d = pd.to_datetime(d)
86
+ elif isinstance(d, float):
87
+ d = datetime.datetime.fromtimestamp(d)
88
+
89
+ if not isinstance(d, datetime.datetime):
90
+ raise ValueError("输入必须是 datetime, timestamp, str 或 pandas.Series 类型。")
91
+
92
+ formatted = d.strftime(language_dict[language]["date_format"])
93
+ if with_time:
94
+ formatted += " " + d.strftime(language_dict[language]["time_format"])
95
+ if with_weekday:
96
+ weekday_index = d.weekday()
97
+ formatted += " " + language_dict[language]["weekday"][weekday_index]
98
+ if not with_holiday:
99
+ return formatted
100
+ # 检查节假日
101
+ global us_holidays, cn_holidays
102
+ if not us_holidays or not cn_holidays:
103
+ try:
104
+ from holidays.countries import US, CN
105
+ except ImportError:
106
+ raise ImportError("请安装 'holidays' 库以支持节假日查询。可以使用 'pip install holidays' 安装。")
107
+ us_holidays = US(categories=US.supported_categories)
108
+ cn_holidays = CN(categories=CN.supported_categories)
109
+ tags = []
110
+ if d in cn_holidays:
111
+ tags.append(f"🇨🇳 {cn_holidays[d]}")
112
+ if d in us_holidays:
113
+ tags.append(f"🇺🇸 {us_holidays[d]}")
114
+
115
+ if tags:
116
+ holiday_str = language_dict[language]["holiday"]
117
+ formatted += f" [{holiday_str}: " + ", ".join(tags) + "]"
118
+ return formatted
119
+
120
+ if isinstance(dt, pd.Series):
121
+ return dt.apply(_format_one)
122
+ else:
123
+ return _format_one(dt)
124
+
125
+
126
+ def format_timedelta(
127
+ delta: datetime.timedelta,
128
+ language: Literal["zh", "en"] = "zh",
129
+ ) -> str:
130
+ """
131
+ 将 timedelta 格式化为精简的中文可读字符串,省略零值单位,四舍五入到秒
132
+
133
+ Args:
134
+ delta: 待格式化的时间间隔
135
+ language: 语言选择,支持 "zh" 和 "en"
136
+
137
+ Returns:
138
+ 精简的中文时间字符串(如 "1天3小时5分" 或 "45秒")
139
+ """
140
+ language_dict = {
141
+ "zh": {
142
+ "days": "天",
143
+ "hours": "小时",
144
+ "minutes": "分",
145
+ "seconds": "秒",
146
+ },
147
+ "en": {
148
+ "days": "days",
149
+ "hours": "hours",
150
+ "minutes": "minutes",
151
+ "seconds": "seconds",
152
+ },
153
+ }
154
+ # 处理负数时间(转为正数)
155
+ delta = abs(delta)
156
+
157
+ # 分解时间单位(四舍五入到秒)
158
+ days = delta.days
159
+ total_seconds = int(delta.total_seconds() + 0.5) # 四舍五入到秒
160
+ hours, remainder = divmod(total_seconds, 3600)
161
+ minutes, seconds = divmod(remainder, 60)
162
+
163
+ # 构建结果列表,跳过零值单位
164
+ parts = []
165
+ if days > 0:
166
+ parts.append(f"{days}{language_dict[language]['days']}")
167
+ if hours > 0:
168
+ parts.append(f"{hours}{language_dict[language]['hours']}")
169
+ if minutes > 0:
170
+ parts.append(f"{minutes}{language_dict[language]['minutes']}")
171
+ if seconds > 0:
172
+ parts.append(f"{seconds}{language_dict[language]['seconds']}")
173
+
174
+ # 处理全零情况(如 timedelta(0))
175
+ return "".join(parts) if parts else f"0{language_dict[language]['seconds']}"
@@ -3,30 +3,11 @@ from collections import defaultdict
3
3
  from pathlib import Path
4
4
  import os
5
5
  import asyncio
6
- import datetime
7
6
  import shutil
8
- import random
9
7
 
10
8
  import pandas as pd
11
9
  from loguru import logger
12
10
 
13
-
14
- date_str = datetime.datetime.now().strftime("%Y%m%d")
15
- datetime_str = datetime.datetime.now().strftime("%Y%m%d_%Hh%Mm%Ss")
16
-
17
-
18
- def random_timestamp(start_timestamp=None, end_timestamp=None):
19
- if start_timestamp is None:
20
- start_timestamp = datetime.datetime(2024, 1, 1).timestamp()
21
- if end_timestamp is None:
22
- end_timestamp = datetime.datetime.now().timestamp()
23
- return random.uniform(start_timestamp, end_timestamp)
24
-
25
-
26
- def random_timestamp_str(start_timestamp=None, end_timestamp=None, format="%Y年%m月%d日%H时%M分"):
27
- return datetime.datetime.fromtimestamp(random_timestamp(start_timestamp, end_timestamp)).strftime(format)
28
-
29
-
30
11
  def auto_retry_to_get_data(retry_times, request, data_key="data", *args, **kwargs):
31
12
  if retry_times == 0:
32
13
  return {}
@@ -40,13 +21,6 @@ def auto_retry_to_get_data(retry_times, request, data_key="data", *args, **kwarg
40
21
  return auto_retry_to_get_data(retry_times - 1, request, data_key, *args, **kwargs)
41
22
 
42
23
 
43
- def append_column(df: pd.DataFrame, query_column: str, output_column: str, transform):
44
- query = df[query_column].tolist()
45
- loop = asyncio.get_event_loop()
46
- result = loop.run_until_complete(transform(query))
47
- df[output_column] = [str(r) for r in result]
48
- return df
49
-
50
24
 
51
25
  def request_wrapper(request_num=10):
52
26
  def request_wrapper_body(func):
@@ -202,47 +176,6 @@ def clean_empty_folder(dir_path):
202
176
  clean_empty_folder(path)
203
177
 
204
178
 
205
- def grouped_col_list(df: pd.DataFrame, key_col="query", value_col="output"):
206
- grouped = defaultdict(list)
207
- if key_col not in df.columns:
208
- logger.warning(f"`{key_col}` not in columns: {list(df.columns)}")
209
- return grouped
210
- for i, row in df.iterrows():
211
- grouped[row[key_col]].append(row[value_col])
212
- return grouped
213
-
214
-
215
- def grouped_col(df: pd.DataFrame, key_col="query", value_col="output"):
216
- grouped = {}
217
- if key_col not in df.columns:
218
- logger.warning(f"`{key_col}` not in columns: {list(df.columns)}")
219
- return grouped
220
- for i, row in df.iterrows():
221
- grouped[row[key_col]] = row[value_col]
222
- return grouped
223
-
224
-
225
- def grouped_row(df: pd.DataFrame, key_col="query"):
226
- grouped = defaultdict(list)
227
- if key_col not in df.columns:
228
- logger.warning(f"`{key_col}` not in columns: {list(df.columns)}")
229
- return grouped
230
- for i, row in df.iterrows():
231
- grouped[row[key_col]].append(row)
232
- return grouped
233
-
234
-
235
- def grouped_row_in_jsonlist(jsonlist: List[Dict[str, Any]], key_col="query"):
236
- grouped = defaultdict(list)
237
- for i, row in enumerate(jsonlist):
238
- if key_col not in row:
239
- logger.warning(f"`{key_col}` not in row: {row}")
240
- notfound_key = f"NotFound:{key_col}"
241
- grouped[notfound_key].append(row)
242
- continue
243
- grouped[row[key_col]].append(row)
244
- return grouped
245
-
246
179
 
247
180
  def submit_file(path: Union[str, Path], target_dir: Union[str, Path]):
248
181
  p = Path(path).absolute()
@@ -267,44 +200,3 @@ def submit_file(path: Union[str, Path], target_dir: Union[str, Path]):
267
200
  logger.info("现在目标文件夹下的文件有:\n" + "\n".join(filenames))
268
201
 
269
202
 
270
- def pretty_limited_text(text: str, limited_length: int = 300, language="zh"):
271
- text = str(text).strip()
272
- if len(text) > limited_length:
273
- # if language == "zh":
274
- # tail = f"...(共{len(text)}字)"
275
- # else:
276
- # tail = f"...({len(text)} words in total)"
277
- # return text[: limited_length - len(tail)] + tail
278
- return text[: limited_length // 2] + text[-limited_length // 2 :]
279
- return text
280
-
281
-
282
- def bucket_count(length):
283
- grouped_count = []
284
- j = 0
285
- for i in range(0, max(length), 50):
286
- grouped_count.append(0)
287
- while length[j] < i:
288
- grouped_count[i // 50] += 1
289
- j += 1
290
- for i, j in enumerate(grouped_count):
291
- if i == 0 or j == 0:
292
- continue
293
- print(f"[{(i-1)*50}, {i*50}) {j} {sum(grouped_count[:i+1])/len(length)*100:.2f}%")
294
-
295
-
296
- def sortedCounter(obj, by="key", reverse=False, return_list=False):
297
- c = Counter(obj)
298
- c_list = [(k, c[k]) for k in c]
299
- if by == "key":
300
- c_list = sorted(c_list, key=lambda x: x[0], reverse=reverse)
301
- elif by in ["value", "count"]:
302
- c_list = sorted(c_list, key=lambda x: x[1], reverse=reverse)
303
- else:
304
- raise Exception(f"unsupported by: {by}")
305
- c = Counter()
306
- for k, v in c_list:
307
- c[k] = v
308
- if return_list:
309
- return c, c_list
310
- return c
xlin/image_util.py ADDED
@@ -0,0 +1,248 @@
1
+
2
+ import base64
3
+ from io import BytesIO
4
+ from loguru import logger
5
+ from PIL import Image, ImageDraw, ImageFont
6
+ import uuid
7
+ import os
8
+
9
+ import requests
10
+
11
+
12
+ def read_image_http_url(image_url: str) -> Image.Image:
13
+ # 使用 requests 获取图像的二进制数据
14
+ response = requests.get(image_url)
15
+ image_data = response.content
16
+
17
+ # 使用 Pillow 将二进制数据转换为 Image.Image 对象
18
+ image = Image.open(BytesIO(image_data))
19
+ return image
20
+
21
+ def image_to_base64(image: Image.Image) -> str:
22
+ buffered = BytesIO()
23
+ image.save(buffered, format="PNG")
24
+ b64 = base64.b64encode(buffered.getvalue()).decode()
25
+ return f"data:image/png;base64,{b64}"
26
+
27
+
28
+ def base64_to_image(base64_str: str) -> Image.Image:
29
+ """
30
+ Convert a base64 string to an image.
31
+ """
32
+ prefix_list = [
33
+ "data:image/png;base64,",
34
+ "data:image/jpeg;base64,",
35
+ "data:image/gif;base64,",
36
+ "data:image/webp;base64,",
37
+ ]
38
+ for prefix in prefix_list:
39
+ if base64_str.startswith(prefix):
40
+ base64_str = base64_str[len(prefix):]
41
+ break
42
+ image_data = base64.b64decode(base64_str)
43
+ image = Image.open(BytesIO(image_data))
44
+ return image
45
+
46
+
47
+ def generate_short_uuid(length=8):
48
+ # 生成标准 UUID
49
+ uuid_value = uuid.uuid4().bytes
50
+
51
+ # 使用 Base64 编码并转换为 URL 安全格式
52
+ encoded = base64.urlsafe_b64encode(uuid_value).decode("ascii")
53
+
54
+ # 移除可能的填充字符 '='
55
+ encoded = encoded.rstrip("=")
56
+
57
+ # 截取指定长度的字符串
58
+ return encoded[:length]
59
+
60
+
61
+
62
+ def scale_to_fit(image: Image.Image, target_size: tuple[int, int]=(512, 512)) -> Image.Image:
63
+ """
64
+ 将图像缩放到适合目标大小的尺寸,同时保持原始宽高比。
65
+
66
+ args:
67
+ image: PIL.Image.Image
68
+ 要缩放的图像。
69
+ target_size: tuple[int, int]
70
+ 目标大小,格式为 (width, height)。
71
+
72
+ return: PIL.Image.Image
73
+ 缩放后的图像。
74
+ """
75
+ original_width, original_height = image.size
76
+ target_width, target_height = target_size
77
+
78
+ # 计算缩放比例
79
+ width_ratio = target_width / original_width
80
+ height_ratio = target_height / original_height
81
+ scale_ratio = min(width_ratio, height_ratio)
82
+ if scale_ratio >= 1:
83
+ # 如果图像已经小于或等于目标大小,则不需要缩放
84
+ return image
85
+
86
+ # 计算新的尺寸
87
+ new_width = round(original_width * scale_ratio)
88
+ new_height = round(original_height * scale_ratio)
89
+
90
+ # 缩放图像
91
+ resized_image = image.resize((new_width, new_height), Image.Resampling.LANCZOS)
92
+ return resized_image
93
+
94
+
95
+ def add_scale_bar(
96
+ image: Image.Image,
97
+ spacing=64,
98
+ color=(0, 0, 0),
99
+ font_size=12,
100
+ left_margin=50,
101
+ top_margin=50,
102
+ tick_length=8,
103
+ tick_width=2,
104
+ text_offset=2,
105
+ origin_size: tuple[int, int] = None,
106
+ ):
107
+ """
108
+ 为图像添加顶部和左侧标尺,并将文字标签放在空白边距中,不与原图重叠。
109
+
110
+ args:
111
+ image: PIL.Image.Image
112
+ 要添加标尺的图像。
113
+ spacing: int
114
+ 刻度之间的间隔,单位为像素。
115
+ color: tuple
116
+ 刻度线和文字的颜色,RGB格式。
117
+ font_size: int
118
+ 文字的字体大小。
119
+ left_margin: int
120
+ 左侧边距的宽度,单位为像素。
121
+ top_margin: int
122
+ 顶部边距的高度,单位为像素。
123
+ tick_length: int
124
+ 刻度线的长度,单位为像素。
125
+ tick_width: int
126
+ 刻度线的宽度,单位为像素。
127
+ text_offset: int
128
+ 文字与刻度线之间的距离,单位为像素。
129
+ origin_size: tuple[int, int]
130
+ 原图的尺寸,格式为 (width, height)。如果未提供,则使用图像的实际尺寸。
131
+ return: PIL.Image.Image
132
+
133
+ 示例用法
134
+ ```
135
+ img = Image.open("/Pictures/example.png")
136
+ out = add_scale_bar(
137
+ img,
138
+ spacing=100,
139
+ color=(0, 0, 0),
140
+ font_size=12,
141
+ left_margin=50,
142
+ top_margin=50,
143
+ tick_length=8,
144
+ text_offset=4,
145
+ origin_size=(img.width, img.height) # 可选,指定原图尺寸
146
+ )
147
+ out
148
+ ```
149
+ """
150
+ # 加载字体
151
+ try:
152
+ font_path = "C:/Windows/Fonts/arial.ttf"
153
+ if not os.path.exists(font_path):
154
+ font_path = "/System/Library/Fonts/Supplemental/Arial.ttf"
155
+ if not os.path.exists(font_path):
156
+ font_path = "/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf"
157
+ if not os.path.exists(font_path):
158
+ font_path = "/usr/share/fonts/truetype/freefont/FreeMono.ttf"
159
+ if not os.path.exists(font_path):
160
+ font_path = "/usr/share/fonts/truetype/liberation/LiberationMono-Regular.ttf"
161
+ if not os.path.exists(font_path):
162
+ font_path = "/usr/share/fonts/truetype/noto/NotoSansMono-Regular.ttf"
163
+ if not os.path.exists(font_path):
164
+ font_path = "/usr/share/fonts/truetype/ubuntu/Ubuntu-C.ttf"
165
+ font = ImageFont.truetype(font_path, font_size)
166
+ except:
167
+ font = ImageFont.load_default()
168
+
169
+ w, h = image.size
170
+ new_w, new_h = w + left_margin, h + top_margin
171
+
172
+ # 创建背景画布并粘贴原图
173
+ mode = image.mode
174
+ bg = (255, 255, 255) if mode == "RGB" else (255,)
175
+ canvas = Image.new(mode, (new_w, new_h), bg)
176
+ canvas.paste(image, (left_margin, top_margin))
177
+
178
+ draw = ImageDraw.Draw(canvas)
179
+
180
+ # 计算文字宽高的 helper
181
+ def text_dimensions(txt):
182
+ bbox = draw.textbbox((0, 0), txt, font=font)
183
+ return bbox[2] - bbox[0], bbox[3] - bbox[1]
184
+
185
+ origin_width, origin_height = origin_size if origin_size else (w, h)
186
+
187
+ # 顶部刻度和文字
188
+ x_ticks = range(0, w + 1, spacing)
189
+ for i, x in enumerate(x_ticks):
190
+ # 计算刻度线的 x 坐标
191
+ px = left_margin + x
192
+ if i == len(x_ticks) - 1:
193
+ # 最后一个刻度线在右侧边界
194
+ px = new_w - tick_width
195
+ # 刻度线
196
+ draw.line([(px, top_margin), (px, top_margin - tick_length)], width=tick_width, fill=color)
197
+ # 文字
198
+ origin_x = x * origin_width // w # 将刻度值映射到原图尺寸
199
+ if i == len(x_ticks) - 1:
200
+ origin_x = origin_width # 确保最后一个刻度值是原图宽度
201
+ txt = str(origin_x)
202
+ tw, th = text_dimensions(txt)
203
+ tx = px - tw / 2
204
+ if i == len(x_ticks) - 1:
205
+ # 最后一个刻度的文字放在刻度线的左边
206
+ tx = tx - tw / 2
207
+ ty = top_margin - tick_length - th - text_offset
208
+ draw.text((tx, ty), txt, fill=color, font=font)
209
+
210
+ # 左侧刻度和文字
211
+ y_ticks = range(0, h + 1, spacing)
212
+ for i, y in enumerate(y_ticks):
213
+ # 计算刻度线的 y 坐标
214
+ py = top_margin + y
215
+ if i == len(y_ticks) - 1:
216
+ # 最后一个刻度线在底部边界
217
+ py = new_h - tick_width
218
+ # 刻度线
219
+ draw.line([(left_margin, py), (left_margin - tick_length, py)], width=tick_width, fill=color)
220
+ # 文字
221
+ origin_y = y * origin_height // h # 将刻度值映射到原图尺寸
222
+ if i == len(y_ticks) - 1:
223
+ origin_y = origin_height
224
+ txt = str(origin_y)
225
+ tw, th = text_dimensions(txt)
226
+ tx = left_margin - tick_length - tw - text_offset
227
+ ty = py - th / 2
228
+ if i == len(y_ticks) - 1:
229
+ # 最后一个刻度的文字放在刻度线的上边
230
+ ty = ty - th / 3 * 2
231
+ draw.text((tx, ty), txt, fill=color, font=font)
232
+
233
+ return canvas
234
+
235
+
236
+
237
+ def scale_to_fit_and_add_scale_bar(image: Image.Image, debug=False) -> Image.Image:
238
+ origin_width, origin_height = image.size
239
+ target_width, target_height = 512, 512
240
+ if debug:
241
+ logger.debug(f"原图尺寸: {origin_width}x{origin_height}, 目标尺寸: {target_width}x{target_height}")
242
+ image = scale_to_fit(image, target_size=(target_width, target_height)) # 缩放图片到目标大小,为了省 image tokens
243
+ if debug:
244
+ logger.debug(f"缩放后图片尺寸: {image.size[0]}x{image.size[1]}")
245
+ image = add_scale_bar(image, origin_size=(origin_width, origin_height)) # 保持缩放后的比例尺为原图的比例尺,方便模型在原图上定位坐标和长宽用于裁剪
246
+ if debug:
247
+ logger.debug(f"添加比例尺后图片尺寸: {image.size[0]}x{image.size[1]}")
248
+ return image
@@ -1,3 +1,4 @@
1
+ from collections import defaultdict
1
2
  import json
2
3
  from typing import *
3
4
 
@@ -6,8 +7,8 @@ from loguru import logger
6
7
  import pandas as pd
7
8
  import pyexcel
8
9
 
9
- from xlin.util import ls
10
- from xlin.xls2xlsx import is_xslx
10
+ from xlin.file_util import ls
11
+ from xlin.xlsx_util import is_xslx
11
12
 
12
13
 
13
14
  def dataframe_to_json_list(df: pd.DataFrame):
@@ -337,3 +338,15 @@ def generator_from_jsonl(path):
337
338
  jsonlist = load_json_list(path)
338
339
  for line in jsonlist:
339
340
  yield line
341
+
342
+ def grouped_row_in_jsonlist(jsonlist: List[Dict[str, Any]], key_col="query"):
343
+ grouped = defaultdict(list)
344
+ for i, row in enumerate(jsonlist):
345
+ if key_col not in row:
346
+ logger.warning(f"`{key_col}` not in row: {row}")
347
+ notfound_key = f"NotFound:{key_col}"
348
+ grouped[notfound_key].append(row)
349
+ continue
350
+ grouped[row[key_col]].append(row)
351
+ return grouped
352
+
@@ -9,9 +9,9 @@ from pathlib import Path
9
9
  from tqdm import tqdm
10
10
  from loguru import logger
11
11
 
12
- from xlin.jsonl import append_to_json_list, dataframe_to_json_list, load_json_list, row_to_json, save_json_list, load_json, save_json
13
- from xlin.read_as_dataframe import read_as_dataframe
14
- from xlin.util import ls
12
+ from xlin.jsonlist_util import append_to_json_list, dataframe_to_json_list, load_json_list, row_to_json, save_json_list, load_json, save_json
13
+ from xlin.dataframe_util import read_as_dataframe
14
+ from xlin.file_util import ls
15
15
 
16
16
 
17
17
  def element_mapping(
xlin/statistic.py CHANGED
@@ -1,10 +1,26 @@
1
1
  import sys
2
2
  from typing import List, Optional
3
- from collections import defaultdict
3
+ from collections import Counter, defaultdict
4
4
 
5
5
  import pandas as pd
6
6
 
7
7
 
8
+ def sortedCounter(obj, by="key", reverse=False, return_list=False):
9
+ c = Counter(obj)
10
+ c_list = [(k, c[k]) for k in c]
11
+ if by == "key":
12
+ c_list = sorted(c_list, key=lambda x: x[0], reverse=reverse)
13
+ elif by in ["value", "count"]:
14
+ c_list = sorted(c_list, key=lambda x: x[1], reverse=reverse)
15
+ else:
16
+ raise Exception(f"unsupported by: {by}")
17
+ c = Counter()
18
+ for k, v in c_list:
19
+ c[k] = v
20
+ if return_list:
21
+ return c, c_list
22
+ return c
23
+
8
24
 
9
25
  def bucket_count(length: List[int], step=50, skip_zero_count=False):
10
26
  grouped_count = []
xlin/text_util.py ADDED
@@ -0,0 +1,24 @@
1
+ def text_is_all_chinese(test: str):
2
+ for ch in test:
3
+ if '\u4e00' <= ch <= '\u9fff':
4
+ continue
5
+ return False
6
+ return True
7
+
8
+
9
+ def text_contains_chinese(test: str):
10
+ for ch in test:
11
+ if '\u4e00' <= ch <= '\u9fff':
12
+ return True
13
+ return False
14
+
15
+ def pretty_limited_text(text: str, limited_length: int = 300, language="zh"):
16
+ text = str(text).strip()
17
+ if len(text) > limited_length:
18
+ # if language == "zh":
19
+ # tail = f"...(共{len(text)}字)"
20
+ # else:
21
+ # tail = f"...({len(text)} words in total)"
22
+ # return text[: limited_length - len(tail)] + tail
23
+ return text[: limited_length // 2] + text[-limited_length // 2 :]
24
+ return text
@@ -41,3 +41,17 @@ class Timer:
41
41
  def __exit__(self, *args):
42
42
  self.end = time.perf_counter()
43
43
  self.interval = self.end - self.start
44
+
45
+ if __name__ == "__main__":
46
+ with Timer() as t:
47
+ time.sleep(1)
48
+ print(t.interval)
49
+ with Benchmark("Test Benchmark") as b:
50
+ time.sleep(1)
51
+ print(b.time)
52
+ @timing
53
+ def test_function(x, y):
54
+ time.sleep(1)
55
+ return x + y
56
+ result = test_function(1, 2)
57
+ print(f"Result of test_function: {result}")
@@ -0,0 +1,282 @@
1
+ Metadata-Version: 2.1
2
+ Name: xlin
3
+ Version: 0.2.2
4
+ Summary: toolbox for LinXueyuan
5
+ License: MIT
6
+ Author: LinXueyuanStdio
7
+ Author-email: 23211526+LinXueyuanStdio@users.noreply.github.com
8
+ Classifier: License :: OSI Approved :: MIT License
9
+ Classifier: Programming Language :: Python :: 2
10
+ Classifier: Programming Language :: Python :: 2.7
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Programming Language :: Python :: 3.4
13
+ Classifier: Programming Language :: Python :: 3.5
14
+ Classifier: Programming Language :: Python :: 3.6
15
+ Classifier: Programming Language :: Python :: 3.7
16
+ Classifier: Programming Language :: Python :: 3.8
17
+ Classifier: Programming Language :: Python :: 3.9
18
+ Classifier: Programming Language :: Python :: 3.10
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Requires-Dist: loguru
22
+ Requires-Dist: pandas
23
+ Requires-Dist: pyexcel
24
+ Requires-Dist: pyexcel-xls
25
+ Requires-Dist: pyexcel-xlsx
26
+ Requires-Dist: pyyaml
27
+ Requires-Dist: tqdm
28
+ Requires-Dist: xlsxwriter
29
+ Description-Content-Type: text/markdown
30
+
31
+ # xlin
32
+
33
+ Python 工具代码集合,提供了丰富的工具函数,涵盖文件操作、数据处理、多进程处理等多个方面,旨在提高开发效率。
34
+
35
+ ## 安装
36
+
37
+ ```bash
38
+ pip install xlin --upgrade
39
+ ```
40
+
41
+ ## 使用方法
42
+
43
+ ```python
44
+ from xlin import *
45
+ ```
46
+
47
+ ### 文件操作类:`ls`,`rm` 和 `cp`
48
+ - `ls`: 列出文件或文件夹下的所有文件。
49
+ - `rm`: 删除文件或文件夹。
50
+ - `cp`: 复制文件或文件夹。
51
+
52
+ ```python
53
+ from xlin import ls, rm, cp
54
+
55
+ dir_path = "./data"
56
+ dir_path = "/mnt/data.json"
57
+ dir_path = "./data,/mnt/data.json"
58
+ dir_path = ["./data", "/mnt/data.json", "./data,/mnt/data.json"]
59
+ def filter_func(path: Path) -> bool:
60
+ return path.name.endswith('.json')
61
+
62
+ filepaths: list[Path] = ls(dir_path, filter=filter_func)
63
+ rm(dir_path)
64
+ cp(dir_path, "./backup_data") # 会根据最大公共前缀保持文件夹结构
65
+ ```
66
+
67
+ ### 读取类
68
+
69
+ - `read_as_json_list`:读取 JSON 文件为列表。
70
+ - `read_as_dataframe`:读取文件为表格。如果是文件夹,则读取文件夹下的所有文件为表格并拼接。
71
+ - `read_as_dataframe_dict`:读取文件为字典,键为表头,值为列数据。
72
+ - `load_text`:加载文本文件。
73
+ - `load_yaml`:加载 YAML 文件。
74
+ - `load_json`:加载 JSON 文件。
75
+ - `load_json_list`:加载 JSON 列表文件。
76
+
77
+
78
+ > `read_as_**` 函数支持文件夹或者文件,支持多种文件格式,包括 Excel、CSV、JSON、Parquet 等。
79
+ >
80
+ > `load_**` 函数主要用于加载单个文件,支持文本、YAML 和 JSON 格式。
81
+
82
+ ```python
83
+ from xlin import *
84
+ import pandas as pd
85
+
86
+ dir_path = "./data"
87
+ dir_path = "./data,data.xlsx,data.csv,data.json,data.jsonl,data.parquet,data.feather,data.pkl,data.h5,data.txt,data.tsv,data.xml,data.html,data.db"
88
+ dir_path = "./data,/mnt/data.json"
89
+ dir_path = ["./data", "/mnt/data.json", "./data,/mnt/data.json"]
90
+ df_single = read_as_dataframe(dir_path)
91
+ jsonlist = read_as_json_list(dir_path)
92
+ df_dict = read_as_dataframe_dict(dir_path) # xlsx or dirs
93
+ for sheet_name, df in df_dict.items():
94
+ print(f"Sheet: {sheet_name}")
95
+ print(df)
96
+
97
+ text = load_text("example.txt")
98
+ yaml_data = load_yaml("example.yaml")
99
+ json_data = load_json("example.json")
100
+ json_list_data = load_json_list("example.jsonl")
101
+ ```
102
+
103
+ ### 保存类
104
+
105
+ ```python
106
+ save_json(data, 'output.json')
107
+ save_json_list(jsonlist, 'output.jsonl')
108
+ save_df(df, 'output.xlsx')
109
+ save_df_dict(df_dict, 'output.xlsx') # 将 read_as_dataframe_dict 返回的字典保存为 Excel 文件。
110
+ save_df_from_jsonlist(jsonlist, 'output_from_jsonlist.xlsx')
111
+ append_to_json_list(data, 'output.jsonl')
112
+ ```
113
+
114
+ ### 并行处理类:`xmap`
115
+ 高效处理 JSON 列表,支持多进程/多线程。
116
+
117
+ ```python
118
+ from xlin import xmap
119
+
120
+ jsonlist = [{"id": 1, "text": "Hello"}, {"id": 2, "text": "World"}]
121
+
122
+ def work_func(item):
123
+ item["text"] = item["text"].upper()
124
+ return item
125
+
126
+ results = xmap(jsonlist, work_func, output_path="output.jsonl", batch_size=2)
127
+ print(results)
128
+ ```
129
+
130
+ ### 合并多个文件:`merge_json_list`,`merge_multiple_df_dict`
131
+ 合并多个 JSONL 文件。
132
+
133
+ ```python
134
+ from xlin import merge_json_list
135
+
136
+ filenames = ['example1.jsonl', 'example2.jsonl']
137
+ output_filename = 'merged.jsonl'
138
+ merge_json_list(filenames, output_filename)
139
+ ```
140
+
141
+ 合并多个 `read_as_dataframe_dict` 返回的字典。
142
+
143
+ ```python
144
+ from xlin import read_as_dataframe_dict, merge_multiple_df_dict
145
+
146
+ df_dict1 = read_as_dataframe_dict('example1.xlsx')
147
+ df_dict2 = read_as_dataframe_dict('example2.xlsx')
148
+ merged_df_dict = merge_multiple_df_dict([df_dict1, df_dict2])
149
+ for sheet_name, df in merged_df_dict.items():
150
+ print(f"Sheet: {sheet_name}")
151
+ print(df)
152
+ ```
153
+
154
+ ### 对 json 文件批量操作
155
+ - 对 JSON 列表应用更改:`apply_changes_to_paths`,`apply_changes_to_jsonlist`
156
+
157
+ ```python
158
+ from xlin import *
159
+
160
+ paths = [Path('example1.jsonl'), Path('example2.jsonl')]
161
+ jsonlist = [{"id": 1, "text": "Hello"}, {"id": 2, "text": "World"}]
162
+
163
+ def change_func(row):
164
+ if row["id"] == 1:
165
+ row["text"] = "New Hello"
166
+ return "updated", row
167
+ return "unchanged", row
168
+
169
+ changes = {"update_text": change_func}
170
+
171
+ # 1. 对文件路径应用更改
172
+ apply_changes_to_paths(paths, changes, save=True)
173
+ # 2. 对 JSON 列表应用更改
174
+ new_jsonlist, updated, deleted = apply_changes_to_jsonlist(jsonlist, changes)
175
+ print(new_jsonlist)
176
+ ```
177
+
178
+ ### 生成器
179
+ - 从多个文件中生成 JSON 列表的生成器:`generator_from_paths`
180
+
181
+ ```python
182
+ from xlin import generator_from_paths
183
+ from pathlib import Path
184
+
185
+ paths = [Path('example1.jsonl'), Path('example2.jsonl')]
186
+
187
+ for path, row in generator_from_paths(paths):
188
+ print(f"Path: {path}, Row: {row}")
189
+ ```
190
+
191
+ ### 数据转换
192
+ - DataFrame 和 JSON 列表之间的转换:`dataframe_to_json_list` 和 `jsonlist_to_dataframe`
193
+
194
+ ```python
195
+ from xlin import dataframe_to_json_list, jsonlist_to_dataframe
196
+ import pandas as pd
197
+
198
+ data = {'col1': [1, 2], 'col2': [3, 4]}
199
+ df = pd.DataFrame(data)
200
+
201
+ json_list = dataframe_to_json_list(df)
202
+ print(json_list)
203
+
204
+ new_df = jsonlist_to_dataframe(json_list)
205
+ print(new_df)
206
+ ```
207
+
208
+ ### 分组
209
+ - 对 DataFrame 进行分组:`grouped_col_list`、`grouped_col` 和 `grouped_row`
210
+
211
+ ```python
212
+ from xlin import grouped_col_list, grouped_col, grouped_row
213
+ import pandas as pd
214
+
215
+ data = {'query': ['a', 'a', 'b'], 'output': [1, 2, 3]}
216
+ df = pd.DataFrame(data)
217
+
218
+ grouped_col_list_result = grouped_col_list(df)
219
+ print(grouped_col_list_result)
220
+
221
+ grouped_col_result = grouped_col(df)
222
+ print(grouped_col_result)
223
+
224
+ grouped_row_result = grouped_row(df)
225
+ print(grouped_row_result)
226
+ ```
227
+
228
+ - 对 JSON 列表进行分组:`grouped_row_in_jsonlist`
229
+
230
+ ```python
231
+ from xlin import grouped_row_in_jsonlist
232
+
233
+ jsonlist = [{"query": "a", "output": 1}, {"query": "a", "output": 2}, {"query": "b", "output": 3}]
234
+ grouped_row_in_jsonlist_result = grouped_row_in_jsonlist(jsonlist)
235
+ print(grouped_row_in_jsonlist_result)
236
+ ```
237
+
238
+ ### 工具类
239
+
240
+ - `random_timestamp` 和 `random_timestamp_str`:生成随机时间戳和格式化的随机时间字符串。
241
+
242
+ ```python
243
+ from xlin import random_timestamp, random_timestamp_str
244
+
245
+ timestamp = random_timestamp()
246
+ print(timestamp)
247
+
248
+ timestamp_str = random_timestamp_str()
249
+ print(timestamp_str)
250
+ ```
251
+
252
+
253
+ - `df_dict_summary`: 对 `read_as_dataframe_dict` 返回的字典进行总结,返回一个 DataFrame 包含每个表的基本信息。
254
+
255
+ ```python
256
+ from xlin import read_as_dataframe_dict, df_dict_summary
257
+
258
+ df_dict = read_as_dataframe_dict('example.xlsx')
259
+ summary = df_dict_summary(df_dict)
260
+ print(summary)
261
+ ```
262
+
263
+ - `text_is_all_chinese` 和 `text_contains_chinese`:判断文本是否全为中文或是否包含中文。
264
+
265
+ ```python
266
+ from xlin import text_is_all_chinese, text_contains_chinese
267
+
268
+ text1 = "你好"
269
+ text2 = "Hello 你好"
270
+
271
+ print(text_is_all_chinese(text1)) # True
272
+ print(text_is_all_chinese(text2)) # False
273
+ print(text_contains_chinese(text2)) # True
274
+ ```
275
+
276
+ ## 许可证
277
+
278
+ 本项目采用 MIT 许可证,详情请参阅 [LICENSE](LICENSE) 文件。
279
+
280
+ ## 作者
281
+
282
+ - LinXueyuanStdio <23211526+LinXueyuanStdio@users.noreply.github.com>
@@ -0,0 +1,17 @@
1
+ xlin/__init__.py,sha256=CIhMAGhFgqwC6w16MzKcwo2mDjmaRUAcrlZFR3Am--I,321
2
+ xlin/dataframe_util.py,sha256=zWpkGN-C9V9qVAVH8K4ElkPVu9pq4MjDbxwjJKSOO2o,12151
3
+ xlin/datetime_util.py,sha256=MHi827LBuAOX6SSMb31staNBjmtnNOXwg7JDk73_pLU,6212
4
+ xlin/file_util.py,sha256=mYTABNywdYoSfh1RLJcH7l1FzgKTFWN2-JZMFzv-ehw,7270
5
+ xlin/image_util.py,sha256=hSNQ5suCrxFXpQwP-wfUT1ig3SfEdC6msuVp2k7J7b8,8438
6
+ xlin/jsonlist_util.py,sha256=dLgrgrSTvg_1plVRCEnilajPM_s3vYdVx2bCTqrZAN8,11316
7
+ xlin/metric.py,sha256=N7wJ35y-C-IaBr1I1CJ_37lTG7gA69zmn9Xg6xSwKoI,1690
8
+ xlin/multiprocess_util.py,sha256=-tskCWQlBBCOPycXLj9Y2MugYg-tHF_QYYWW7c1ixOk,17300
9
+ xlin/statistic.py,sha256=ioJJjL4qwHiwNPeBFBB67keoAIbB-uZM51zkDYviar0,17037
10
+ xlin/text_util.py,sha256=ejFD8-j8tLCbPlCPFg0Tu3MEMPEpF7R5_IpXXjl6qzA,735
11
+ xlin/timing_util.py,sha256=nNVKtSXel-Cc8SF_BqPRNkyNDOjGqOMxTol-L1vpON4,1340
12
+ xlin/xlsx_util.py,sha256=uSmXcDvIhi5Sq0LGidMXy0wErNBXdjaoa6EftYVjTXs,947
13
+ xlin/yaml_util.py,sha256=kICi7G3Td5q2MaSXXt85qNTWoHMgjzt7pvn7r3C4dME,183
14
+ xlin-0.2.2.dist-info/LICENSE,sha256=60ys6rRtc1dZOP8UjSUr9fAqhZudT3WpKe5WbMCralM,1066
15
+ xlin-0.2.2.dist-info/METADATA,sha256=gEhtB67hCGkiiEtGPSW9PbKXh2B2lEOoU1JWegGY1U8,7991
16
+ xlin-0.2.2.dist-info/WHEEL,sha256=IrRNNNJ-uuL1ggO5qMvT1GGhQVdQU54d6ZpYqEZfEWo,92
17
+ xlin-0.2.2.dist-info/RECORD,,
xlin/ischinese.py DELETED
@@ -1,13 +0,0 @@
1
- def text_is_all_chinese(test: str):
2
- for ch in test:
3
- if '\u4e00' <= ch <= '\u9fff':
4
- continue
5
- return False
6
- return True
7
-
8
-
9
- def text_contains_chinese(test: str):
10
- for ch in test:
11
- if '\u4e00' <= ch <= '\u9fff':
12
- return True
13
- return False
@@ -1,33 +0,0 @@
1
- Metadata-Version: 2.1
2
- Name: xlin
3
- Version: 0.1.38
4
- Summary: toolbox for LinXueyuan
5
- License: MIT
6
- Author: LinXueyuanStdio
7
- Author-email: 23211526+LinXueyuanStdio@users.noreply.github.com
8
- Classifier: License :: OSI Approved :: MIT License
9
- Classifier: Programming Language :: Python :: 2
10
- Classifier: Programming Language :: Python :: 2.7
11
- Classifier: Programming Language :: Python :: 3
12
- Classifier: Programming Language :: Python :: 3.4
13
- Classifier: Programming Language :: Python :: 3.5
14
- Classifier: Programming Language :: Python :: 3.6
15
- Classifier: Programming Language :: Python :: 3.7
16
- Classifier: Programming Language :: Python :: 3.8
17
- Classifier: Programming Language :: Python :: 3.9
18
- Classifier: Programming Language :: Python :: 3.10
19
- Classifier: Programming Language :: Python :: 3.11
20
- Classifier: Programming Language :: Python :: 3.12
21
- Requires-Dist: loguru
22
- Requires-Dist: pandas
23
- Requires-Dist: pyexcel
24
- Requires-Dist: pyexcel-xls
25
- Requires-Dist: pyexcel-xlsx
26
- Requires-Dist: pyyaml
27
- Requires-Dist: tqdm
28
- Requires-Dist: xlsxwriter
29
- Description-Content-Type: text/markdown
30
-
31
- # xlin
32
- 个人 python 工具代码
33
-
@@ -1,15 +0,0 @@
1
- xlin/__init__.py,sha256=MWWCNPgJFS_oV2US52ULa4yg4Ku61qjn40NVKqcp9-c,248
2
- xlin/ischinese.py,sha256=Ia9IMQ6q-UHkdLwqS70L1fTnfSPbluFrv_I1UqsKquo,293
3
- xlin/jsonl.py,sha256=QLIipsORyMC5OlTW5yntNnXS1aZ4so984yT_c0elM80,10854
4
- xlin/metric.py,sha256=N7wJ35y-C-IaBr1I1CJ_37lTG7gA69zmn9Xg6xSwKoI,1690
5
- xlin/multiprocess_mapping.py,sha256=XZJLsYRHyNooeBFngnSZ6l_YhbK0xjbN1_nK8GonmkE,17290
6
- xlin/read_as_dataframe.py,sha256=ufTH1z-ewdE4X33trXQDWgvsjCE18hzRxSFEvoH7Eaw,9173
7
- xlin/statistic.py,sha256=nwFSN8BWfTQRimI-zfp6RwfA-I9aFDbemtV2cyh6Hq8,16533
8
- xlin/timing.py,sha256=XMT8dMcMolOMohDvAZOIM_BAiPMREhGQKnO1kc5s6PU,998
9
- xlin/util.py,sha256=HEDJv09tNmvHCgQdP4uhMkDM8fQgcuYa0MuMXZmyZns,10977
10
- xlin/xls2xlsx.py,sha256=uSmXcDvIhi5Sq0LGidMXy0wErNBXdjaoa6EftYVjTXs,947
11
- xlin/yaml.py,sha256=kICi7G3Td5q2MaSXXt85qNTWoHMgjzt7pvn7r3C4dME,183
12
- xlin-0.1.38.dist-info/LICENSE,sha256=60ys6rRtc1dZOP8UjSUr9fAqhZudT3WpKe5WbMCralM,1066
13
- xlin-0.1.38.dist-info/METADATA,sha256=1yJfCyje0O72bLoSxXYr2NMqThAz5pNrAhD-x9DOrJw,1098
14
- xlin-0.1.38.dist-info/WHEEL,sha256=IrRNNNJ-uuL1ggO5qMvT1GGhQVdQU54d6ZpYqEZfEWo,92
15
- xlin-0.1.38.dist-info/RECORD,,
File without changes
File without changes
File without changes
File without changes