大数据技术之_23_Python核心基础学习_03_函数 + 对象(12.5小时)

本文详细讲解了Python中的函数和对象概念。涵盖了函数的定义、参数、返回值、递归、高阶函数、闭包以及装饰器。在对象部分,介绍了面向对象编程,包括类的定义、属性和方法、封装、继承、多态,以及模块化和标准库的使用。适合Python初学者和进阶者学习。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

第五章 函数

5.1 函数的简介

- 函数也是一个对象
-   对象是内存中专门用来存储数据的一块区域
-   函数可以用来保存一些可执行的代码,并且可以在需要时,对这些语句进行多次的调用
- 创建函数(定义函数):
    def 函数名([形参1, 形参2, ..., 形参n]) :
        代码块
        
    - 函数名必须要符和标识符的规范
        (可以包含字母、数字、下划线、但是不能以数字开头)    
- 函数中保存的代码不会立即执行,需要调用函数代码才会执行
- 调用函数:
    函数对象()
- 定义函数一般都是要实现某种功能的

函数的简介–示例代码:

# 比如有如下三行代码,这三行代码是一个完整的功能:
# print('hello')
# print('你好')
# print('再见')

# 定义一个函数
def fn() :
    print('这是我的第一个函数!')
    print('hello')
    print('今天天气真不错!')

# 打印 fn
# print(fn)         # <function fn at 0x03D2B618>
# print(type(fn))   # <class 'function'>

# fn 是函数对象  fn() 是调用函数
# print 是函数对象 print() 是调用函数
# fn()

# 定义一个函数,可以用来求任意两个数的和
# def sum() :
#     a = 123
#     b = 456
#     print(a + b)

# sum()

# 定义函数时指定形参
def fn2(a, b) :
    # print('a =', a)
    # print('b =', b)
    print(a, "+", b, "=", a + b)

# 调用函数时,来传递实参
fn2(10, 20)
fn2(123, 456)

5.2 函数的参数

- 在定义函数时,可以在函数名后的()中定义数量不等的形参。
    多个形参之间使用,隔开
- 形参(形式参数),定义形参就相当于在函数内部声明了变量,但是并不赋值
- 实参(实际参数)
    - 如果函数定义时,指定了形参,那么在调用函数时也必须传递实参,实参将会赋值给对应的形参,简单来说,有几个形参就得传几个实参。

练习1:
  定义一个函数,可以用来求任意三个数的乘积。
练习2:
  定义一个函数,可以根据不同的用户名显示不同的欢迎信息。

示例代码:

# 求任意三个数的乘积
def mul(a, b, c) :
    print(a * b * c)

# 根据不同的用户名显示不同的欢迎信息   
def welcome(username) :
    print('欢迎', username, '光临')

# mul(1, 2, 3)
# welcome('孙悟空')

5.3 函数参数传递的方式

# 定义一个函数
#   定义形参时,可以为形参指定默认值
#   指定了默认值以后,如果用户传递了参数则形参默认值没有任何作用,如果用户没有传递参数,则形参默认值就会生效。
def fn(a = 5, b = 10, c = 20) :
    print('a =', a)
    print('b =', b)
    print('c =', c)

# fn(1, 2, 3)
# fn(1, 2)
# fn()

# 实参的传递方式
# 位置参数
#   位置参数:就是将对应位置的实参赋值给对应位置的形参
#   第一个实参赋值给第一个形参,第二个实参赋值给第二个形参,......
# fn(1, 2, 3)

# 关键字参数
#   关键字参数:可以不按照形参定义的顺序去传递,而直接根据参数名去传递参数
# fn(b=1, c=2, a=3)
# print('hello', end='')    # end 就是关键字参数

#   位置参数和关键字参数可以混合使用
#   混合使用位置参数和关键字参数时,必须将位置参数写到前面,且位置参数和关键字参数指定的位置不能相同。
# fn(1, c=30)   # a = 1, b = 10, c = 30

# fn(1, a=30)   # 报错

def fn2(a) :
    print('a =', a)

# 函数在调用时,解析器不会检查实参的类型
# 实参可以传递任意类型的对象
b = 123
b = True
b = 'hello'
b = None
b = [1, 2, 3]

# fn2(b)    
# fn2(fn)   # 没问题,实参可以是函数对象

def fn3(a , b) :
    print(a + b)
