feasytools 0.0.2__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.
@@ -0,0 +1,20 @@
1
+ Metadata-Version: 2.1
2
+ Name: feasytools
3
+ Version: 0.0.2
4
+ Summary: A set of tools for data processing, including Time Function, Table, Priority Queue, Range List, etc.
5
+ Home-page: UNKNOWN
6
+ Author: fmy_xfk
7
+ License: UNKNOWN
8
+ Description: # FEasyTools: Some useful components
9
+ - dprov: Data provider as time functions
10
+ - argchk: Argument parser with type checking
11
+ - table: Table helper for the CSV, SDT & SDT.GZ format, and their reader and writer.
12
+ - pq: Heap, priority queue & buffered priority queue
13
+ - rangelist: An xml reader for a time range list
14
+
15
+ Platform: UNKNOWN
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: License :: OSI Approved :: MIT License
18
+ Classifier: Operating System :: OS Independent
19
+ Requires-Python: >=3.6
20
+ Description-Content-Type: text/markdown
@@ -0,0 +1,6 @@
1
+ # FEasyTools: Some useful components
2
+ - dprov: Data provider as time functions
3
+ - argchk: Argument parser with type checking
4
+ - table: Table helper for the CSV, SDT & SDT.GZ format, and their reader and writer.
5
+ - pq: Heap, priority queue & buffered priority queue
6
+ - rangelist: An xml reader for a time range list
@@ -0,0 +1,12 @@
1
+ from .tfunc import *
2
+ from .argchk import *
3
+ from .table import *
4
+ from .pq import *
5
+ from .rangelist import *
6
+
7
+ def time2str(tspan:float):
8
+ tspan=round(tspan)
9
+ s=tspan%60
10
+ m=tspan//60%60
11
+ h=tspan//3600
12
+ return f"{h:02}:{m:02}:{s:02}"
@@ -0,0 +1,67 @@
1
+ # 具有类型注释功能的ArgParse: ArgChecker
2
+ import sys
3
+ from typing import Any, Optional, Union
4
+
5
+ class ArgChecker:
6
+ @staticmethod
7
+ def __cast(v:str)->'Union[None,bool,int,float,str]':
8
+ if v=="True": return True
9
+ if v=="False": return False
10
+ if v=="None": return None
11
+ try:
12
+ return int(v)
13
+ except:
14
+ pass
15
+ try:
16
+ return float(v)
17
+ except:
18
+ return v.strip('"')
19
+ @staticmethod
20
+ def get_dict()->'dict[str,Union[str,Any]]':
21
+ '''将输入参数以字典的形式返回'''
22
+ cur_key=None
23
+ ret={}
24
+ for v in sys.argv[1:]:
25
+ if v.startswith('-'):
26
+ if cur_key!=None:
27
+ ret[cur_key]=True
28
+ cur_key=v.strip('-')
29
+ elif cur_key!=None:
30
+ ret[cur_key]=ArgChecker.__cast(v)
31
+ cur_key=None
32
+ else:
33
+ raise ValueError(f"无效参数'{v}'")
34
+ if cur_key!=None: ret[cur_key]=True
35
+ return ret
36
+
37
+ def __init__(self,pars: 'Optional[dict[str,Any]]'=None):
38
+ self.__args=self.get_dict() if pars is None else pars
39
+
40
+ def pop_bool(self, key: str) -> bool:
41
+ if self.__args.pop(key, False): return True
42
+ return False
43
+
44
+ def pop_int(self, key: str, default: Optional[int] = None) -> int:
45
+ val = self.__args.pop(key, default)
46
+ if val is None: raise ValueError(f"必须指定'{key}'参数")
47
+ return int(val)
48
+
49
+ def pop_str(self, key: str, default: Optional[str]=None) -> str:
50
+ val = self.__args.pop(key, default)
51
+ if val is None: raise ValueError(f"必须指定'{key}'参数")
52
+ return str(val)
53
+
54
+ def pop_float(self, key: str, default: Optional[float] = None) -> float:
55
+ val = self.__args.pop(key, default)
56
+ if val is None: raise ValueError(f"必须指定'{key}'参数")
57
+ return float(val)
58
+
59
+ def empty(self) -> bool:
60
+ return len(self.__args) == 0
61
+
62
+ def keys(self): return self.__args.keys()
63
+ def values(self): return self.__args.values()
64
+ def items(self): return self.__args.items()
65
+
66
+ def __str__(self):
67
+ return str(self.__args)
@@ -0,0 +1,185 @@
1
+ import heapq
2
+ from collections import deque
3
+ from typing import Generic, TypeVar
4
+
5
+ QItem=TypeVar("QItem")
6
+
7
+ class Heap(Generic[QItem]):
8
+ '''小根堆数据结构'''
9
+ def __init__(self):
10
+ self._q=[]
11
+
12
+ def push(self,item:QItem):
13
+ '''入堆, O(log n)'''
14
+ heapq.heappush(self._q,item)
15
+
16
+ def pop(self)->QItem:
17
+ '''弹出堆顶元素, O(log n)'''
18
+ return heapq.heappop(self._q)
19
+
20
+ def remove(self,item)->bool:
21
+ '''弹出item元素, O(n)'''
22
+ try:
23
+ idx=self._q.index(item)
24
+ except ValueError:
25
+ return False
26
+ self._q.pop(idx)
27
+ heapq.heapify(self._q)
28
+ return True
29
+
30
+ @property
31
+ def top(self)->QItem:
32
+ '''获取堆顶元素,但不弹出, O(1)'''
33
+ return self._q[0]
34
+
35
+ def __len__(self)->int:
36
+ return len(self._q)
37
+
38
+ def __contains__(self,obj)->bool:
39
+ '''检查元素是否存在, O(n)'''
40
+ return obj in self._q
41
+
42
+ def empty(self)->bool:
43
+ '''检测堆是否为空, O(1)'''
44
+ return len(self._q)==0
45
+
46
+ class PQueue(Generic[QItem]):
47
+ '''优先级队列(小根堆数据结构)'''
48
+ def __init__(self):
49
+ self._q:'list[tuple[int,QItem]]'=[]
50
+
51
+ def push(self,pri:int,item:QItem)->None:
52
+ '''
53
+ 入队, O(log n)
54
+ pri: 优先级
55
+ item: 元素
56
+ '''
57
+ heapq.heappush(self._q,(pri,item))
58
+
59
+ def pop(self)->'tuple[int,QItem]':
60
+ '''获取队首元素并出队, O(log n)'''
61
+ return heapq.heappop(self._q)
62
+
63
+ def remove(self,item:QItem)->bool:
64
+ '''弹出第一个item元素, O(n), 返回是否成功'''
65
+ idx=-1
66
+ for i,(_,data) in enumerate(self._q):
67
+ if data == item:
68
+ idx = i
69
+ break
70
+ if idx==-1: return False
71
+ self._q.pop(idx)
72
+ heapq.heapify(self._q)
73
+ return True
74
+
75
+ @property
76
+ def top(self)->'tuple[int,QItem]':
77
+ '''获取队首元素,但不出队, O(1)'''
78
+ return self._q[0]
79
+
80
+ def __len__(self)->int:
81
+ return len(self._q)
82
+
83
+ def __contains__(self,obj)->bool:
84
+ return obj in self._q
85
+
86
+ def empty(self)->bool:
87
+ '''检测队列是否为空, O(1)'''
88
+ return len(self._q)==0
89
+
90
+ def __str__(self)->str:
91
+ '''转为字符串, O(nlogn)'''
92
+ q2=self._q.copy()
93
+ q2.sort()
94
+ return str(q2)
95
+
96
+ class BufferedPQ(Generic[QItem]):
97
+ '''
98
+ 含有缓冲的优先级队列。
99
+ 该数据结构分为两部分,一个普通队列(记为q)和一个优先级队列(记为p)。
100
+ 优先级队列大小恒定(设大小为n),普通队列大小可变(设大小为m)。
101
+ 数据进入普通队列,而后进入优先级队列,最后离开本结构。
102
+ '''
103
+ def __init__(self,p_size:int):
104
+ self._sz=p_size
105
+ self._P:'PQueue[QItem]'=PQueue()
106
+ self._Q:'deque[tuple[int,QItem]]'=deque()
107
+ self._sP=set()
108
+ self._sQ=set()
109
+
110
+ def __len__(self)->int:
111
+ return len(self._P)+len(self._Q)
112
+
113
+ @property
114
+ def p_size(self)->int:
115
+ '''获取优先级区大小'''
116
+ return self._sz
117
+
118
+ @property
119
+ def p_len(self)->int:
120
+ '''检查优先级区长度, O(1)'''
121
+ return len(self._P)
122
+
123
+ @property
124
+ def q_len(self)->int:
125
+ '''检查缓冲区长度, O(1)'''
126
+ return len(self._Q)
127
+
128
+ def __contains__(self,obj)->bool:
129
+ '''检查元素是否存在, O(1)'''
130
+ return obj in self._sP or obj in self._sQ
131
+
132
+ def push(self,pri:int,obj:QItem)->bool:
133
+ '''
134
+ 入队, O(log n)
135
+ pri: 优先级
136
+ item: 元素
137
+ '''
138
+ if self.__contains__(obj): return False
139
+ if len(self._P)<self._sz:
140
+ self._P.push(pri,obj)
141
+ self._sP.add(obj)
142
+ else:
143
+ self._Q.append((pri,obj))
144
+ self._sQ.add(obj)
145
+ return True
146
+
147
+ def top(self)->'tuple[int,QItem]':
148
+ '''获取队首元素,但不出队, O(1)'''
149
+ return self._P.top
150
+
151
+ def pop(self)->'tuple[int,QItem]':
152
+ '''获取队首元素并出队, O(log n)'''
153
+ ret=self._P.pop()
154
+ self._sP.remove(ret[1])
155
+ if len(self._Q)>0:
156
+ pri,obj=self._Q.popleft()
157
+ self._sQ.remove(obj)
158
+ self._P.push(pri,obj)
159
+ self._sP.add(obj)
160
+ return ret
161
+
162
+ def empty(self)->bool:
163
+ '''检测队列是否为空, O(1)'''
164
+ return self._P.empty()
165
+
166
+ def remove(self,obj)->bool:
167
+ '''弹出第一个obj元素, O(n+m), 返回是否成功'''
168
+ if self._P.remove(obj): return True
169
+ try:
170
+ self._Q.remove(obj)
171
+ return True
172
+ except:
173
+ return False
174
+
175
+ def p_has(self,obj)->bool:
176
+ '''检查元素是否存在于优先级区, O(1)'''
177
+ return obj in self._sP
178
+
179
+ def q_has(self,obj)->bool:
180
+ '''检查元素是否存在于缓冲区, O(1)'''
181
+ return obj in self._sQ
182
+
183
+ def __str__(self)->str:
184
+ '''转为字符串, O(nlogn+m)'''
185
+ return f"BufferedPQ[P={self._P},Q={self._Q}]"
@@ -0,0 +1,46 @@
1
+ import xml.etree.ElementTree as ET
2
+ from typing import Union, overload
3
+
4
+ class RangeList:
5
+ '''时间范围列表,用于表示一系列时间段'''
6
+ @staticmethod
7
+ def parse_time(s:str)->int:
8
+ '''将时间字符串转换为秒数, 支持格式为hh:mm:ss或者一个可转化成int的字符串'''
9
+ try:
10
+ return int(s)
11
+ except:
12
+ h,m,s = s.split(":")
13
+ return int(h)*3600+int(m)*60+int(s)
14
+
15
+ @overload
16
+ def __init__(self, data: ET.Element): '''从xml节点初始化'''
17
+ @overload
18
+ def __init__(self, data: 'list[tuple[int,int]]'): '''从列表初始化'''
19
+
20
+ def __init__(self, data: 'Union[list[tuple[int,int]], ET.Element]'):
21
+ if isinstance(data,ET.Element):
22
+ loop_period = int(data.attrib.get("loop_period", 0))
23
+ loop_times = int(data.attrib.get("loop_times", 1))
24
+ assert loop_times >= 1
25
+ data = [(self.parse_time(itm.attrib['btime']),self.parse_time(itm.attrib["etime"])) for itm in data]
26
+ if loop_times > 1 and len(data) > 1:
27
+ assert loop_period > data[-1][1]
28
+ n = len(data)
29
+ for j in range(1, loop_times):
30
+ for i in range(n):
31
+ if data[i][0]>=data[i][1]: raise ValueError(f"起始时间{data[i][0]}晚于终止时间{data[i][1]}")
32
+ data.append((data[i][0]+loop_period*j,data[i][1]+loop_period*j))
33
+ self._d:'list[tuple[int,int]]' = data
34
+
35
+ def __contains__(self, t:int):
36
+ for (l,r) in self._d:
37
+ if l<=t and t<r: return True
38
+ return False
39
+
40
+ def __len__(self)->int: return self._d.__len__()
41
+
42
+ def __getitem__(self, indices): return self._d.__getitem__(indices)
43
+
44
+ def __str__(self): return str(self._d)
45
+
46
+ def __iter__(self): return iter(self._d)
@@ -0,0 +1,490 @@
1
+ import struct, gzip
2
+ from typing import IO, BinaryIO, Generic, Optional, TextIO, Type, TypeVar, Union, Iterable, overload
3
+ import numpy as np
4
+ from abc import abstractmethod, ABCMeta
5
+
6
+ Table_DType = Type[Union[np.int32,np.float32]]
7
+ _Table_DType = TypeVar("_Table_DType",np.int32,np.float32)
8
+
9
+ def _Lchk(L):
10
+ assert L=='i' or L=='f'
11
+
12
+ def _dtypechk(dtype):
13
+ assert dtype==np.int32 or dtype==np.float32
14
+
15
+ def _L2dtype(L)->Type:
16
+ return np.int32 if L=='i' else np.float32
17
+
18
+ def _dtype2L(dtype)->str:
19
+ return 'i' if dtype==np.int32 else 'f'
20
+
21
+ class TableWriter(metaclass = ABCMeta):
22
+ '''数据表写入器(抽象类)'''
23
+ @abstractmethod
24
+ def __init__(self,col_names:'list[str]',dtype:Type)->None:
25
+ _dtypechk(dtype)
26
+ self._col_names:'list[str]' = col_names
27
+ self._dtype:Table_DType = dtype
28
+ @property
29
+ def col_num(self)->int:
30
+ '''数据表的列数'''
31
+ return len(self._col_names)
32
+ @property
33
+ def dtype(self)->Type:
34
+ '''数据表的数据类型'''
35
+ return self._dtype
36
+ @abstractmethod
37
+ def write(self,data:list)->None: '''写入一条数据, 数据长度必须与列数相等'''
38
+ @abstractmethod
39
+ def write_all(self,data:np.ndarray)->None: '''写入多条数据, 数据长度(参数ndarray的列数)必须与本表的列数相等'''
40
+ @abstractmethod
41
+ def close(self)->None: '''关闭写入器'''
42
+
43
+ class FileTableWriter(TableWriter):
44
+ '''文件数据表写入器(抽象类)'''
45
+ @abstractmethod
46
+ def __init__(self,fh:IO,col_names:'list[str]',dtype:Type)->None:
47
+ super().__init__(col_names,dtype)
48
+ self._fh=fh
49
+
50
+ class MemoryTableWriter(TableWriter):
51
+ '''内存数据表写入器'''
52
+ _data:np.ndarray
53
+ def __init__(self,col_names:'list[str]',dtype:Type)->None:
54
+ '''
55
+ 初始化
56
+ col_names: 列名
57
+ dtype: 数据类型, 可以是np.int32或np.float32
58
+ '''
59
+ super().__init__(col_names,dtype)
60
+ self._data=np.zeros((0,len(col_names)),dtype=dtype)
61
+
62
+ def write(self,data:list)->None:
63
+ self._data=np.vstack([self._data,np.array(data,dtype=self._dtype)])
64
+
65
+ def write_all(self,data:np.ndarray)->None:
66
+ self._data=np.vstack([self._data,data])
67
+
68
+ def close(self): pass
69
+
70
+ @property
71
+ def data(self)->np.ndarray:
72
+ '''获取写入的所有数据'''
73
+ return self._data
74
+
75
+ class CsvTableWriter(FileTableWriter):
76
+ '''CSV数据表写入器'''
77
+ _fh: TextIO
78
+ def __init__(self,fname:str,col_names:'list[str]',dtype:Type)->None:
79
+ '''
80
+ 初始化
81
+ fname: 文件名
82
+ col_names: 列名
83
+ dtype: 数据类型, 可以是np.int32或np.float32
84
+ '''
85
+ super().__init__(open(fname,"w"),col_names,dtype)
86
+ self._fh.write(','.join(self._col_names)+"\n")
87
+
88
+ def write(self,data:list)->None:
89
+ self._fh.write(','.join(map(str,data))+"\n")
90
+
91
+ def write_all(self, data: np.ndarray)->None:
92
+ for ln in data: self.write(ln)
93
+
94
+ def close(self): self._fh.close()
95
+
96
+ class BinTableWriter(FileTableWriter):
97
+ '''二进制数据表写入器(抽象类)'''
98
+ _fh: IO
99
+ @abstractmethod
100
+ def __init__(self,fh,col_names:'list[str]',dtype:Type,buf_sz:int=1024):
101
+ super().__init__(fh,col_names,dtype)
102
+ header=_dtype2L(dtype)+('|'.join(self._col_names))
103
+ header+=" "*((4-len(header)%4)%4)
104
+ header=header.encode()
105
+ self._fh.write(struct.pack("<I",len(header)))
106
+ self._fh.write(header)
107
+ self._buf=[]
108
+ self._buf_sz=buf_sz
109
+ self._dcnt=0
110
+
111
+ def __wbuf(self):
112
+ self._fh.write(np.stack(self._buf,dtype=self._dtype).tobytes())
113
+ self._buf=[]
114
+
115
+ def write(self,data:list):
116
+ self._buf.append(data)
117
+ self._dcnt+=1
118
+ if len(data)>=self._buf_sz: self.__wbuf()
119
+
120
+ def write_all(self, data: np.ndarray):
121
+ if data.dtype!=self._dtype: data.astype(self._dtype)
122
+ self._fh.write(data.tobytes())
123
+
124
+ def close(self):
125
+ if len(self._buf)>0: self.__wbuf()
126
+ self._fh.close()
127
+
128
+ class SdtTableWriter(BinTableWriter):
129
+ '''SDT数据表写入器'''
130
+ def __init__(self,fname:str,col_names:'list[str]',dtype:Type,buf_sz:int=1024):
131
+ '''
132
+ 初始化
133
+ fname: 文件名
134
+ col_names: 列名
135
+ dtype: 数据类型, 可以是np.int32或np.float32
136
+ buf_sz: 缓冲区大小, 默认为1024
137
+ '''
138
+ super().__init__(open(fname,"wb"),col_names,dtype,buf_sz)
139
+
140
+ class SdtGzTableWriter(BinTableWriter):
141
+ '''SDT.GZ数据表写入器'''
142
+ def __init__(self,fname:str,col_names:'list[str]',dtype:Type,buf_sz:int=1024):
143
+ '''
144
+ 初始化
145
+ fname: 文件名
146
+ col_names: 列名
147
+ dtype: 数据类型, 可以是np.int32或np.float32
148
+ buf_sz: 缓冲区大小, 默认为1024
149
+ '''
150
+ super().__init__(gzip.open(fname,"wb"),col_names,dtype,buf_sz)
151
+
152
+ class TableReader(metaclass = ABCMeta):
153
+ '''数据表读取器(抽象类)'''
154
+ @abstractmethod
155
+ def __init__(self,col_names:'list[str]',dtype:Type)->None:
156
+ _dtypechk(dtype)
157
+ self._col_names:'list[str]'=col_names
158
+ self._col_cnt=len(self._col_names)
159
+ self._cmap = {cn:i for i,cn in enumerate(self._col_names)}
160
+ self._dtype:Type=dtype
161
+ @property
162
+ def head(self)->'list[str]':
163
+ '''表头'''
164
+ return self._col_names
165
+ @property
166
+ def dtype(self)->Type:
167
+ '''数据类型, 可以是np.int32或np.float32'''
168
+ return self._dtype
169
+ @abstractmethod
170
+ def read(self,cnt:int)->np.ndarray: '''从当前位置开始, 读取1行内容'''
171
+ @abstractmethod
172
+ def read_all(self)->np.ndarray: '''从头开始, 读取所有内容'''
173
+ @abstractmethod
174
+ def close(self)->None: '''关闭TableReader'''
175
+
176
+ class FileTableReader(TableReader):
177
+ '''文件数据表读取器(抽象类)'''
178
+ @abstractmethod
179
+ def __init__(self,f:IO,col_names:'list[str]',dtype:Type)->None:
180
+ super().__init__(col_names,dtype)
181
+ self._fh:IO=f
182
+ def close(self)->None:
183
+ self._fh.close()
184
+
185
+ class MemoryTableReader(TableReader):
186
+ '''内存数据表读取器'''
187
+ _data:np.ndarray
188
+ def __init__(self,col_names:'list[str]',data:np.ndarray):
189
+ '''
190
+ 初始化
191
+ col_names: 列名
192
+ data: 数据
193
+ '''
194
+ super().__init__(col_names,data.dtype)
195
+ self._data=data
196
+ self.__pos = 0
197
+
198
+ def read(self,cnt:int)->np.ndarray:
199
+ assert cnt > 0, "读取行数必须大于0"
200
+ ed = cnt + self.__pos
201
+ if ed > self._data.shape[0]: ed = self._data.shape[0]
202
+ return self._data[self.__pos:ed]
203
+
204
+ def read_all(self)->np.ndarray:
205
+ return self._data
206
+
207
+ def close(self): pass
208
+
209
+ class BinTableReader(FileTableReader):
210
+ '''二进制数据表读取器(抽象类)'''
211
+ _fh:BinaryIO
212
+ @abstractmethod
213
+ def __init__(self,fname:str,openFunc,allowNegativeSeek:bool=False)->None:
214
+ self.__fn=fname
215
+ self._openF=openFunc
216
+ fh:BinaryIO=self._openF(self.__fn,"rb")
217
+ hlen=struct.unpack("<I",fh.read(4))[0]
218
+ header=fh.read(hlen).decode()
219
+ super().__init__(fh,header[1:].strip().split('|'),_L2dtype(header[0]))
220
+ self._dstart=hlen+4
221
+ self._itmsz=self._col_cnt*4
222
+ self._neg=allowNegativeSeek
223
+
224
+ def seek(self,row:int,col:int=0)->int:
225
+ '''定位到第row行的第col个数据, row和col均从0开始编号'''
226
+ dst=self._dstart+self._itmsz*row+4*col
227
+ if self._neg or self._fh.tell()<=dst:
228
+ return self._fh.seek(dst)
229
+ else:
230
+ self._fh.close()
231
+ self._fh=self._openF(self.__fn,"rb")
232
+ return self._fh.seek(dst)
233
+
234
+ def read(self,cnt:int)->Optional[np.ndarray]:
235
+ data=self._fh.read(self._itmsz*cnt)
236
+ if data==b'': return None
237
+ return np.frombuffer(data,self._dtype).reshape(-1,self._col_cnt)
238
+
239
+ def read_all(self)->np.ndarray:
240
+ self.seek(0,0)
241
+ return np.frombuffer(self._fh.read(),self._dtype).reshape(-1,self._col_cnt)
242
+
243
+ def read_col(self,col_id:Union[int,str])->np.ndarray:
244
+ '''读取col_id列. col_id可以是下标, 也可以是列名'''
245
+ if isinstance(col_id,str): col_id=self._cmap[col_id]
246
+ i=0; dat=[]
247
+ while True:
248
+ self.seek(i,col_id)
249
+ d=self._fh.read(4)
250
+ if d==b'': break
251
+ dat.append(d)
252
+ i+=1
253
+ return np.frombuffer(b''.join(dat),dtype=self._dtype)
254
+
255
+ def read_at(self,start:int,cnt:int)->Optional[np.ndarray]:
256
+ '''读取range(start,start+cnt)范围的行'''
257
+ self.seek(start)
258
+ return self.read(cnt)
259
+
260
+ class SdtTableReader(BinTableReader):
261
+ '''SDT表格读取器'''
262
+ def __init__(self,fname:str):
263
+ '''
264
+ 初始化
265
+ fname: 文件名
266
+ '''
267
+ super().__init__(fname,open,True)
268
+
269
+ class SdtGzTableReader(BinTableReader):
270
+ '''SDT.GZ表格读取器'''
271
+ def __init__(self,fname:str):
272
+ '''
273
+ 初始化
274
+ fname: 文件名
275
+ '''
276
+ super().__init__(fname,gzip.open,False)
277
+
278
+ class CsvTableReader(FileTableReader):
279
+ '''CSV表格读取器'''
280
+ _fh:TextIO
281
+ def __init__(self,fname:str,dtype:Type)->None:
282
+ '''
283
+ 初始化
284
+ fname: 文件名
285
+ dtype: 数据类型, 可以是np.int32或np.float32
286
+ '''
287
+ self.__fn=fname
288
+ fh=open(fname,"r")
289
+ super().__init__(fh,fh.readline().strip().split(","),dtype)
290
+
291
+ def __parseline(self,ln:str)->np.ndarray:
292
+ strs=map(lambda x: x.strip(), ln.split(','))
293
+ if self._dtype==np.int32:
294
+ return np.array(list(map(int,strs)),dtype=np.int32)
295
+ else:
296
+ return np.array(list(map(float,strs)),dtype=np.float32)
297
+
298
+ def __read1(self)->Optional[np.ndarray]:
299
+ '''从当前位置开始, 读取1行'''
300
+ ln = self._fh.readline().strip()
301
+ if ln == "": return None
302
+ return self.__parseline(ln)
303
+
304
+ def read(self,cnt:int)->Optional[np.ndarray]:
305
+ if cnt == 1: return self.__read1()
306
+ ret = []
307
+ for _ in range(cnt):
308
+ dat = self.__read1()
309
+ if dat is None: break
310
+ ret.append(dat)
311
+ if len(ret) == 0: return None
312
+ return np.stack(ret)
313
+
314
+ def read_all(self)->np.ndarray:
315
+ self._fh.close()
316
+ self._fh = open(self.__fn,"r")
317
+ self._fh.readline()
318
+ return np.stack([self.__parseline(x) for x in self._fh.readlines()])
319
+
320
+ def createTableReader(fname:str,dtype:Optional[Type]=None)->TableReader:
321
+ '''从文件名创建TableReader, 对于CSV文件需要指定dtype'''
322
+ fn=fname.lower()
323
+ if fn.endswith(".csv"):
324
+ assert dtype is not None
325
+ return CsvTableReader(fname,dtype)
326
+ elif fn.endswith(".sdt"):
327
+ return SdtTableReader(fname)
328
+ elif fn.endswith(".sdt.gz"):
329
+ return SdtGzTableReader(fname)
330
+ else:
331
+ raise ValueError("不支持的文件类型")
332
+
333
+ def createTableWriter(fname:str,cols:'list[str]',dtype:Type)->TableWriter:
334
+ '''从文件名创建TableWriter'''
335
+ fn=fname.lower()
336
+ if fn.endswith(".csv"):
337
+ return CsvTableWriter(fname,cols,dtype)
338
+ elif fn.endswith(".sdt"):
339
+ return SdtTableWriter(fname,cols,dtype)
340
+ elif fn.endswith(".sdt.gz"):
341
+ return SdtGzTableWriter(fname,cols,dtype)
342
+ elif fn.endswith("<mem>"):
343
+ return MemoryTableWriter(cols,dtype)
344
+ else:
345
+ raise ValueError("不支持的文件类型")
346
+
347
+ def _convbinfile(r:Union[BinaryIO,gzip.GzipFile],w:Union[BinaryIO,gzip.GzipFile],bufsz:int=1024*1024*64):
348
+ while True:
349
+ data=r.read(bufsz)
350
+ if data==b'': break
351
+ w.write(data)
352
+ r.close()
353
+ w.close()
354
+
355
+ def convertTableFile(rfile:str,wfile:str,dtype:Optional[Type]=None,bufsz:int=1024)->None:
356
+ '''将一种文件类型的Table转化成另一文件类型的Table, 如果输入文件为CSV文件, 还需要指定dtype'''
357
+ if rfile.lower().endswith(".sdt.gz") and wfile.lower().endswith(".sdt"):
358
+ _convbinfile(gzip.open(rfile,"rb"),open(wfile,"wb"))
359
+ return
360
+ elif rfile.lower().endswith(".gz") and wfile.lower().endswith(".sdt.gz"):
361
+ _convbinfile(open(rfile,"rb"),gzip.open(wfile,"wb"))
362
+ return
363
+ r=createTableReader(rfile,dtype)
364
+ w=createTableWriter(wfile,r.head,r.dtype)
365
+ while True:
366
+ data=r.read(bufsz)
367
+ if data is None: break
368
+ w.write_all(data)
369
+ r.close()
370
+ w.close()
371
+
372
+ class ReadOnlyTable(Generic[_Table_DType]):
373
+ '''
374
+ **只读**数据表, 表中所有数据必须是同一类型, 必须是32位int或float格式.
375
+ 某一行的内容直接用下标获取; 某一列的内容用col方法获取.
376
+ 由于是只读列表,因此必须从文件加载. 支持的文件类型包含csv, sdt和sdt.gz.
377
+ '''
378
+ _d:'Optional[np.ndarray[tuple[int,int],np.dtype[_Table_DType]]]'
379
+ _btr:TableReader
380
+
381
+ @overload
382
+ def __init__(self,source:MemoryTableReader):
383
+ '''使用MemoryTableReader初始化'''
384
+ @overload
385
+ def __init__(self,source:MemoryTableWriter):
386
+ '''使用MemoryTableWriter初始化'''
387
+ @overload
388
+ def __init__(self,source:str,dtype:Optional[Table_DType]=None,preload:bool=False):
389
+ '''
390
+ 初始化
391
+ fname: 数据表文件名, 仅支持csv, sdt和sdt.gz文件.
392
+ dtype: 表格数据类型, 只有csv文件需要提供此项.
393
+ preload: 是否在初始化时预加载全部数据.
394
+
395
+ 注意:
396
+ 预加载使得初始化速度大幅降低, 但大幅提升了运行时性能.
397
+ csv强制开启预加载功能, sdt和sdt.gz自选(推荐sdt.gz开启, sdt关闭).
398
+ 如果数据过大导致内存不足, 请勿使用预加载功能.
399
+ '''
400
+
401
+ def __init__(self,source:Union[str,MemoryTableWriter,MemoryTableReader],dtype:Optional[Table_DType]=None,preload:bool=False):
402
+ if isinstance(source,str):
403
+ self._btr=createTableReader(source,dtype)
404
+ fn=source.lower()
405
+ if preload or fn.lower().endswith(".csv"):
406
+ self._d=self._btr.read_all()
407
+ else:
408
+ self._d=None
409
+ elif isinstance(source,MemoryTableReader):
410
+ self._btr=source
411
+ self._d=source._data
412
+ elif isinstance(source,MemoryTableWriter):
413
+ self._btr=MemoryTableReader(source._col_names,source._data)
414
+ self._d=source._data
415
+
416
+ @property
417
+ def head(self)->'list[str]':
418
+ '''表头'''
419
+ return self._btr._col_names
420
+
421
+ @property
422
+ def dtype(self)->Table_DType:
423
+ '''
424
+ 表中数据类型, 可取np.int32或np.float32
425
+ '''
426
+ return self._btr._dtype
427
+
428
+ @property
429
+ def data(self)->'np.ndarray[(int,int),np.dtype[_Table_DType]]':
430
+ '''
431
+ 表格数据. 如果没有预加载全部数据, 则调用此项时会加载所有数据.
432
+ '''
433
+ if self._d is None: self._d=self._btr.read_all()
434
+ return self._d
435
+
436
+ def force_load_all(self): self._d=self._btr.read_all()
437
+
438
+ @overload
439
+ def col(self,c:str): '''获取列名为c的列'''
440
+ @overload
441
+ def col(self,c:int): '''获取第c列'''
442
+ @overload
443
+ def col(self,c:Iterable[Union[str,int]]): '''获取多列'''
444
+
445
+ def col(self,c:Union[Iterable[Union[str,int]],str,int])->'np.ndarray[int,np.dtype[_Table_DType]]':
446
+ if isinstance(c,int) or isinstance(c,str):
447
+ if self._d is None:
448
+ assert isinstance(self._btr,BinTableReader)
449
+ return self._btr.read_col(c)
450
+ else:
451
+ if isinstance(c,str): c=self._btr._cmap[c]
452
+ return self._d[:,c]
453
+ elif isinstance(c,Iterable):
454
+ nc:list[int]=[self._btr._cmap[x] if isinstance(x,str) else x for x in c]
455
+ return self.data[:,nc]
456
+ else:
457
+ raise TypeError("不支持的索引类型")
458
+
459
+ def row(self,row_id:int)->'Optional[np.ndarray[int,np.dtype[_Table_DType]]]':
460
+ '''获取一行数据'''
461
+ if self._d is not None: return self._d[row_id]
462
+ assert isinstance(self._btr,BinTableReader)
463
+ return self._btr.read_at(row_id,1)
464
+
465
+ def at(self,col_name:str,row_id:int)->_Table_DType:
466
+ '''获取列名为col_name,行为row_id的数据'''
467
+ return self.data[self._btr._cmap[col_name],row_id]
468
+
469
+ def __getitem__(self,indices)->'Union[np.ndarray[tuple[int,...],np.dtype[_Table_DType]],_Table_DType]':
470
+ '''仅限数字下标'''
471
+ return self.data[indices]
472
+
473
+ def save(self,path:str):
474
+ '''
475
+ 保存到.csv或.bin.gz文件
476
+ path: 文件路径
477
+ '''
478
+ if self._d is None: self._d=self._btr.read_all()
479
+ fn=path.lower()
480
+ if fn.endswith(".csv"):
481
+ CsvTableWriter(path,self.head,self.dtype).write_all(self._d)
482
+ elif fn.endswith(".sdt"):
483
+ SdtTableWriter(path,self.head,self.dtype).write_all(self._d)
484
+ elif fn.endswith(".sdt.gz"):
485
+ SdtGzTableWriter(path,self.head,self.dtype).write_all(self._d)
486
+ else:
487
+ raise ValueError("不支持的文件名")
488
+
489
+ def __str__(self):
490
+ return ','.join(self.head)+"\n"+super().__str__()
@@ -0,0 +1,203 @@
1
+ from abc import ABCMeta, abstractmethod
2
+ from functools import reduce
3
+ from operator import add, sub, mul, truediv, floordiv
4
+ from typing import Callable, Iterable, Optional, Union
5
+ import bisect
6
+
7
+ class TimeFunc(metaclass=ABCMeta):
8
+ '''时变函数'''
9
+ @abstractmethod
10
+ def __call__(self,time:int)->float: ...
11
+ @abstractmethod
12
+ def __str__(self)->str: ...
13
+ def __add__(self,other:'FloatLike')->'TimeFunc':
14
+ return calcFunc(self,other,'+')
15
+ def __sub__(self,other:'TimeFunc')->'TimeFunc':
16
+ return calcFunc(self,other,'-')
17
+ def __mul__(self,other)->'TimeFunc':
18
+ return calcFunc(self,other,'*')
19
+ def __truediv__(self,other)->'TimeFunc':
20
+ return calcFunc(self,other,'/')
21
+ def __floordiv__(self,other)->'TimeFunc':
22
+ return calcFunc(self,other,'//')
23
+
24
+ _oper_trans = {
25
+ '+': add,
26
+ '-': sub,
27
+ '*': mul,
28
+ '/': truediv,
29
+ '//': floordiv
30
+ }
31
+ _Oper = Callable[[float,float],float]
32
+ FloatLike = Union[TimeFunc,float,int]
33
+
34
+ class OverrideFunc(TimeFunc):
35
+ '''重载函数. 正常情况下返回原始时间函数的值, 当设置了重载值时返回重载值'''
36
+ def __init__(self,default:TimeFunc,override_val:Optional[float] = None):
37
+ self._val:TimeFunc=default
38
+ self._override:Optional[float] = override_val
39
+
40
+ def __call__(self,t:int)->float:
41
+ if self._override is not None: return self._override
42
+ return self._val(t)
43
+
44
+ def setOverride(self,overval:float):
45
+ '''设置重载值'''
46
+ self._override=overval
47
+
48
+ def clearOverride(self):
49
+ '''清除重载值'''
50
+ self._override=None
51
+
52
+ def __str__(self):
53
+ if self._override is None: return f"OverrideF<{self._val}>"
54
+ return f"OverrideF<O={self._override}>"
55
+
56
+ def __repr__(self): return str(self)
57
+
58
+ class PlusFunc(TimeFunc):
59
+ '''和函数'''
60
+ def __init__(self,f1:TimeFunc,f2:TimeFunc): self._f1=f1; self._f2=f2
61
+ def __call__(self,_t:int)->float: return self._f1(_t)+self._f2(_t)
62
+ def __str__(self)->str: return f"F<{self._f1}+{self._f2}>"
63
+ def __repr__(self)->str: return str(self)
64
+
65
+ class QuickSumFunc(TimeFunc):
66
+ '''快速求和函数'''
67
+ def __init__(self,funcs:'list[TimeFunc]'): self._fs=funcs
68
+ def __call__(self,_t:int)->float: return sum(f(_t) for f in self._fs)
69
+ def __str__(self)->str: return f"FSum<{len(self._fs)} funcs>"
70
+ def __repr__(self)->str: return str(self)
71
+
72
+ class MinusFunc(TimeFunc):
73
+ '''差函数'''
74
+ def __init__(self,f1:TimeFunc,f2:TimeFunc): self._f1=f1; self._f2=f2
75
+ def __call__(self,_t:int)->float: return self._f1(_t)-self._f2(_t)
76
+ def __str__(self)->str: return f"F<{self._f1}-{self._f2}>"
77
+ def __repr__(self)->str: return str(self)
78
+
79
+ class MulFunc(TimeFunc):
80
+ '''积函数'''
81
+ def __init__(self,f1:TimeFunc,f2:TimeFunc): self._f1=f1; self._f2=f2
82
+ def __call__(self,_t:int)->float: return self._f1(_t)*self._f2(_t)
83
+ def __str__(self)->str: return f"F<{self._f1}*{self._f2}>"
84
+ def __repr__(self)->str: return str(self)
85
+
86
+ class QuickMulFunc(TimeFunc):
87
+ '''快速求积函数'''
88
+ def __init__(self,funcs:'list[TimeFunc]'): self._fs=funcs
89
+ def __call__(self,_t:int)->float: return reduce(mul, (f(_t) for f in self._fs))
90
+ def __str__(self)->str: return f"FMul<{len(self._fs)} funcs>"
91
+ def __repr__(self)->str: return str(self)
92
+
93
+ class TrueDivFunc(TimeFunc):
94
+ '''商函数'''
95
+ def __init__(self,f1:TimeFunc,f2:TimeFunc): self._f1=f1; self._f2=f2
96
+ def __call__(self,_t:int)->float: return self._f1(_t)/self._f2(_t)
97
+ def __str__(self)->str: return f"F<{self._f1}/{self._f2}>"
98
+ def __repr__(self)->str: return str(self)
99
+
100
+ class FloorDivFunc(TimeFunc):
101
+ '''整除函数'''
102
+ def __init__(self,f1:TimeFunc,f2:TimeFunc): self._f1=f1; self._f2=f2
103
+ def __call__(self,_t:int)->float: return self._f1(_t)//self._f2(_t)
104
+ def __str__(self)->str: return f"F<{self._f1}//{self._f2}>"
105
+ def __repr__(self)->str: return str(self)
106
+
107
+ class ConstFunc(TimeFunc):
108
+ '''常数函数'''
109
+ def __init__(self,const:float): self._val:float=const
110
+ def __call__(self,time:int)->float: return self._val
111
+ def __str__(self)->str: return f"ConstF<{self._val}>"
112
+ def __repr__(self)->str: return str(self)
113
+
114
+ class SegFunc(TimeFunc):
115
+ '''分段常数函数'''
116
+ def __init__(self,time_line:'list[int]',data:'list[float]'):
117
+ if len(time_line) != len(data): raise ValueError(f"时间线长度{len(time_line)}和数据长度{len(data)}不一致")
118
+ for i in range(1,len(time_line)):
119
+ if time_line[i]<=time_line[i-1]:
120
+ raise ValueError(f"时间必须严格递增: [{i}]={time_line[i]}<=[{i-1}]={time_line[i-1]}")
121
+ self._tl = time_line
122
+ self._d = data
123
+
124
+ def __call__(self,time:int)->float:
125
+ if time < self._tl[0]: raise ValueError(f"时间{time}必须在开始时间{self._tl[0]}之后")
126
+ return self._d[bisect.bisect_right(self._tl, time) - 1]
127
+ def __str__(self)->str: return f"SegF<{len(self._d)} segs>"
128
+ def __repr__(self)->str: return str(self)
129
+
130
+ class TimeImplictFunc(TimeFunc):
131
+ '''隐式时变函数, 即调用的时机决定了时间, 而无需在__call__时指定。__call__的参数time无效'''
132
+ def __init__(self,func:'Callable[[],float]'):self._f=func
133
+ def __call__(self,time:int)->float:return self._f()
134
+ def __str__(self)->str:return f"TImpF<{self._f}>"
135
+ def __repr__(self)->str: return str(self)
136
+
137
+ class ComFunc(TimeFunc):
138
+ '''将普通Python函数包装成可运算函数'''
139
+ def __init__(self,func:'Callable[[int],float]'):self._f=func
140
+ def __call__(self,time:int)->float:return self._f(time)
141
+ def __str__(self)->str:return f"TImpF<{self._f}>"
142
+ def __repr__(self)->str: return str(self)
143
+
144
+ class ManualFunc(TimeFunc):
145
+ '''手动指定常数函数'''
146
+ def __init__(self,init_val:float):self._v=init_val
147
+ def setManual(self,val:float):self._v=val
148
+ def __call__(self,time:int)->float:return self._v
149
+ def __str__(self)->str:return f"ManF<{self._v}>"
150
+ def __repr__(self)->str: return str(self)
151
+
152
+ def __calc_c0(f1:Union[ConstFunc,float],f2:float,op:_Oper)->float:
153
+ if isinstance(f1,float): return op(f1,f2)
154
+ elif isinstance(f1,ConstFunc): return op(f1._val,f2)
155
+ else: raise TypeError(f1)
156
+
157
+ def __calc_c1(f1:SegFunc,f2:float,op:_Oper)->SegFunc:
158
+ return SegFunc(f1._tl,[op(d,f2) for d in f1._d])
159
+
160
+ def __calc_c2(f1:TimeImplictFunc,f2:float,op:_Oper)->TimeImplictFunc:
161
+ return TimeImplictFunc(lambda: op(f1._f(),f2))
162
+
163
+ def quicksum(funcs:Iterable[TimeFunc])->TimeFunc:
164
+ '''快速求和'''
165
+ ret = list(funcs)
166
+ if len(ret)==0: return ConstFunc(0)
167
+ if len(ret)==1: return ret[0]
168
+ if len(ret)==2: return PlusFunc(ret[0],ret[1])
169
+ return QuickSumFunc(ret)
170
+
171
+ def quickmul(funcs:Iterable[TimeFunc])->TimeFunc:
172
+ '''快速求积'''
173
+ ret = list(funcs)
174
+ if len(ret)==0: return ConstFunc(1)
175
+ if len(ret)==1: return ret[0]
176
+ if len(ret)==2: return MulFunc(ret[0],ret[1])
177
+ return QuickMulFunc(ret)
178
+
179
+ def calcFunc(f1:FloatLike,f2:FloatLike,op:str)->TimeFunc:
180
+ _op = _oper_trans[op]
181
+ if isinstance(f2,ConstFunc): f2=f2._val
182
+ if isinstance(f2,(float,int)):
183
+ if isinstance(f1,(ConstFunc,float,int)): return ConstFunc(__calc_c0(f1,f2,_op))
184
+ elif isinstance(f1,SegFunc): return __calc_c1(f1,f2,_op)
185
+ elif isinstance(f1,TimeImplictFunc): return __calc_c2(f1,f2,_op)
186
+ else: f2 = ConstFunc(f2)
187
+ if isinstance(f1,(float,int)): f1 = ConstFunc(f1)
188
+ if isinstance(f1,ConstFunc) and op in ['+','*']:
189
+ if isinstance(f2,SegFunc): return __calc_c1(f2,f1._val,_op)
190
+ elif isinstance(f2,TimeImplictFunc): return __calc_c2(f2,f1._val,_op)
191
+ assert isinstance(f1,TimeFunc) and isinstance(f2,TimeFunc)
192
+ if op=='+': return PlusFunc(f1,f2)
193
+ elif op=='-': return MinusFunc(f1,f2)
194
+ elif op=='*': return MulFunc(f1,f2)
195
+ elif op=='/': return TrueDivFunc(f1,f2)
196
+ elif op=='//': return FloorDivFunc(f1,f2)
197
+ else: raise ValueError(op)
198
+
199
+ def makeFunc(time_line:'list[int]',data:list)->TimeFunc:
200
+ '''生成分段常数函数或常数函数'''
201
+ if len(time_line)!=len(data): raise ValueError(f"时间线长度{len(time_line)}和数据长度{len(data)}不一致")
202
+ if len(data)==1: return ConstFunc(data[0])
203
+ else: return SegFunc(time_line,data)
@@ -0,0 +1,20 @@
1
+ Metadata-Version: 2.1
2
+ Name: feasytools
3
+ Version: 0.0.2
4
+ Summary: A set of tools for data processing, including Time Function, Table, Priority Queue, Range List, etc.
5
+ Home-page: UNKNOWN
6
+ Author: fmy_xfk
7
+ License: UNKNOWN
8
+ Description: # FEasyTools: Some useful components
9
+ - dprov: Data provider as time functions
10
+ - argchk: Argument parser with type checking
11
+ - table: Table helper for the CSV, SDT & SDT.GZ format, and their reader and writer.
12
+ - pq: Heap, priority queue & buffered priority queue
13
+ - rangelist: An xml reader for a time range list
14
+
15
+ Platform: UNKNOWN
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: License :: OSI Approved :: MIT License
18
+ Classifier: Operating System :: OS Independent
19
+ Requires-Python: >=3.6
20
+ Description-Content-Type: text/markdown
@@ -0,0 +1,14 @@
1
+ README.md
2
+ setup.py
3
+ feasytools/__init__.py
4
+ feasytools/argchk.py
5
+ feasytools/pq.py
6
+ feasytools/rangelist.py
7
+ feasytools/table.py
8
+ feasytools/tfunc.py
9
+ feasytools.egg-info/PKG-INFO
10
+ feasytools.egg-info/SOURCES.txt
11
+ feasytools.egg-info/dependency_links.txt
12
+ feasytools.egg-info/requires.txt
13
+ feasytools.egg-info/top_level.txt
14
+ test/test.py
@@ -0,0 +1 @@
1
+ numpy>=1.19.0
@@ -0,0 +1 @@
1
+ feasytools
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,21 @@
1
+ from setuptools import setup, find_packages
2
+
3
+ with open("README.md", "r") as f:
4
+ long_description = f.read()
5
+
6
+ setup(
7
+ name="feasytools",
8
+ version="0.0.2",
9
+ author="fmy_xfk",
10
+ packages=find_packages(),
11
+ description="A set of tools for data processing, including Time Function, Table, Priority Queue, Range List, etc.",
12
+ long_description=long_description,
13
+ long_description_content_type="text/markdown",
14
+ classifiers=[
15
+ "Programming Language :: Python :: 3",
16
+ "License :: OSI Approved :: MIT License",
17
+ "Operating System :: OS Independent",
18
+ ],
19
+ python_requires=">=3.6",
20
+ install_requires=["numpy>=1.19.0"],
21
+ )
@@ -0,0 +1,61 @@
1
+ import sys
2
+ from pathlib import Path
3
+ print(__file__)
4
+ sys.path.append(str(Path(__file__).parent.parent))
5
+ print(sys.path)
6
+ from feasytools import *
7
+
8
+ print("ArgChecker test:")
9
+ print(ArgChecker())
10
+
11
+ print("PQ test:")
12
+ hp = Heap()
13
+ hp.push("h")
14
+ hp.push("e")
15
+ assert hp.empty() == False
16
+ assert len(hp) == 2
17
+ assert hp.remove("h") == True
18
+ hp.push("f")
19
+ assert hp.pop() == "e"
20
+ assert hp.pop() == "f"
21
+ assert hp.empty() == True
22
+
23
+ q = PQueue()
24
+ q.push(3,"henks")
25
+ q.push(1,"shjd")
26
+ assert q.pop() == (1,"shjd")
27
+ assert q.remove("henks") == True
28
+
29
+ print("Rangelist test:")
30
+ rl = RangeList([(0,1),(2,3),(4,5)])
31
+ assert 0 in rl
32
+ assert not 1 in rl
33
+
34
+ # More general tests will be added in the future
35
+
36
+ print("TimeFunc test:")
37
+ f1 = ConstFunc(1)
38
+ f2 = TimeImplictFunc(lambda: 1+2+3)
39
+ f3 = ComFunc(lambda t: t)
40
+ f4 = ManualFunc(1)
41
+ f4.setManual(2)
42
+ print(f1(0))
43
+ print(f2(0))
44
+ print(f3(0))
45
+ print(f4(0))
46
+ print(f1)
47
+ print(f2)
48
+ print(f3)
49
+ print(f4)
50
+ f5 = quicksum([f1,f2,f3,f4])
51
+ print(f5)
52
+ f6 = quickmul([f1,f2,f3,f4])
53
+ print(f6)
54
+ f7 = f5 + 1
55
+ print(f7)
56
+ f8 = f6 * 2
57
+ print(f8)
58
+ f9 = f5 + f6
59
+ print(f9)
60
+ f10 = f5 * f6
61
+ print(f10)