SuperModelingFactory 0.2.0__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.
- ExcelMaster/ExcelFormatTool.py +487 -0
- ExcelMaster/ExcelMaster.py +917 -0
- ExcelMaster/Template.py +525 -0
- ExcelMaster/Utility.py +233 -0
- ExcelMaster/__init__.py +3 -0
- Modeling_Tool/Core/Binning_Tool.py +1608 -0
- Modeling_Tool/Core/Binning_Tool.pyi +48 -0
- Modeling_Tool/Core/Check_DuckDB_Compatibility.py +835 -0
- Modeling_Tool/Core/Json_Data_Converter.py +621 -0
- Modeling_Tool/Core/Model_Registry_Tool.py +199 -0
- Modeling_Tool/Core/ODPS_Tool.py +284 -0
- Modeling_Tool/Core/Slope_Tool.py +356 -0
- Modeling_Tool/Core/Slope_Tool.pyi +31 -0
- Modeling_Tool/Core/XOR_Encryptor.py +207 -0
- Modeling_Tool/Core/XOR_Encryptor.pyi +26 -0
- Modeling_Tool/Core/__init__.py +99 -0
- Modeling_Tool/Core/kDataFrame.py +228 -0
- Modeling_Tool/Core/kDataFrame.pyi +43 -0
- Modeling_Tool/Core/sample_weight_utils.py +77 -0
- Modeling_Tool/Core/utils.py +2672 -0
- Modeling_Tool/Eval/Evaluation_Tool.py +1452 -0
- Modeling_Tool/Eval/Evaluation_Tool.pyi +47 -0
- Modeling_Tool/Eval/Model_Eval_Tool.py +2548 -0
- Modeling_Tool/Eval/Model_Eval_Tool.pyi +37 -0
- Modeling_Tool/Eval/__init__.py +54 -0
- Modeling_Tool/Eval/evaluate_model.py +2008 -0
- Modeling_Tool/Eval/evaluate_model.pyi +50 -0
- Modeling_Tool/Eval/weighted_eval_utils.py +326 -0
- Modeling_Tool/Explainability/Coalition_Structure.py +305 -0
- Modeling_Tool/Explainability/Model_Explainer.py +743 -0
- Modeling_Tool/Explainability/__init__.py +24 -0
- Modeling_Tool/Feature/Distribution_Tool.py +509 -0
- Modeling_Tool/Feature/Distribution_Tool.pyi +42 -0
- Modeling_Tool/Feature/Feature_Insights.py +762 -0
- Modeling_Tool/Feature/Feature_Insights.pyi +33 -0
- Modeling_Tool/Feature/PSI_Tool.py +1195 -0
- Modeling_Tool/Feature/PSI_Tool.pyi +29 -0
- Modeling_Tool/Feature/WOE_Engine_Feature_Patch.py +355 -0
- Modeling_Tool/Feature/__init__.py +40 -0
- Modeling_Tool/Model/Backward_Tool.py +778 -0
- Modeling_Tool/Model/Backward_Tool.pyi +45 -0
- Modeling_Tool/Model/GBM_Search_Tool.py +251 -0
- Modeling_Tool/Model/GBM_Tool.py +1610 -0
- Modeling_Tool/Model/GBM_Tool.pyi +90 -0
- Modeling_Tool/Model/LRM_Tool.py +1198 -0
- Modeling_Tool/Model/LRM_Tool.pyi +47 -0
- Modeling_Tool/Model/__init__.py +61 -0
- Modeling_Tool/Sample/Distribution_Adaptation.py +131 -0
- Modeling_Tool/Sample/Distribution_Adaptation.pyi +30 -0
- Modeling_Tool/Sample/Reject_Infer.py +413 -0
- Modeling_Tool/Sample/Reject_Infer.pyi +43 -0
- Modeling_Tool/Sample/Sample_Split.py +520 -0
- Modeling_Tool/Sample/Sample_Split.pyi +43 -0
- Modeling_Tool/Sample/__init__.py +31 -0
- Modeling_Tool/UAT/UAT_Consistency_Checker.py +1180 -0
- Modeling_Tool/UAT/__init__.py +19 -0
- Modeling_Tool/WOE/WOE_Adapter.py +204 -0
- Modeling_Tool/WOE/WOE_Adapter.pyi +21 -0
- Modeling_Tool/WOE/WOE_Master.py +491 -0
- Modeling_Tool/WOE/WOE_Master.pyi +40 -0
- Modeling_Tool/WOE/WOE_Monotone_Binner.py +3324 -0
- Modeling_Tool/WOE/WOE_Monotone_Binner.pyi +71 -0
- Modeling_Tool/WOE/WOE_Plot_Tool.py +919 -0
- Modeling_Tool/WOE/WOE_Plot_Tool.pyi +46 -0
- Modeling_Tool/WOE/WOE_Report_Builder.py +214 -0
- Modeling_Tool/WOE/WOE_Report_Builder.pyi +24 -0
- Modeling_Tool/WOE/WOE_Tool.py +1094 -0
- Modeling_Tool/WOE/WOE_Tool.pyi +41 -0
- Modeling_Tool/WOE/__init__.py +86 -0
- Modeling_Tool/WOE/plot_woe_tool.py +290 -0
- Modeling_Tool/WOE/plot_woe_tool.pyi +25 -0
- Modeling_Tool/__init__.py +176 -0
- Report/Report_Tool.py +380 -0
- Report/__init__.py +3 -0
- supermodelingfactory-0.2.0.dist-info/METADATA +268 -0
- supermodelingfactory-0.2.0.dist-info/RECORD +79 -0
- supermodelingfactory-0.2.0.dist-info/WHEEL +5 -0
- supermodelingfactory-0.2.0.dist-info/licenses/LICENSE +102 -0
- supermodelingfactory-0.2.0.dist-info/top_level.txt +3 -0
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# =============================================================================
|
|
2
|
+
# Modeling_Tool.WOE.WOE_Plot_Tool
|
|
3
|
+
# -----------------------------------------------------------------------------
|
|
4
|
+
# Copyright (c) 2026 Kyle Sun <github.com/Kyle-J-Sun>. All rights reserved.
|
|
5
|
+
# SuperModelingFactory — Licensed under the Business Source License 1.1.
|
|
6
|
+
#
|
|
7
|
+
# This stub describes the public API of a closed-source module compiled to a
|
|
8
|
+
# native extension (.so / .pyd). The original source is not distributed.
|
|
9
|
+
# Production / commercial use requires a separate commercial license.
|
|
10
|
+
#
|
|
11
|
+
# FINGERPRINT: SMF-WOEPLOTTOOL-9fc25265
|
|
12
|
+
# (Unique trace marker. Do not remove or alter — used for plagiarism
|
|
13
|
+
# detection across the public internet.)
|
|
14
|
+
# =============================================================================
|
|
15
|
+
|
|
16
|
+
from matplotlib import pyplot as plt
|
|
17
|
+
import numpy as np
|
|
18
|
+
import pandas as pd
|
|
19
|
+
import logging
|
|
20
|
+
import os
|
|
21
|
+
import shutil
|
|
22
|
+
from matplotlib.font_manager import FontProperties
|
|
23
|
+
from .WOE_Tool import calc_woe, calc_iv, mapping_woe
|
|
24
|
+
from Modeling_Tool.Core.utils import mkdir_if_not_exist
|
|
25
|
+
def plot_woe(woe_df, var_rename = None, to_show = True, save_dir = None, fig_name = 'var.png'): ...
|
|
26
|
+
def get_woe_table(binning_res, var, dep): ...
|
|
27
|
+
def get_mapped_woe_summary_single(data, var, ref_woe_table, tgt_name): ...
|
|
28
|
+
def get_mapped_woe_summary_grp(data, var, ref_woe_table, tgt_name, grp_name = None): ...
|
|
29
|
+
def get_mapped_woe_summary(data, ref_woe_table, tgt_name, varlist = None, grp_name = None): ...
|
|
30
|
+
def plot_woe_group(woe_grp_df, grp_name = None, var_rename = None, to_show = True, save_dir = None, fig_name = 'var_group.png'): ...
|
|
31
|
+
def align_bin_num(woe_table, grp_woe_df, grp_name): ...
|
|
32
|
+
def get_bivar_graph(data, varlist, sep, ref_woe_table, save_dir, group = None, woe_suffix = '_woe'): ...
|
|
33
|
+
|
|
34
|
+
class WOEPlotter:
|
|
35
|
+
def __init__(self, var_rename = None, to_show = True, save_dir = None, fig_name = 'var.png', grp_name = None): ...
|
|
36
|
+
def plot(self, woe_df, var_rename = None, to_show = None, save_dir = None, fig_name = None): ...
|
|
37
|
+
def plot_group(self, woe_grp_df, grp_name = None, var_rename = None, to_show = None, save_dir = None, fig_name = None): ...
|
|
38
|
+
|
|
39
|
+
class WOEAnalyzer:
|
|
40
|
+
def __init__(self, ref_woe_table = None, tgt_name = None): ...
|
|
41
|
+
def get_woe_table(self, binning_res, var, dep = None): ...
|
|
42
|
+
def get_mapped_woe_summary_single(self, data, var, ref_woe_table = None, tgt_name = None): ...
|
|
43
|
+
def get_mapped_woe_summary_grp(self, data, var, ref_woe_table = None, tgt_name = None, grp_name = None): ...
|
|
44
|
+
def get_mapped_woe_summary(self, data, ref_woe_table = None, tgt_name = None, varlist = None, grp_name = None): ...
|
|
45
|
+
def align_bin_num(self, woe_table, grp_woe_df, grp_name): ...
|
|
46
|
+
def get_bivar_graph(self, data, varlist, sep = None, ref_woe_table = None, save_dir = None, group = None, woe_suffix = '_woe'): ...
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import logging
|
|
3
|
+
import pandas as pd
|
|
4
|
+
# NOTE: ExcelMaster is an optional internal dependency. The user passes an
|
|
5
|
+
# already-initialized ExcelMaster instance via the `em` parameter, so this
|
|
6
|
+
# module does not need to import the class at module load time. Importing it
|
|
7
|
+
# eagerly here would break public installs (e.g. from PyPI) where the
|
|
8
|
+
# internal ExcelMaster package is not available.
|
|
9
|
+
|
|
10
|
+
def get_woe_plot_report_new(em, ws, woe_plot_dir, grp_name, varlist, means_rpt=None, var_dict=None):
|
|
11
|
+
import os
|
|
12
|
+
import logging
|
|
13
|
+
|
|
14
|
+
if var_dict is None:
|
|
15
|
+
var_dict = {}
|
|
16
|
+
|
|
17
|
+
train_image_dir = woe_plot_dir
|
|
18
|
+
|
|
19
|
+
valid_varlist = []
|
|
20
|
+
for var in varlist:
|
|
21
|
+
train_fig_path = f"{train_image_dir}/{var}_{grp_name}.png"
|
|
22
|
+
if os.path.isfile(train_fig_path):
|
|
23
|
+
valid_varlist.append(var)
|
|
24
|
+
|
|
25
|
+
logging.info(f"Valid Number of Vars: {len(valid_varlist)}")
|
|
26
|
+
|
|
27
|
+
em.merge_col(ws, ncols=5, text="Bivar Table")
|
|
28
|
+
varlist = valid_varlist
|
|
29
|
+
image_size = (40, 9)
|
|
30
|
+
|
|
31
|
+
# 为解释行预留空间,起始行上移一行
|
|
32
|
+
em.reset_curr_loc(loc=(2, 1))
|
|
33
|
+
|
|
34
|
+
info_loc_list = {"train": [], f"train_{grp_name}": []}
|
|
35
|
+
image_loc_list = {"train": [], f"train_{grp_name}": []}
|
|
36
|
+
|
|
37
|
+
explanation_format = 'TEXT_NO_FORMAT' # 可自行修改为其他预定义格式
|
|
38
|
+
|
|
39
|
+
for var in varlist:
|
|
40
|
+
explanation = var_dict.get(var, "")
|
|
41
|
+
|
|
42
|
+
cur_r, cur_c = em.get_curr_loc()
|
|
43
|
+
# 写入解释行,不移动光标
|
|
44
|
+
em.merge_col(ws, loc=(cur_r, cur_c), ncols=image_size[1],
|
|
45
|
+
text=explanation, cformat=explanation_format, skipby=None)
|
|
46
|
+
|
|
47
|
+
# 图像从解释行的下一行开始
|
|
48
|
+
img_start_row = cur_r + 1
|
|
49
|
+
img_start_col = cur_c
|
|
50
|
+
em.reset_curr_loc(loc=(img_start_row, img_start_col))
|
|
51
|
+
|
|
52
|
+
train_fig_path = f"{train_image_dir}/{var}.png"
|
|
53
|
+
train_group_fig_path = f"{train_image_dir}/{var}_{grp_name}.png"
|
|
54
|
+
|
|
55
|
+
# --- 原有写入逻辑(Column 1 / 2 / 3)保持不变 ---
|
|
56
|
+
em._resize_image(imgPath=train_fig_path, resize=image_size, outPath=train_fig_path)
|
|
57
|
+
train_image_loc = em.insert_image(ws, figPath=train_fig_path, retCellRange="value")
|
|
58
|
+
image_loc_list["train"].append(train_image_loc)
|
|
59
|
+
|
|
60
|
+
start_row = train_image_loc[0]
|
|
61
|
+
start_col = train_image_loc[3] + 1
|
|
62
|
+
em.reset_curr_loc(loc=(start_row, start_col))
|
|
63
|
+
|
|
64
|
+
em._resize_image(imgPath=train_group_fig_path, resize=image_size, outPath=train_group_fig_path)
|
|
65
|
+
train_group_image_loc = em.insert_image(ws, figPath=train_group_fig_path, retCellRange="value")
|
|
66
|
+
image_loc_list[f"train_{grp_name}"].append(train_group_image_loc)
|
|
67
|
+
|
|
68
|
+
start_row = train_group_image_loc[0]
|
|
69
|
+
start_col = train_group_image_loc[3] + 1
|
|
70
|
+
em.reset_curr_loc(loc=(start_row, start_col))
|
|
71
|
+
|
|
72
|
+
if means_rpt is not None:
|
|
73
|
+
means_rpt_var = means_rpt.loc[means_rpt['attribute'] == var, :].round(2).drop(columns=['attribute']).T
|
|
74
|
+
means_rpt_loc = em.write_dataframe(ws, df=means_rpt_var, title=f"Means for {var}", index=True, header=False, retCellRange="value")
|
|
75
|
+
for i in range(means_rpt_loc[2], means_rpt_loc[2] + 1):
|
|
76
|
+
cell_range = [i, means_rpt_loc[1] + 1, i, means_rpt_loc[3]]
|
|
77
|
+
em.set_color_scale(ws, cell_range=cell_range, colors=("#FFFFFF", "#F8696B"))
|
|
78
|
+
em.set_cell_format(ws, cell_range=cell_range, cformat="NUM%.2")
|
|
79
|
+
|
|
80
|
+
# 下一个变量块的起始行基于图像结束行 + 间隔
|
|
81
|
+
start_row = train_image_loc[2] + 3
|
|
82
|
+
start_col = train_image_loc[1]
|
|
83
|
+
em.reset_curr_loc(loc=(start_row, start_col))
|
|
84
|
+
|
|
85
|
+
# 补写变量名标签(左侧列),起始行对应第一个解释行,行高增加1
|
|
86
|
+
em.reset_curr_loc(loc=(2, 0))
|
|
87
|
+
for i, var in enumerate(varlist):
|
|
88
|
+
em.gap_number = 0
|
|
89
|
+
repeat_num = image_size[0] + 3 + 1 # 图像高度 + 间隔 + 解释行
|
|
90
|
+
var_name_df = pd.DataFrame([var] * repeat_num)
|
|
91
|
+
var_df_loc = em.write_dataframe(ws, df=var_name_df, title=None, index=False, header=False, retCellRange="value")
|
|
92
|
+
|
|
93
|
+
return 0
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
class WoeReportBuilder:
|
|
97
|
+
"""
|
|
98
|
+
A streamlined class for creating multiple WOE analysis report sheets in one Excel workbook.
|
|
99
|
+
|
|
100
|
+
Example usage:
|
|
101
|
+
builder = WoeReportBuilder(
|
|
102
|
+
em=em,
|
|
103
|
+
data=data,
|
|
104
|
+
valid_varlist=valid_varlist,
|
|
105
|
+
woe_suffix='_woe',
|
|
106
|
+
proc_means_func=proc_means_by_grp,
|
|
107
|
+
missing_rate_ref=0.95,
|
|
108
|
+
default_var_dict=var_dict_1
|
|
109
|
+
)
|
|
110
|
+
builder.add_group('sample_ind_fnl', woe_plot_dir='../customized_woe_by_sample/')
|
|
111
|
+
builder.add_group('launch_month', woe_plot_dir='../customized_woe_by_month/')
|
|
112
|
+
builder.add_group('platform_2', woe_plot_dir='../customized_woe_by_platform/')
|
|
113
|
+
builder.close()
|
|
114
|
+
"""
|
|
115
|
+
|
|
116
|
+
def __init__(self, em, data, valid_varlist: list, woe_suffix: str = '_woe',
|
|
117
|
+
proc_means_func=None, missing_rate_ref=0.95,
|
|
118
|
+
default_var_dict: dict = None):
|
|
119
|
+
"""
|
|
120
|
+
Parameters
|
|
121
|
+
----------
|
|
122
|
+
em : ExcelMaster
|
|
123
|
+
An initialized ExcelMaster instance (with the target .xlsx file path).
|
|
124
|
+
data : pd.DataFrame
|
|
125
|
+
The full dataset (must contain the raw variables and their woe columns).
|
|
126
|
+
valid_varlist : list
|
|
127
|
+
List of variable names to be plotted.
|
|
128
|
+
woe_suffix : str
|
|
129
|
+
Suffix that identifies the woe‑transformed columns (e.g., '_woe').
|
|
130
|
+
proc_means_func : callable
|
|
131
|
+
Function with signature (data, full_var_list, group_cols, spec_missing_value) -> pd.DataFrame
|
|
132
|
+
It should return a long-format dataframe of binned means.
|
|
133
|
+
missing_rate_ref : float
|
|
134
|
+
Missing rate threshold for proc_means_func.
|
|
135
|
+
default_var_dict : dict, optional
|
|
136
|
+
Default dictionary mapping variable name -> explanation text.
|
|
137
|
+
Can be overridden per group if needed.
|
|
138
|
+
"""
|
|
139
|
+
|
|
140
|
+
self.proc_means_func = proc_means_func
|
|
141
|
+
if proc_means_func is None:
|
|
142
|
+
from Modeling_Tool.Feature.Distribution_Tool import proc_means_by_grp
|
|
143
|
+
self.proc_means_func = proc_means_by_grp
|
|
144
|
+
|
|
145
|
+
self.em = em
|
|
146
|
+
self.data = data
|
|
147
|
+
self.valid_varlist = valid_varlist
|
|
148
|
+
self.woe_suffix = woe_suffix
|
|
149
|
+
self.missing_rate_ref = missing_rate_ref
|
|
150
|
+
self.default_var_dict = default_var_dict or {}
|
|
151
|
+
|
|
152
|
+
# Pre‑cast raw variables to float (as done in the original script)
|
|
153
|
+
try:
|
|
154
|
+
self.data[self.valid_varlist] = self.data[self.valid_varlist].astype(float)
|
|
155
|
+
except KeyError as e:
|
|
156
|
+
logging.warning(f"Some valid variables not found in data: {e}")
|
|
157
|
+
|
|
158
|
+
# Build full variable list (raw + woe columns)
|
|
159
|
+
self.full_var_list = self.valid_varlist + [x + self.woe_suffix for x in self.valid_varlist]
|
|
160
|
+
|
|
161
|
+
def add_group(self, group_name: str, woe_plot_dir: str,
|
|
162
|
+
sheet_name: str = None, cell_scale: tuple = (1, 2),
|
|
163
|
+
var_dict: dict = None):
|
|
164
|
+
"""
|
|
165
|
+
Add a new worksheet with WOE plots for the given group.
|
|
166
|
+
|
|
167
|
+
Parameters
|
|
168
|
+
----------
|
|
169
|
+
group_name : str
|
|
170
|
+
Name of the grouping column (e.g., 'sample_ind_fnl').
|
|
171
|
+
woe_plot_dir : str
|
|
172
|
+
Path to the directory containing the pre‑generated WOE images.
|
|
173
|
+
The function expects files like {var}.png and {var}_{group_name}.png.
|
|
174
|
+
sheet_name : str, optional
|
|
175
|
+
Name of the Excel sheet. If not given, a name like "Bivar_{group_name}" is used.
|
|
176
|
+
cell_scale : tuple (row_scale, col_scale), default (1, 2)
|
|
177
|
+
Cell size scaling for the worksheet.
|
|
178
|
+
var_dict : dict, optional
|
|
179
|
+
Variable explanation dictionary. Falls back to self.default_var_dict.
|
|
180
|
+
"""
|
|
181
|
+
# Compute means report using the provided function
|
|
182
|
+
means_rpt = self.proc_means_func(
|
|
183
|
+
self.data,
|
|
184
|
+
self.full_var_list,
|
|
185
|
+
[group_name],
|
|
186
|
+
spec_missing_value=self.missing_rate_ref
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
# Build sheet name
|
|
190
|
+
if sheet_name is None:
|
|
191
|
+
sheet_name = f"Bivar_{group_name}"
|
|
192
|
+
|
|
193
|
+
# Add worksheet
|
|
194
|
+
ws = self.em.add_worksheet(name=sheet_name, cell_scale=cell_scale)
|
|
195
|
+
|
|
196
|
+
# Use the provided var_dict or fall back to default
|
|
197
|
+
active_var_dict = var_dict if var_dict is not None else self.default_var_dict
|
|
198
|
+
|
|
199
|
+
# Call the updated plot report function (get_woe_plot_report_new)
|
|
200
|
+
# which must now accept var_dict.
|
|
201
|
+
get_woe_plot_report_new(
|
|
202
|
+
self.em, ws,
|
|
203
|
+
woe_plot_dir=woe_plot_dir,
|
|
204
|
+
grp_name=group_name,
|
|
205
|
+
varlist=self.valid_varlist,
|
|
206
|
+
means_rpt=means_rpt,
|
|
207
|
+
var_dict=active_var_dict
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
logging.info(f"Added WOE report sheet: {sheet_name}")
|
|
211
|
+
|
|
212
|
+
def close(self):
|
|
213
|
+
"""Close and save the Excel workbook."""
|
|
214
|
+
self.em.close_workbook()
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# =============================================================================
|
|
2
|
+
# Modeling_Tool.WOE.WOE_Report_Builder
|
|
3
|
+
# -----------------------------------------------------------------------------
|
|
4
|
+
# Copyright (c) 2026 Kyle Sun <github.com/Kyle-J-Sun>. All rights reserved.
|
|
5
|
+
# SuperModelingFactory — Licensed under the Business Source License 1.1.
|
|
6
|
+
#
|
|
7
|
+
# This stub describes the public API of a closed-source module compiled to a
|
|
8
|
+
# native extension (.so / .pyd). The original source is not distributed.
|
|
9
|
+
# Production / commercial use requires a separate commercial license.
|
|
10
|
+
#
|
|
11
|
+
# FINGERPRINT: SMF-WOEREPORTBUI-85e09901
|
|
12
|
+
# (Unique trace marker. Do not remove or alter — used for plagiarism
|
|
13
|
+
# detection across the public internet.)
|
|
14
|
+
# =============================================================================
|
|
15
|
+
|
|
16
|
+
import os
|
|
17
|
+
import logging
|
|
18
|
+
import pandas as pd
|
|
19
|
+
def get_woe_plot_report_new(em, ws, woe_plot_dir, grp_name, varlist, means_rpt = None, var_dict = None): ...
|
|
20
|
+
|
|
21
|
+
class WoeReportBuilder:
|
|
22
|
+
def __init__(self, em, data, valid_varlist: list, woe_suffix: str = '_woe', proc_means_func = None, missing_rate_ref = 0.95, default_var_dict: dict = None): ...
|
|
23
|
+
def add_group(self, group_name: str, woe_plot_dir: str, sheet_name: str = None, cell_scale: tuple = (1, 2), var_dict: dict = None): ...
|
|
24
|
+
def close(self): ...
|