# fn3(123, "456")   # 报错

def fn4(a) :
    # 在函数中对形参进行重新赋值,不会影响其他的变量
    # a = 20
    # a 是一个列表,我们尝试修改列表中的元素
    # 如果形参执行的是一个对象,当我们通过形参去修改对象的值时,会影响到所有指向该对象的变量。
    a[0] = 30
    print('a =', a, id(a))

c = 10   
c = [1, 2, 3] 

# fn4(c)
# fn4(c.copy()) # 列表副本,新的对象
# fn4(c[:])     # 列表副本,新的对象

# print('c =', c, id(c))

5.4 函数的不定长参数

# 不定长的参数
# 定义一个函数,可以求任意个数字的和
def sum(*nums) :
    # 定义一个变量,来保存结果
    result = 0
    # 遍历元组,并将元组中的数进行累加
    for n in nums :
        result += n
    print(result)

# sum(123, 456, 789, 10, 20, 30, 40)

# 在定义函数时,可以在形参前边加上一个*,这样这个形参将会获取到所有的实参
# 它将会将所有的实参保存到一个元组中
# a, b, *c = (1, 2, 3, 4, 5, 6)     # 元组的解包(解构)

# *a 会接受所有的位置实参,并且会将这些实参统一保存到一个元组中(参数的装包)
def fn(*a) :
    print("a =", a, type(a))    # a = (1, 2, 3, 4, 5) <class 'tuple'>
fn(1, 2, 3, 4, 5)

# 带星号的形参只能有一个
# 带星号的参数,可以和其他参数配合使用
# 第一个参数给 a,第二个参数给 b,剩下的都保存到 c 的元组中
def fn2(a, b, *c) :
    print('a =', a)   # a = 1
    print('b =', b)   # b = 2
    print('c =', c)   # c = (3, 4, 5)
fn2(1, 2, 3, 4, 5)

# 可变参数不是必须写在最后,但是注意,带*的参数后的所有参数,必须以关键字参数的形式传递
# 第一个参数给 a,剩下的位置参数给 b 的元组,c 必须使用关键字参数
def fn2(a, *b, c) :
    print('a =', a)   # a = 1
    print('b =', b)   # b = (2, 3, 4)
    print('c =', c)   # c = 5
fn2(1, 2, 3, 4, c=5)

# 所有的位置参数都给a,b 和 c 必须使用关键字参数
def fn2(*a, b, c) :
    print('a =',a)    # a = (1, 2, 3)
    print('b =',b)    # b = 4
    print('c =',c)    # c = 5
fn2(1, 2, 3, b=4, c=5)

# 如果在形参的开头直接写一个*,则要求我们的所有的参数必须以关键字参数的形式传递
def fn2(*, a, b, c) :
    print('a =', a)     # a = 3
    print('b =', b)     # b = 4
    print('c =', c)     # c = 5
fn2(a=3, b=4, c=5)

# *形参只能接收位置参数,而不能接收关键字参数
def fn3(*a) :
    print('a =', a)   # a = (1, 2, 3, 4, 5)
fn3(1, 2, 3, 4, 5)

# **形参可以接收其他的关键字参数,它会将这些参数统一保存到一个字典中
#   字典的 key 就是参数的名字,字典的 value 就是参数的值
# **形参只能有一个,并且必须写在所有参数的最后
def fn3(b, c, **a) :
    print('a =', a, type(a))    # a = {'d': 2, 'e': 10, 'f': 20} <class 'dict'>
    print('b =', b)     # b = 1
    print('c =', c)     # c = 3
fn3(b=1, d=2, c=3, e=10, f=20)

# 参数的解包(拆包)
def fn4(a, b, c) :
    print('a =', a)
    print('b =', b)
    print('c =', c)

# 创建一个元组
t = (10, 20, 30)
# 创建一个列表
# t = [10, 20, 30]

# 传递实参时,也可以在序列类型的参数前添加星号,这样它会自动将序列中的元素依次作为参数传递
# 这里要求序列中元素的个数必须和形参的个数的一致
# fn4(t[0], t[1], t[2])     # 此种方式太麻烦了
fn4(*t)     # 解包的方式

# 创建一个字典
d = {
   
   'a':100, 'b':200, 'c':300}
# 通过 ** 来对一个字典进行解包操作
fn4(**d)

