tpltable 0.3.2__tar.gz → 0.3.4__tar.gz

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,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: tpltable
3
- Version: 0.3.2
3
+ Version: 0.3.4
4
4
  Summary: define "Excel" "Table|StyleTable" "Row|Col|StyleRow|StyleCol". Can dynamic modify excel data & styles. Support multiple kinds of indexes. (70%)
5
5
  Author-email: 2229066748@qq.com
6
6
  Maintainer: Eagle'sBaby
@@ -5,7 +5,7 @@ from setuptools import find_packages, setup
5
5
 
6
6
  setup(
7
7
  name='tpltable',
8
- version='0.3.2',
8
+ version='0.3.4',
9
9
  description='define "Excel" "Table|StyleTable" "Row|Col|StyleRow|StyleCol". Can dynamic modify excel data & styles. Support multiple kinds of indexes. (70%)',
10
10
  author_email='2229066748@qq.com',
11
11
  maintainer="Eagle'sBaby",
@@ -19,7 +19,8 @@ from openpyxl.styles import Font, Color, Alignment, Border, Side, PatternFill
19
19
  NaN = pd.NA
20
20
  nan = pd.NA
21
21
 
22
- isnan = pd.isna
22
+ def isnan(value):
23
+ return pd.isna(value) or str(value) == 'nan'
23
24
 
24
25
  # NOTE: Colorama Start -------------------------------------------------------------------------->
25
26
  # 初始化colorama
@@ -72,6 +73,11 @@ COLORAMA_STYLES = {
72
73
 
73
74
  __PAT_COORD = re.compile("([A-Z]+)(\d+)")
74
75
 
76
+
77
+ # 模糊的行列索引混用警告
78
+ class AmbiguousIndexWarning(UserWarning):
79
+ ...
80
+
75
81
  class Coord:
76
82
  def __init__(self, iloc_c, iloc_r):
77
83
  self._r:int = iloc_r
@@ -142,7 +148,7 @@ def index_from_string(letters:str, *, only_int:bool=False, should:str=None) -> U
142
148
  if letters.isdigit():
143
149
  if should == 's':
144
150
  warnings.warn(
145
- f"Here should be a letter, but got '{letters}'. Regarded as '{get_column_letter(int(letters))}'."
151
+ AmbiguousIndexWarning(f"Here should be a letter (Col-Index), but got '{letters}' (Row-Index). Regarded as '{get_column_letter(int(letters))}'."),
146
152
  )
147
153
  return int(letters)
148
154
  _s = __PAT_COORD.match(letters)
@@ -153,7 +159,7 @@ def index_from_string(letters:str, *, only_int:bool=False, should:str=None) -> U
153
159
  return Coord(index_from_string(_c) - 1, int(_r) - 1)
154
160
  if should == 'i':
155
161
  warnings.warn(
156
- f"Here should be an digit, but got '{letters}'. Regarded as '{_column_index_from_string(letters)}'."
162
+ AmbiguousIndexWarning(f"Here should be an digit (Row-Index), but got '{letters}' (Col-Index). Regarded as '{_column_index_from_string(letters)}'.")
157
163
  )
158
164
  return _column_index_from_string(letters)
159
165
 
@@ -0,0 +1,12 @@
1
+ from tpltable.basic import *
2
+
3
+
4
+ class UnitCell:
5
+ def __init__(self, value, style):
6
+ self.value = value
7
+ self.style = style
8
+
9
+ def __str__(self):
10
+ return str(self.value)
11
+
12
+
@@ -342,6 +342,7 @@ class ExcelFileIOException(Exception):
342
342
 
343
343
 
344
344
  class Excel(ListkeysDict):
345
+ _DEFAULT_PREFIX = 'Sheet'
345
346
  def __init__(self, fpath: str = None, *, style: bool = True):
346
347
  """
347
348
  *Create an Excel object from an excel file or empty
@@ -77,6 +77,24 @@ COLOR_MAGENTA = '00FF00FF'
77
77
  COLOR_DARKMAGENTA = '00800080'
78
78
  COLOR_LIGHTMAGENTA = '00FFC0CB'
79
79
 
80
+ SIDE_THIN_BLACK = Side(style='thin', color=COLOR_BLACK)
81
+
82
+ BORDER_LTRB = Border(SIDE_THIN_BLACK, SIDE_THIN_BLACK, SIDE_THIN_BLACK, SIDE_THIN_BLACK)
83
+ BORDER_NOLEFT = Border(None, SIDE_THIN_BLACK, SIDE_THIN_BLACK, SIDE_THIN_BLACK)
84
+ BORDER_NORIGHT = Border(SIDE_THIN_BLACK, None, SIDE_THIN_BLACK, SIDE_THIN_BLACK)
85
+ BORDER_NOTOP = Border(SIDE_THIN_BLACK, SIDE_THIN_BLACK, None, SIDE_THIN_BLACK)
86
+ BORDER_NOBOTTOM = Border(SIDE_THIN_BLACK, SIDE_THIN_BLACK, SIDE_THIN_BLACK, None)
87
+ BORDER_LT = Border(SIDE_THIN_BLACK, SIDE_THIN_BLACK, None, None)
88
+ BORDER_RB = Border(None, None, SIDE_THIN_BLACK, SIDE_THIN_BLACK)
89
+ BORDER_LB = Border(SIDE_THIN_BLACK, None, None, SIDE_THIN_BLACK)
90
+ BORDER_RT = Border(None, SIDE_THIN_BLACK, SIDE_THIN_BLACK, None)
91
+ BORDER_LR = Border(SIDE_THIN_BLACK, SIDE_THIN_BLACK, None, None)
92
+ BORDER_TB = Border(None, None, SIDE_THIN_BLACK, SIDE_THIN_BLACK)
93
+ BORDER_L = Border(SIDE_THIN_BLACK, None, None, None)
94
+ BORDER_R = Border(None, SIDE_THIN_BLACK, None, None)
95
+ BORDER_T = Border(None, None, SIDE_THIN_BLACK, None)
96
+ BORDER_B = Border(None, None, None, SIDE_THIN_BLACK)
97
+
80
98
 
81
99
  def _GetCellStyle(ws, cell, style_type: str) -> object:
82
100
  if style_type == TYPE_COLOR:
@@ -241,11 +259,11 @@ class Style:
241
259
  # border_style: Incomplete | None = None,
242
260
  _l, _r, _t, _b, _diag = _bd.left, _bd.right, _bd.top, _bd.bottom, _bd.diagonal
243
261
  return {
244
- 'left': {'style': _l.style, 'color': _l.color, 'border_style': _l.border_style},
245
- 'right': {'style': _r.style, 'color': _r.color, 'border_style': _r.border_style},
246
- 'top': {'style': _t.style, 'color': _t.color, 'border_style': _t.border_style},
247
- 'bottom': {'style': _b.style, 'color': _b.color, 'border_style': _b.border_style},
248
- 'diagonal': {'style': _diag.style, 'color': _diag.color, 'border_style': _diag.border_style},
262
+ 'left': {'style': _l.style, 'color': _l.color, 'border_style': _l.border_style} if _l is not None else None,
263
+ 'right': {'style': _r.style, 'color': _r.color, 'border_style': _r.border_style} if _r is not None else None,
264
+ 'top': {'style': _t.style, 'color': _t.color, 'border_style': _t.border_style} if _t is not None else None,
265
+ 'bottom': {'style': _b.style, 'color': _b.color, 'border_style': _b.border_style} if _b is not None else None,
266
+ 'diagonal': {'style': _diag.style, 'color': _diag.color, 'border_style': _diag.border_style} if _diag is not None else None,
249
267
  'diagonal_direction': _bd.diagonal_direction,
250
268
  'vertical': _bd.vertical, 'horizontal': _bd.horizontal,
251
269
  'diagonalUp': _bd.diagonalUp, 'diagonalDown': _bd.diagonalDown,
@@ -301,6 +319,9 @@ class Style:
301
319
  self[key] = value
302
320
 
303
321
 
322
+
323
+
324
+
304
325
  def _raw_style() -> Style:
305
326
  wb = Workbook()
306
327
  ws = wb.active
@@ -4,13 +4,99 @@ import warnings
4
4
  import numpy as np
5
5
  import pandas as pd
6
6
  from tpltable.core import *
7
- from tpltable.style import Style
7
+ from tpltable.style import Style, TYPE_BORDER
8
8
 
9
9
  _PAT_COORD = re.compile(r"([A-Z]+)(\d+)")
10
+
11
+
10
12
  class EMPTY_INDEX:
11
13
  pass
12
14
 
13
- class _ExcelIndexsLike_1DArray:
15
+
16
+ class _ExcelIndexsLike_1DArray_Def:
17
+
18
+ def _reindexself(self) -> None:
19
+ """
20
+ Reindex the Series
21
+ * Return None. Modify self._sr inplace.
22
+ :return:
23
+ """
24
+ raise NotImplementedError
25
+
26
+ def _reconstruct(self, other) -> None:
27
+ """
28
+ Reconstruct self
29
+ * Return None. Modify self._sr inplace.
30
+ :param other: inst of __class__. The other instance.
31
+ :return:
32
+ """
33
+ raise NotImplementedError
34
+
35
+ def _construct_new_from_index(self, new_index: Union[int, slice], *, inplace=False) -> '__class__() | None':
36
+ """
37
+ Construct a new __class__ from the new index
38
+ * Use this instead of directly create a new __class__ object. So you can override this method to add some other logic.
39
+ :param new_index: int|slice. The new index you want to index self
40
+ *
41
+ :param inplace: bool. If True, modify self inplace. If True, Return None.
42
+ :return: inst of __class__ | None
43
+ """
44
+ raise NotImplementedError
45
+
46
+ def _construct_new_from_insert(self, insert_index: int, *values, inplace=False) -> '__class__() | None':
47
+ """
48
+ Construct a new __class__ from the new index
49
+ * Use this instead of directly create a new __class__ object. So you can override this method to add some other logic.
50
+ :param insert_index: int. The index to insert before.
51
+ :param *values: Any. The values to insert.
52
+ :param inplace:
53
+ :return: inst of __class__ | None
54
+ """
55
+ raise NotImplementedError
56
+
57
+ def _construct_new_from_delete(self, delete_index: Union[int, slice], *, inplace=False) -> '__class__() | None':
58
+ """
59
+ Construct a new __class__ from the new index
60
+ * Use this instead of directly create a new __class__ object. So you can override this method to add some other logic.
61
+ :param delete_index: int|slice. The index to delete.
62
+ *
63
+ :param inplace: bool. If True, modify self inplace. If True, Return None.
64
+ :return: inst of __class__ | None
65
+ """
66
+ raise NotImplementedError
67
+
68
+ def on_subinstance(self, construction: object) -> '__class__()':
69
+ """
70
+ Hook function called when a subinstance is created.
71
+ * Return the new instance. So you can modify the new instance.
72
+ * When this method is called, self is not modified.
73
+ :param construction: inst of __class__. The subinstance created.
74
+ :return: inst of __class__ | None
75
+ """
76
+ return construction
77
+
78
+ def on_insert(self, construction: object) -> '__class__()':
79
+ """
80
+ Hook function called when a subinstance is created.
81
+ * Return the new instance. So you can modify the new instance.
82
+ * When this method is called, self is not modified.
83
+ :param construction: inst of __class__. The subinstance created.
84
+ :return: inst of __class__
85
+ """
86
+ return construction
87
+
88
+ def on_delete(self, construction: object) -> '__class__()':
89
+ """
90
+ Hook function called when a subinstance is created.
91
+ * Return the new instance. So you can modify the new instance.
92
+ * When this method is called, self is not modified.
93
+ :param construction: inst of __class__. The subinstance created.
94
+ :return: inst of __class__
95
+ """
96
+ return construction
97
+
98
+
99
+ class _ExcelIndexsLike_1DArray_Base(_ExcelIndexsLike_1DArray_Def):
14
100
  _CLS_DEFAULT_INDEX_TYPE = None # "i" or "s" or None
15
101
 
16
102
  # None: integer index, start with 0 and label'0'
@@ -18,37 +104,119 @@ class _ExcelIndexsLike_1DArray:
18
104
  # "s": letter index, start with 0 and label:'A'
19
105
  def __init__(self, _from=None, *, copy: bool = True):
20
106
  self._sr: pd.Series = None
21
- if isinstance(_from, pd.Series):
22
- self._sr = _from
23
- if copy:
24
- self._sr = self._sr.copy()
107
+
108
+ if isinstance(_from, list):
109
+ try:
110
+ _from = np.array(_from)
111
+ except Exception as e:
112
+ raise ValueError(f"Failed to convert list to 1D array. {e}")
113
+
114
+ if _from is None:
115
+ self._sr = pd.Series()
25
116
  elif isinstance(_from, np.ndarray):
26
117
  # check shape
27
118
  s = _from.shape
28
119
  if np.ndim(_from) != 1:
29
120
  raise ValueError(f"Only 1D array is supported. But got {np.ndim(_from)}D array.(shape={s})")
30
121
  self._sr = pd.Series(_from)
31
-
32
- elif isinstance(_from, list):
33
- try:
34
- arr = np.array(_from)
35
- s = arr.shape
36
- if np.ndim(arr) != 1:
37
- raise ValueError(f"Only 1D array is supported. But got {np.ndim(arr)}D array from list.(shape={s})")
38
- self._sr = pd.Series(arr)
39
- except Exception as e:
40
- raise ValueError(f"Failed to convert list to 1D array. {e}")
122
+ elif isinstance(_from, pd.Series):
123
+ self._sr = _from
124
+ if copy:
125
+ self._sr = self._sr.copy()
126
+ elif isinstance(_from, _ExcelIndexsLike_1DArray_Base):
127
+ self._sr = _from._sr.copy()
41
128
  else:
42
129
  raise ValueError(f"Unsupported type: {type(_from)}")
43
130
 
44
- self._reindex()
131
+ self._reindexself()
45
132
 
46
- def _reindex(self):
133
+ def _reindexself(self):
134
+ """
135
+ Reindex the Series
136
+ * Return None. Modify self._sr inplace.
137
+ :return:
138
+ """
47
139
  if self._CLS_DEFAULT_INDEX_TYPE == "s":
48
140
  self._sr.index = [get_column_letter(i + 1) for i in range(self.size)]
49
141
  elif self._CLS_DEFAULT_INDEX_TYPE == "i":
50
142
  self._sr.index = [i + 1 for i in range(self.size)]
51
143
 
144
+ def _reconstruct(self, other):
145
+ """
146
+ Reconstruct self
147
+ * Return None. Modify self._sr inplace.
148
+ :param other: inst of __class__. The other instance.
149
+ :return:
150
+ """
151
+ self._sr = other._sr.copy()
152
+ self._reindexself()
153
+
154
+ def _construct_new_from_index(self, new_index: Union[int, slice], *, inplace=False) -> '__class__() | None':
155
+ """
156
+ Construct a new __class__ from the new index
157
+ * Use this instead of directly create a new __class__ object. So you can override this method to add some other logic.
158
+ :param new_index: int|slice. The new index you want to index self
159
+ *
160
+ :param inplace: bool. If True, modify self inplace. If True, Return None.
161
+ :return: inst of __class__ | None
162
+ """
163
+ _new = self.__class__(self._sr.iloc[new_index], copy=False)
164
+ _new = self.on_subinstance(_new)
165
+ if inplace:
166
+ self._reconstruct(_new)
167
+ else:
168
+ return _new
169
+
170
+ def _construct_new_from_insert(self, insert_index: int, *values, inplace=False) -> '__class__() | None':
171
+ """
172
+ Construct a new __class__ from the new index
173
+ * Use this instead of directly create a new __class__ object. So you can override this method to add some other logic.
174
+ :param insert_index: int. The index to insert before.
175
+ :param *values: Any. The values to insert.
176
+ :param inplace:
177
+ :return: inst of __class__ | None
178
+ """
179
+ _construct = self._sr.to_list()
180
+ values = list(values)
181
+ _construct = _construct[:insert_index] + values + _construct[insert_index:]
182
+ _new = self.__class__(_construct, copy=False)
183
+ _new = self.on_insert(_new)
184
+ if inplace:
185
+ self._reconstruct(_new)
186
+ else:
187
+ return _new
188
+
189
+ def _construct_new_from_delete(self, delete_index: Union[int, slice], *, inplace=False) -> '__class__() | None':
190
+ """
191
+ Construct a new __class__ from the new index
192
+ * Use this instead of directly create a new __class__ object. So you can override this method to add some other logic.
193
+ :param delete_index: int|slice. The index to delete. Special case: slice(None) means clear all.
194
+ *
195
+ :param inplace: bool. If True, modify self inplace. If True, Return None.
196
+ :return: inst of __class__ | None
197
+ """
198
+ if delete_index == slice(None):
199
+ _new = self.__class__()
200
+ else:
201
+ _new = self.__class__(self._sr.drop(self._sr.index[delete_index]), copy=False)
202
+ _new = self.on_delete(_new)
203
+
204
+ if inplace:
205
+ self._reconstruct(_new)
206
+ else:
207
+ return _new
208
+
209
+ def _parse_slice(self, key: slice) -> Tuple[int, int, int]:
210
+ """
211
+ Parse the slice to get the start, stop and step
212
+ :param key: slice. The slice to parse.
213
+ :return: start, stop, step
214
+ """
215
+ _start = key.start if key.start is not None else 0
216
+ _stop = key.stop if key.stop is not None else self.size
217
+ _step = key.step if key.step is not None else 1
218
+ return _start, _stop, _step
219
+
52
220
  def __str__(self):
53
221
  txt = str(self._sr)
54
222
 
@@ -61,6 +229,9 @@ class _ExcelIndexsLike_1DArray:
61
229
 
62
230
  return txt
63
231
 
232
+ def __len__(self):
233
+ return self.size
234
+
64
235
  @property
65
236
  def size(self):
66
237
  return self._sr.shape[0]
@@ -80,10 +251,12 @@ class _ExcelIndexsLike_1DArray:
80
251
  :return: type, real_index
81
252
  * type:
82
253
  ' ': 简单索引类型
254
+ 'n': 全选类型
83
255
  "s": 文本ABC索引类型
84
256
  "i": 文本123索引类型
85
257
  "t??": slice类型, ? 可以是 ' nis'中的一个
86
258
  """
259
+ skey = skey.upper()
87
260
  # NOTE: index_from_string will thorw warning if the type is not matched
88
261
  # 先考虑是否是slice
89
262
  if ':' in skey:
@@ -94,20 +267,22 @@ class _ExcelIndexsLike_1DArray:
94
267
  _t1 = 'i' if l1.isdigit() else 's'
95
268
 
96
269
  start, end = index_from_string(l0, should=self._CLS_DEFAULT_INDEX_TYPE, only_int=True), index_from_string(l1, should=self._CLS_DEFAULT_INDEX_TYPE, only_int=True)
97
- return f"t{_t0}{_t1}", slice(start, end + 1)
270
+ return f"t{_t0}{_t1}", slice(start, end + 1) if start <= end else slice(end, start + 1) # NOTE: 'A:C' with 'C:A' is the same
98
271
 
99
272
  if _PAT_COORD.match(skey):
100
273
  raise ValueError(f"Unexpected index: {skey}.")
101
274
  _type = 'i' if skey.isdigit() else 's'
102
275
  return _type, index_from_string(skey, should=self._CLS_DEFAULT_INDEX_TYPE, only_int=True) - 1
103
276
 
104
- def __dim1_parse_index_key(self, key) -> Tuple[U[str, None], U[int, slice]]:
277
+ def _dim1_parse_index_key(self, key, *, err_when_slice=False) -> Tuple[U[str, None], U[int, slice]]:
105
278
  """
106
279
  Parse the key to get the type and index
107
280
  :param key:
281
+ :param err_when_slice: bool. If True, raise error when slice is found.
108
282
  :return: type, index
109
283
  * type:
110
284
  ' ': 简单索引类型
285
+ 'n': 全选类型
111
286
  "s": 文本ABC索引类型
112
287
  "i": 文本123索引类型
113
288
  "t??": slice类型, ? 可以是 ' nis'中的一个
@@ -121,53 +296,47 @@ class _ExcelIndexsLike_1DArray:
121
296
  # NOTE:交给专用于处理字符串索引的函数处理
122
297
  return self.__dim1_parse_str_index(key)
123
298
  elif isinstance(key, slice):
299
+ if err_when_slice:
300
+ raise ValueError(f"Unexpected slice index: {key}. (Because param err_when_slice=True).")
124
301
  _a, _b, _step = key.start, key.stop, key.step
302
+ _atype, _aindex, _btype, _bindex = 'n', None, 'n', None
125
303
  if _a is None and _b is None:
126
304
  return "tnn", key
127
- _atype, _aindex, _btype, _bindex, _any_flag = 'n', None, 'n', None, False
128
305
  if _a is not None:
129
- _atype, _aindex = self.__dim1_parse_index_key(_a)
130
- _any_flag = True
306
+ _atype, _aindex = self._dim1_parse_index_key(_a)
131
307
  if _b is not None:
132
- _btype, _bindex = self.__dim1_parse_index_key(_b)
133
- _any_flag = True
134
- assert _atype[0] != 't' and _btype[0] != 't', f"Unexpected slice index: {key}"
135
- assert not _any_flag or _atype == _btype, f"Unexpected index: '{key}'. Which is not in the same type."
308
+ _btype, _bindex = self._dim1_parse_index_key(_b)
309
+ if _atype != _btype:
310
+ raise ValueError(f"Unexpected index: '{key}'. Which is not in the same type.")
311
+
136
312
  return f"t{_atype}{_btype}", slice(_aindex, _bindex, _step)
137
313
  else:
138
314
  raise ValueError(f"Unsupported type: {type(key)}")
139
315
 
140
316
  def __getitem__(self, key):
141
- """
142
-
143
- :param key:
144
- only unit types:
145
- int -> value
146
- alpha -> value
147
- slice -> sub Series (new CLS)
148
- :return:
149
- """
150
- _stype, _index = self.__dim1_parse_index_key(key)
317
+ _stype, _index = self._dim1_parse_index_key(key)
151
318
  if _stype == 'n':
152
- return self
153
- elif _stype[0] != 't':
319
+ return self.copy() # None or ... Type
320
+ elif _stype[0] != 't': # return single value directly
154
321
  return self._sr.iloc[_index]
155
322
  else:
156
- return self.__class__(self._sr.iloc[_index], copy=False)
323
+ return self._construct_new_from_index(_index) # slice Type
157
324
 
158
325
  def __setitem__(self, key, value):
159
- _stype, _index = self.__dim1_parse_index_key(key)
326
+ _stype, _index = self._dim1_parse_index_key(key)
160
327
  if _stype == 'n':
161
- _index = slice()
162
- self._sr.iloc[_index] = value
328
+ _index = slice(None)
329
+ self._sr.iloc[_index] = value # NOTE: Set value won't change the shape. So no need to construct new one.
163
330
 
164
331
  def __delitem__(self, key):
165
- _stype, _index = self.__dim1_parse_index_key(key)
332
+ _stype, _index = self._dim1_parse_index_key(key)
166
333
  if _stype == 'n':
167
334
  self.clear()
168
335
  else:
169
- self._sr.drop(self._sr.index[_index], inplace=True)
170
- self._reindex()
336
+ self._construct_new_from_delete(_index, inplace=True)
337
+
338
+
339
+ class ExcelIndexsLike_1DArray(_ExcelIndexsLike_1DArray_Base):
171
340
 
172
341
  def append(self, *values):
173
342
  """
@@ -175,33 +344,30 @@ class _ExcelIndexsLike_1DArray:
175
344
  :param values: Any. The values to append.
176
345
  :return: None
177
346
  """
178
- self._sr = self._sr._append(pd.Series(values), ignore_index=True)
179
- self._reindex()
347
+ self._construct_new_from_insert(self.size, *values, inplace=True)
180
348
 
181
- def insert(self, index, *values):
349
+ def insert(self, index: Union[str, int], *values):
182
350
  """
183
351
  Insert values before the index
184
- :param index: int|str. The index to insert before. (Can be a column-letter string)
352
+ :param index: int|str. The index to insert before. (Can be a column-letter string) But not support slice.
185
353
  :param values: Any. The values to insert.
186
354
  :return: None
187
355
  """
188
- _stype, _index = self.__dim1_parse_index_key(index)
356
+ _stype, _index = self._dim1_parse_index_key(index)
189
357
  if _stype[0] == "t" or _stype == "n":
190
358
  raise ValueError(f"Here only accept non-slice index In {self.__class__.__name__}. But got {index}")
191
359
 
192
- _construct_list = self._sr.iloc[:_index].to_list() + list(values) + self._sr.iloc[_index:].to_list()
193
- self._sr = pd.Series(_construct_list)
194
- self._reindex()
360
+ self._construct_new_from_insert(_index, *values, inplace=True)
195
361
 
196
- def pop(self, index, n: int = 1):
362
+ def pop(self, index: Union[str, int], n: int = 1):
197
363
  """
198
364
  Remove and return the value at the index
199
- :param index: int|str. The index to remove. (Can be a column-letter string)
365
+ :param index: int|str. The index to remove. (Can be a column-letter string) But not support slice.
200
366
  :param n: int. The number of values to remove.
201
367
  - if index + n > size, remove until the end.
202
368
  :return: Any. The removed value. (If n > 1, return a list of values)
203
369
  """
204
- _stype, _index = self.__dim1_parse_index_key(index)
370
+ _stype, _index = self._dim1_parse_index_key(index)
205
371
  if _stype[0] == "t" or _stype == "n":
206
372
  raise ValueError(f"Here only accept non-slice index In {self.__class__.__name__}. But got {index}")
207
373
 
@@ -210,9 +376,7 @@ class _ExcelIndexsLike_1DArray:
210
376
  end = self.size
211
377
 
212
378
  _poped = self._sr.iloc[_index:end].to_list()
213
- _construct_list = self._sr.iloc[:_index].to_list() + self._sr.iloc[end:].to_list()
214
- self._sr = pd.Series(_construct_list)
215
- self._reindex()
379
+ self._construct_new_from_delete(slice(_index, end), inplace=True)
216
380
  if n == 1:
217
381
  return _poped[0]
218
382
  return _poped
@@ -223,10 +387,7 @@ class _ExcelIndexsLike_1DArray:
223
387
  :param other: Series/List/NumpyArray/ExcelIndexed-1D
224
388
  :return: None
225
389
  """
226
- if not isinstance(other, _ExcelIndexsLike_1DArray):
227
- other = self.__class__(other)
228
- self._sr = self._sr._append(other._sr, ignore_index=True)
229
- self._reindex()
390
+ self._construct_new_from_insert(self.size, *other, inplace=True)
230
391
 
231
392
  def index(self, value):
232
393
  """
@@ -261,22 +422,21 @@ class _ExcelIndexsLike_1DArray:
261
422
  Reverse the Series in place.
262
423
  :return: None
263
424
  """
264
- self._sr = self._sr.iloc[::-1]
265
- self._reindex()
425
+ self._construct_new_from_index(slice(None, None, -1), inplace=True)
266
426
 
267
427
  def clear(self):
268
428
  """
269
429
  Clear the Series.
270
430
  :return: None
271
431
  """
272
- self._sr = pd.Series()
432
+ self._construct_new_from_delete(slice(None), inplace=True)
273
433
 
274
434
  def copy(self):
275
435
  """
276
436
  Return a shallow copy of the Series.
277
437
  :return: Series. A shallow copy of the Series.
278
438
  """
279
- return self.__class__(self._sr.copy(), copy=False)
439
+ return self.__class__(self._sr.copy(), copy=False) # NOTE: copy won't change the shape. So no need to construct new one.
280
440
 
281
441
  def sort(self, key=None, reverse=False):
282
442
  """
@@ -287,8 +447,8 @@ class _ExcelIndexsLike_1DArray:
287
447
  """
288
448
  _construct_list = self._sr.to_list()
289
449
  _construct_list.sort(key=key, reverse=reverse)
290
- self._sr = pd.Series(_construct_list)
291
- self._reindex()
450
+ self._sr = pd.Series(_construct_list) # NOTE: sort won't change the shape. So no need to construct new one.
451
+ self._reindexself()
292
452
 
293
453
  def __iter__(self):
294
454
  return iter(self._sr)
@@ -297,7 +457,7 @@ class _ExcelIndexsLike_1DArray:
297
457
  return value in self._sr
298
458
 
299
459
  def __eq__(self, other):
300
- if not isinstance(other, _ExcelIndexsLike_1DArray):
460
+ if not isinstance(other, ExcelIndexsLike_1DArray):
301
461
  other = self.__class__(other)
302
462
  return self._sr.equals(other._sr)
303
463
 
@@ -316,14 +476,14 @@ class _ExcelIndexsLike_1DArray:
316
476
  return self.__class__(_construct_list)
317
477
 
318
478
 
319
- class Row(_ExcelIndexsLike_1DArray): # Like 1: (A B C D)
479
+ class Row(ExcelIndexsLike_1DArray): # Like 1: (A B C D)
320
480
  _CLS_DEFAULT_INDEX_TYPE = "s"
321
481
 
322
482
  def __str__(self):
323
483
  return "Row Series:\n" + super().__str__() + "\n"
324
484
 
325
485
 
326
- class Col(_ExcelIndexsLike_1DArray): # Like A: (1 2 3 4)
486
+ class Col(ExcelIndexsLike_1DArray): # Like A: (1 2 3 4)
327
487
  _CLS_DEFAULT_INDEX_TYPE = "i"
328
488
 
329
489
  def __str__(self):
@@ -362,7 +522,7 @@ class _ExcelIndexsLike_2DArray:
362
522
  warnings.warn(
363
523
  f"Table's input should be rows, but got a column. Regarded as a row.(at index:{i})"
364
524
  )
365
- elif isinstance(_l, _ExcelIndexsLike_1DArray):
525
+ elif isinstance(_l, ExcelIndexsLike_1DArray):
366
526
  _construct_list.append(_l._sr.to_list())
367
527
  elif isinstance(_l, list):
368
528
  _construct_list.append(_l.copy())
@@ -431,10 +591,8 @@ class _ExcelIndexsLike_2DArray:
431
591
  _cnd[lt.ir, lt.ic] = ':' + _cnd[lt.ir, lt.ic][1:]
432
592
  _unhandled_mask[lt.ir:rb.ir + 1, lt.ic:rb.ic + 1] = False
433
593
 
434
-
435
594
  return str(pd.DataFrame(_cnd, index=self._df.index, columns=self._df.columns, copy=False))
436
595
 
437
-
438
596
  def __repr__(self):
439
597
  c = Coord(self._df.shape[1] - 1, self._df.shape[0] - 1)
440
598
  return f"Table[:'{c.letter}']"
@@ -776,6 +934,12 @@ class _ExcelIndexsLike_2DArray:
776
934
  sr, sc = self._df.shape
777
935
  sr, sc = sr - 1, sc - 1
778
936
 
937
+ if isinstance(ir, slice):
938
+ ir = ir.stop if ir.stop is not None else sr
939
+
940
+ if isinstance(ic, slice):
941
+ ic = ic.stop if ic.stop is not None else sc
942
+
779
943
  if ir is None:
780
944
  ir = sr
781
945
  if ic is None:
@@ -787,9 +951,9 @@ class _ExcelIndexsLike_2DArray:
787
951
 
788
952
  # enlarge the table
789
953
  _2d_list = self._df.values.tolist()
790
- if ir >= sr:
954
+ if ir > sr:
791
955
  _2d_list += [[fill] * sc for _ in range(ir - sr)]
792
- if ic >= sc:
956
+ if ic > sc:
793
957
  for _l in _2d_list:
794
958
  _l.extend([fill] * (ic - sc))
795
959
  self._reconstruct(_2d_list, *self._other_raws, copy=False)
@@ -842,7 +1006,7 @@ class _ExcelIndexsLike_2DArray:
842
1006
  _i0 = slice(None)
843
1007
  elif _is_i1_slice and _i1.start is None and _i1.stop is None:
844
1008
  _i1 = None
845
- else: # 单个元素赋值
1009
+ else: # 元素赋值
846
1010
  self._enlarge(_i0, _i1, fill=np.nan)
847
1011
  self._df.iloc[_i0, _i1] = value
848
1012
 
@@ -940,7 +1104,6 @@ class _ExcelIndexsLike_2DArray:
940
1104
  else:
941
1105
  self._df.iloc[_i0, _i1] = np.nan
942
1106
 
943
-
944
1107
  def append(self, *values, axis=0):
945
1108
  """
946
1109
  Append Rows to the end of the table
@@ -1002,9 +1165,9 @@ class _ExcelIndexsLike_2DArray:
1002
1165
 
1003
1166
  # 先考虑扩张
1004
1167
  if _r < _new_r:
1005
- self.append([[fill.copy() if _has_fill_copy else fill] * _c for _ in range(_new_r - _r)], axis=0)
1168
+ self.append(*[[fill.copy() if _has_fill_copy else fill] * _c for _ in range(_new_r - _r)], axis=0)
1006
1169
  if _c < _new_c:
1007
- self.append([fill.copy() if _has_fill_copy else fill for _ in range(_new_c - _c)], axis=1)
1170
+ self.append(*[fill.copy() if _has_fill_copy else fill for _ in range(_new_c - _c)], axis=1)
1008
1171
 
1009
1172
  # 再考虑缩小 NOTE: 缩小不必考虑label的问题
1010
1173
  if _r > _new_r:
@@ -1012,7 +1175,6 @@ class _ExcelIndexsLike_2DArray:
1012
1175
  if _c > _new_c:
1013
1176
  self._reconstruct(self._df.iloc[:, :_new_c], *self._other_raws, copy=False)
1014
1177
 
1015
-
1016
1178
  def __iter__(self):
1017
1179
  for _r in self._df.values.tolist():
1018
1180
  yield _r
@@ -1033,6 +1195,8 @@ class _ExcelIndexsLike_2DArray:
1033
1195
  return not self._df.equals(other._df)
1034
1196
  return np.array(self._df) != other
1035
1197
 
1198
+ def __len__(self):
1199
+ return self.shape[0]
1036
1200
 
1037
1201
 
1038
1202
  class StyleRow(Row):
@@ -1046,6 +1210,7 @@ class StyleRow(Row):
1046
1210
  for ic in range(self.shape[0]):
1047
1211
  self._sr.iloc[ic][key] = value
1048
1212
 
1213
+
1049
1214
  class StyleCol(Col):
1050
1215
  def set(self, key, value):
1051
1216
  """
@@ -1057,9 +1222,11 @@ class StyleCol(Col):
1057
1222
  for ir in range(self.shape[0]):
1058
1223
  self._sr.iloc[ir][key] = value
1059
1224
 
1225
+
1060
1226
  class StyleTable(_ExcelIndexsLike_2DArray):
1061
1227
  _ROW_CLS = StyleRow
1062
1228
  _COL_CLS = StyleCol
1229
+
1063
1230
  def set(self, key, value):
1064
1231
  """
1065
1232
  Set the style of each cell
@@ -1071,11 +1238,12 @@ class StyleTable(_ExcelIndexsLike_2DArray):
1071
1238
  for ic in range(self.shape[1]):
1072
1239
  self._df.iloc[ir, ic][key] = value
1073
1240
 
1241
+
1074
1242
  class Table(_ExcelIndexsLike_2DArray):
1075
1243
  # def __init__(self, _from=None, *, copy: bool = True):
1076
- def __init__(self, data=None, styles=None, merges:list=None, *, copy: bool = True):
1244
+ def __init__(self, data=None, styles=None, merges: list = None, *, copy: bool = True):
1077
1245
  super().__init__(data, copy=copy)
1078
- self._styles = StyleTable() if styles is None else StyleTable(styles)
1246
+ self._styles = StyleTable() if styles is None else (styles.copy() if isinstance(styles, StyleTable) else StyleTable(styles))
1079
1247
  self._merges = []
1080
1248
 
1081
1249
  self.__raw_merges = merges
@@ -1101,7 +1269,7 @@ class Table(_ExcelIndexsLike_2DArray):
1101
1269
  def merges(self):
1102
1270
  return self._merges
1103
1271
 
1104
- def merge(self, key:U[int, str, slice, None], key1:U[int, str, slice, None]=EMPTY_INDEX):
1272
+ def merge(self, key: U[int, str, slice, None], key1: U[int, str, slice, None] = EMPTY_INDEX, *, border: Border = None):
1105
1273
  if key1 is not EMPTY_INDEX:
1106
1274
  key = key, key1
1107
1275
 
@@ -1139,6 +1307,9 @@ class Table(_ExcelIndexsLike_2DArray):
1139
1307
 
1140
1308
  self._merges.append((lt, rb))
1141
1309
 
1310
+ if border is not None:
1311
+ self._styles[key].set(TYPE_BORDER, border)
1312
+
1142
1313
  def _reconstruct(self, *args, **kwargs):
1143
1314
  """
1144
1315
  用于缩小或扩大表格时自动同步调整styles
@@ -1251,6 +1422,7 @@ class Table(_ExcelIndexsLike_2DArray):
1251
1422
 
1252
1423
 
1253
1424
  def __1d_test(_print=True):
1425
+
1254
1426
  d = [1, 2, 3, 4, 5]
1255
1427
 
1256
1428
  r = Row(d)
@@ -1281,6 +1453,16 @@ def __1d_test(_print=True):
1281
1453
  if _print: print(f"删除测试:\n{r}")
1282
1454
 
1283
1455
 
1456
+ def _1d_unit_test(_print=True):
1457
+ tc = TimeRecorder()
1458
+ __1d_test(_print)
1459
+ warnings.filterwarnings('ignore')
1460
+ for i in range(999):
1461
+ __1d_test(_print)
1462
+ warnings.filterwarnings('default')
1463
+ print(f"测试结束,耗时: {tc.dms(1000)} ms / 测试")
1464
+
1465
+
1284
1466
  def __2d_test(_print=True):
1285
1467
  import colorama
1286
1468
  r0 = Col([1, 2, 3, 4, 5])
@@ -1352,4 +1534,4 @@ if __name__ == '__main__':
1352
1534
  # print(f"Finish Test. Cost:{round((time.time() - _a) * 1000, 2)} ms")
1353
1535
  # exit(0)
1354
1536
 
1355
- __2d_test()
1537
+ _1d_unit_test(False)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: tpltable
3
- Version: 0.3.2
3
+ Version: 0.3.4
4
4
  Summary: define "Excel" "Table|StyleTable" "Row|Col|StyleRow|StyleCol". Can dynamic modify excel data & styles. Support multiple kinds of indexes. (70%)
5
5
  Author-email: 2229066748@qq.com
6
6
  Maintainer: Eagle'sBaby
@@ -1 +0,0 @@
1
- from tpltable.basic import *
File without changes
File without changes
File without changes