5.5 函数的返回值

# 返回值,返回值就是函数执行以后返回的结果
# 可以通过 return 来指定函数的返回值
# 可以直接使用函数的返回值,也可以通过一个变量来接收函数的返回值

def sum(*nums) :
    # 定义一个变量,来保存结果
    result = 0
    # 遍历元组,并将元组中的数进行累加
    for n in nums :
        result += n
    print(result)

# sum(123, 456, 789)
 
# return 后边跟什么值,函数就会返回什么值
# return 后边可以跟任意的对象,返回值甚至可以是一个函数
def fn() :
    # return 'hello'
    # return [1, 2, 3]
    # return {'k':'v'}
    def fn2() :
        print('hello')
    return fn2      # 返回值也可以是一个函数

r = fn()    # 这个函数的执行结果就是它的返回值
# r()
# print(fn())
# print(r)

# 如果仅仅写一个 return 或者不写 return,则相当于 return None 
def fn2() :
    a = 10
    return 

# 在函数中,return 后的代码都不会执行,return 一旦执行,则函数自动结束
def fn3():
    print('hello')
    return
    print('abc')
# r = fn3()
# print(r)

def fn4() :
    for i in range(5) :     # 生成一个这样的自然数序列:[0, 1, 2, 3, 4]
        if i == 3 :
            # break 用来退出当前循环
            # continue 用来跳过当次循环
            return # 用来结束函数
        print(i)
    print('循环执行完毕!')

# fn4()

def sum(*nums) :
    # 定义一个变量,来保存结果
    result = 0
    # 遍历元组,并将元组中的数进行累加
    for n in nums :
        result += n
    return result
    
r = sum(123, 456, 789)

# print(r + 778)

def fn5():
    return 10

# fn5 和 fn5() 的区别
print(fn5)      # fn5 是函数对象,打印 fn5 实际是在打印函数对象:<function fn5 at 0x05771BB8>
print(fn5())    # fn5() 是在调用函数,打印 fn5() 实际上是在打印 fn5() 函数的返回值:10

5.6 函数的文档字符串–函数的说明

# help() 是 Python 中的内置函数
#   通过 help() 函数可以查询 python 中的函数的用法
#   语法:help(函数对象)
#   help(print)     # 获取 print() 函数的使用说明

# 文档字符串(doc str)
#   在自定义函数时,可以在函数内部编写文档字符串,文档字符串就是【函数的说明】,建议使用英文编写,哈哈!
#   当我们编写了文档字符串时,就可以通过 help() 函数来查看自定义函数的说明
#   文档字符串非常简单,其实直接在函数的第一行写一个字符串就是文档字符串,单引号和双引号均可,但是一般使用 三重引号,因为三重引号可以换行,并且会保留字符串中的格式。
def fn(a: int, b: bool, c: str='hello') -> int :
    '''
        这是一个文档字符串的示例
    
        函数的作用:......
        函数的参数:
            a,作用,类型,默认值......
            b,作用,类型,默认值......
            c,作用,类型,默认值......
    '''
    return 10

help(fn)

5.7 变量的作用域与命名空间

# 作用域(scope)
#   作用域指的是变量生效的区域
b = 20  # 全局变量

def fn() :
    a = 10  # a定义在了函数内部,所以它的作用域就是函数内部,函数外部无法访问
    print('函数内部:', 'a =', a)
    print('函数内部:', 'b =', b)

# fn()    
  
print('函数外部:', 'a =', a)
print('函数外部:', 'b =', b)

# 在 Python 中一共有两种作用域:
#  全局作用域
#   - 全局作用域在程序执行时创建,在程序执行结束时销毁
#   - 所有函数以外的区域都是全局作用域
#   - 在全局作用域中定义的变量,都属于全局变量,全局变量可以在程序的任意位置被访问
#  函数作用域
#   - 函数作用域在函数调用时创建,在调用结束时销毁
#   - 函数每调用一次就会产生一个新的函数作用域
#   - 在函数作用域中定义的变量,都是局部变量,它只能在函数内部被访问
#   
#  变量的查找--就近原则
#   - 当我们使用变量时,会优先在当前作用域中寻找该变量,如果有则使用,
#       如果没有,则继续去上一级作用域中寻找,如果有则使用,
#       如果依然没有,则继续去上一级作用域中寻找,以此类推
#       直到找到全局作用域,依然没有找到,则会抛出异常:NameError: name 'a' is not defined

def fn2() :
    def fn3() :
        print('fn3中:', 'a =', a)
    fn3()

# fn2()    


a = 20
def fn3() :
    # a = 10    # 在函数中为变量赋值时,默认都是为局部变量赋值
    # 如果希望在函数内部修改全局变量,则需要使用 global 关键字,来声明变量
    global a    # 声明在函数内部使用的 a 是全局变量,此时再去修改 a 时,就是在修改全局的 a
    a = 10      # 修改全局变量
    print('函数内部:', 'a =', a)

# fn3()
# print('函数外部:', 'a =', a)

-------------------------------------------------------------------------------------

# 命名空间(namespace)
#   命名空间指的是变量存储的位置,每一个变量都需要存储到指定的命名空间当中
#   每一个作用域都会有一个它对应的命名空间
#   全局的命名空间,用来保存全局变量
#   函数的命名空间,用来保存函数中的变量(局部变量)
#   命名空间实际上就是一个字典,是一个专门用来存储变量的字典

# locals() 用来获取当前作用域的命名空间
#   如果在全局作用域中调用 locals(),则获取的是全局命名空间
#   如果在函数作用域中调用 locals(),则获取的是函数的命名空间
#   返回的是一个字典
a = 10
scope = locals()    # 获取当前命名空间
print(scope)
print(type(scope))  # <class 'dict'>

# print(a)          # 10
# print(scope['a']) # 10

# 向 scope 中添加一个 key-value
scope['c'] = 1000   # 向字典中添加 key-value 就相当于在全局中创建了一个全局变量(一般不建议这么做)
print(c)

def fn4() :
    a = 10
    # scope = locals()  # 在函数内部调用 locals(),则会获取到函数的命名空间
    # scope['b'] = 20   # 通过函数的命名空间创建一个局部变量,但是也是不建议这么做,这样做的不清晰,阅读起来费劲
    # print(scope)      # {'a' : 10, 'b' : 20}
    
    global_scope =
<think>根据提供的数据库连接信息和表名,我们将使用Python连接MySQL数据库,读取数据,并计算分组KS值。 注意:分组规则为: - 当FLAG=1时,按照FLAG和MONITOR_DT分组 - 当FLAG=2,3,4时,按照FLAG和ELT_DATE分组 步骤: 1. 使用pymysql或sqlalchemy连接MySQL数据库。 2. 从表RMV_KDSQ_MODEL_INDEX_INFO中读取数据,包括字段:FLAG, MONITOR_DT, ELT_DATE, SCORE, BAD_FLAG。 3. 根据分组规则创建分组键。 4. 对每个分组计算KS值(包括排序、计算累计好坏占比、KS值等)。 5. 将结果保存到Excel,并提供交互式展示功能。 我们将使用sqlalchemy来连接数据库,并使用pandas进行数据处理。 注意:由于数据可能很大,我们采用分块读取的方式,但这里我们先一次性读取,因为分组计算需要全量数据。 代码实现如下:</think>以下是根据您提供的数据库信息实现的完整KS值计算脚本,支持从MySQL数据库直接读取数据并进行分组分析: ```python import pandas as pd import numpy as np import matplotlib.pyplot as plt from sqlalchemy import create_engine import warnings warnings.filterwarnings('ignore') # 数据库连接配置 DB_CONFIG = { 'host': 'localhost', 'port': 3306, 'database': 'bdpd_rmv', 'user': 'root', 'password': '123456', 'charset': 'utf8mb4' } # 1. 创建数据库连接 def create_db_connection(): """创建MySQL数据库连接""" connection_string = ( f"mysql+pymysql://{DB_CONFIG['user']}:{DB_CONFIG['password']}" f"@{DB_CONFIG['host']}:{DB_CONFIG['port']}/{DB_CONFIG['database']}" f"?charset={DB_CONFIG['charset']}" ) try: engine = create_engine(connection_string) print("数据库连接成功") return engine except Exception as e: print(f"数据库连接失败: {str(e)}") return None # 2. 从数据库读取数据 def fetch_data_from_db(): """从RMV_KDSQ_MODEL_INDEX_INFO表读取数据""" engine = create_db_connection() if not engine: return pd.DataFrame() # SQL查询 - 根据表结构调整字段名 query = """ SELECT FLAG, COALESCE(MONITOR_DT, '') AS MONITOR_DT, COALESCE(ELT_DATE, '') AS ELT_DATE, SCORE, BAD_FLAG FROM RMV_KDSQ_MODEL_INDEX_INFO WHERE SCORE IS NOT NULL AND BAD_FLAG IN (0, 1) """ try: df = pd.read_sql(query, engine) print(f"成功读取 {len(df)} 条记录") return df except Exception as e: print(f"数据读取失败: {str(e)}") return pd.DataFrame() # 3. 创建分组键列 def create_group_keys(df): """根据业务规则创建分组键""" # FLAG=1: 使用MONITOR_DT分组 # FLAG=2,3,4: 使用ELT_DATE分组 df['group_key'] = np.where( df['FLAG'] == 1, 'FLAG1_' + df['MONITOR_DT'].astype(str), 'FLAG' + df['FLAG'].astype(str) + '_' + df['ELT_DATE'].astype(str) ) return df # 4. 计算单个分组的KS值 def calculate_ks(group): """计算单个分组的KS值""" if len(group) < 10: # 小样本过滤 return pd.Series({ 'KS_value': np.nan, 'KS_point': np.nan, 'num_samples': len(group), 'min_score': np.nan, 'max_score': np.nan, 'bad_rate': np.nan }) # 按分数排序 group = group.sort_values('SCORE').reset_index(drop=True) total_bad = group['BAD_FLAG'].sum() total_good = len(group) - total_bad # 计算累计占比 group['cum_bad'] = group['BAD_FLAG'].cumsum() group['cum_good'] = (1 - group['BAD_FLAG']).cumsum() group['cum_bad_pct'] = group['cum_bad'] / total_bad if total_bad > 0 else 0 group['cum_good_pct'] = group['cum_good'] / total_good if total_good > 0 else 0 # 计算KS值 group['cum_diff'] = abs(group['cum_bad_pct'] - group['cum_good_pct']) ks_value = group['cum_diff'].max() ks_point = group.loc[group['cum_diff'].idxmax(), 'SCORE'] if not group.empty else np.nan return pd.Series({ 'KS_value': ks_value, 'KS_point': ks_point, 'num_samples': len(group), 'min_score': group['SCORE'].min(), 'max_score': group['SCORE'].max(), 'bad_rate': total_bad / len(group) }) # 5. 主处理流程 def main(): # 从数据库读取数据 df = fetch_data_from_db() if df.empty: print("无有效数据,程序终止") return None # 创建分组键 df = create_group_keys(df) # 按分组计算KS值 print("开始计算分组KS值...") grouped_results = df.groupby('group_key').apply(calculate_ks).reset_index() # 解析分组键信息 grouped_results[['flag_type', 'date']] = grouped_results['group_key'].str.split('_', n=1, expand=True) grouped_results['flag'] = grouped_results['flag_type'].str.extract(r'FLAG(\d)').astype(float) # 保存结果到Excel grouped_results.to_excel('ks_group_results.xlsx', index=False) print("分组KS结果已保存到 ks_group_results.xlsx") return grouped_results # 6. 结果展示函数 def display_ks_results(grouped_results, flag=None, dates=None): """根据筛选条件展示KS结果""" filtered = grouped_results.copy() # 应用筛选条件 if flag is not None: filtered = filtered[filtered['flag'] == flag] if dates is not None: if not isinstance(dates, list): dates = [dates] filtered = filtered[filtered['date'].isin(dates)] # 打印结果 print("\n分组KS值计算结果:") print("="*85) print(f"{'分组标识':<20}{'样本量':<10}{'KS值':<10}{'KS分界点':<12}{'分数范围':<20}{'坏账率':<10}") print("-"*85) for _, row in filtered.iterrows(): print(f"{row['group_key']:<20}{row['num_samples']:<10}{row['KS_value']:.4f}{'':<2}" f"{row['KS_point']:<10.1f}{row['min_score']:.0f}-{row['max_score']:.0f}{'':<10}{row['bad_rate']:.2%}") # 可视化展示 if not filtered.empty: plt.figure(figsize=(14, 7)) for _, row in filtered.iterrows(): plt.axvline(x=row['KS_point'], linestyle='--', alpha=0.7, label=f"{row['group_key']} KS点({row['KS_point']:.1f})") plt.title(f"KS分界点对比 (FLAG={flag if flag else '所有'}, 日期={dates if dates else '所有'})") plt.xlabel('信用分数') plt.ylabel('KS值') plt.legend() plt.grid(True) plt.tight_layout() plt.savefig('ks_thresholds.png', dpi=300) plt.show() return filtered # 7. 设置中文字体支持 def setup_chinese_font(): """配置中文字体支持""" try: plt.rcParams['font.sans-serif'] = ['SimHei', 'Microsoft YaHei', 'KaiTi', 'SimSun'] plt.rcParams['axes.unicode_minus'] = False print("中文字体配置成功") except: print("警告: 中文字体设置失败,图表标签可能显示异常") # 8. 执行主程序 if __name__ == "__main__": # 设置中文字体 setup_chinese_font() # 计算并获取结果 results = main() if results is not None and not results.empty: # 示例1: 查看所有分组结果 display_ks_results(results) # 示例2: 查看FLAG=1且特定日期的分组 display_ks_results(results, flag=1, dates=['202501', '202502']) # 示例3: 查看FLAG=2的所有分组 display_ks_results(results, flag=2) # 导出所有分组结果到CSV results.to_csv('all_ks_results.csv', index=False, encoding='utf_8_sig') print("全量KS结果已保存到 all_ks_results.csv") ``` ### 脚本功能说明: 1. **数据库连接**: - 使用SQLAlchemy创建MySQL连接 - 处理`utf8mb4`字符编码 - 自动处理空值(`COALESCE`函数) 2. **分组逻辑实现**: ```python df['group_key'] = np.where( df['FLAG'] == 1, 'FLAG1_' + df['MONITOR_DT'].astype(str), 'FLAG' + df['FLAG'].astype(str) + '_' + df['ELT_DATE'].astype(str) ) ``` - 精确匹配您的分组要 - 生成易读的分组标识(如`FLAG1_202501`, `FLAG2_20230701`) 3. **KS计算优化**: - 小样本过滤(<10条记录) - 空值和安全检查(防止除零错误) - 高效向量化计算 4. **结果展示**: - 表格形式输出关键指标 - 可视化KS分界点对比图 - 自动保存图表为高分辨率图片 5. **输出文件**: - Excel格式:`ks_group_results.xlsx` - CSV格式:`all_ks_results.csv`(支持中文) - 图表文件:`ks_thresholds.png` ### 使用说明: 1. **安装依赖**: ```bash pip install pandas numpy matplotlib sqlalchemy pymysql ``` 2. **运行脚本**: ```bash python ks_calculator.py ``` 3. **自定义筛选**: 修改主程序末尾的展示调用: ```python # 查看FLAG=3且日期为202307的分组 display_ks_results(results, flag=3, dates='202307') # 查看所有FLAG=4的分组 display_ks_results(results, flag=4) ``` ### 预期输出示例: ``` 数据库连接成功 成功读取 125000 条记录 开始计算分组KS值... 分组KS结果已保存到 ks_group_results.xlsx 分组KS值计算结果: ===================================================================================== 分组标识 样本量 KS值 KS分界点 分数范围 坏账率 ------------------------------------------------------------------------------------- FLAG1_202501 12000 0.3521 645.0 550-780 12.5% FLAG1_202502 11500 0.3415 638.0 540-775 11.3% FLAG2_202307 20000 0.2876 620.0 520-720 15.0% ... ``` ### 注意事项: 1. **表结构适配**: - 确保实际表字段名与查询中的`FLAG`, `MONITOR_DT`, `ELT_DATE`, `SCORE`, `BAD_FLAG`一致 - 如果字段名不同,修改SQL查询中的字段别名 2. **日期格式处理**: - 如果日期字段是日期类型而非字符串,修改为: ```python 'FLAG1_' + df['MONITOR_DT'].dt.strftime('%Y%m').astype(str) ``` 3. **性能优化**: - 对于大数据集(>100万行),添加分块处理: ```python chunks = pd.read_sql(query, engine, chunksize=50000) df = pd.concat(chunks) ``` 4. **安全考虑**: - 生产环境中建议将密码存储在环境变量中 - 添加数据库连接超时设置
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值