【Python零基础快速入门系列 09】高级程序员绝世心法——模块化之函数封装_程序中模块的封装(2)

img
img

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

1. 函数概述

官方定义:函数是组织好的,可重复使用的,用来实现单一,或相关联功能的代码段。

为什么要用函数?在回答这个问题之前,我们先看两张机房接线的对比图:

image-20220514220513417

左侧是堆积如山凌乱的排线,对于运维工作人员来说,每次维护都是上刑场;右侧是按功能区分不同颜色整齐整洁的排线,对运维人员来说,只需要快速定位问题就好了。

无论任何一种编程语言,我们都有同样的要求:高内聚、低耦合。什么叫做高内聚,就是同样功能或相似功能的代码聚合在一起,不要相似的代码这个代码文件有一点,那个代码文件也有一点,分散在不同的代码文件中;什么叫做低耦合,不相关代码不要混淆在一起,你中有我,我中有你。

举个高耦合的例子,初级程序员经常犯的毛病就是爱用全局变量,然后整个代码工程文件都在使用。这种用法有非常多的缺点:

  • 不好维护,全局变量在很多文件中都在使用及处理,在发生变更时非常容易遗漏;
  • 对于多线程、多进程业务来说,会有同步问题;
  • 维护难度高,要是交给别的开发者维护,简直就是灾难,通俗的话叫做“屎山”;
  • 基本上难以移植,不能形成可复用的代码资产;

而要实现高内聚、低耦合的方式就是模块化。

说到模块化,提一个问题:一个成熟的代码工程的层次结构是怎样的?

代码工程,代码多级分层,模块文件,类,函数,代码块。

以华为OpenHarmony项目为例,越是复杂的系统,代码分层越多,但是总体都符合上面描述的代码层次结构。

3

代码块是一个功能的最小组成单元,而要实现功能的封装,就需要用到函数来实现。函数实现了功能的内聚封装,逻辑低耦合。

以嵌入式开发中常见的串口驱动模块举例吧。

硬件驱动层

  • 串口驱动模块文件
    • 串口初始化函数
      • 串口初始化相关代码块集合
    • 串口发送函数
    • 串口接收函数
    • 串口释放函数
  • 指示灯驱动模块文件
  • AD采样模块文件

从串口驱动模块举例可以看到,相关功能的代码块集合封装成函数,多个相关函数的集合封装成模块文件,多个模块文件构成代码分层级,多个代码分层最终实现了产品的功能。

常说万丈高楼平地起,编程也是一样,一个个的函数就像积木一样组合在一起,最终成为万丈高楼。

高内聚、低耦合基本是初级程序员走上高级程序员所必须要悟到的绝世心法。

所以函数到底是什么?相关联的、功能单一的代码集合,隔离无关代码,高度聚焦的、可重复使用,易于移植的代码封装。

2. 函数分类

2.1 内置函数

像print、len、input、range这些都属于内置函数,python内部定义好的函数,可以直接使用。

2.2 自定义函数

除了内置函数之外,开发者还可以自定义函数,实现想要的功能,从而达到一次编写、多次调用的目的

3. 函数的定义

3.1 函数的结构

def [函数名]([函数的参数]):  # 参数根据需要来,可以没有参数
    [函数实现代码块]
    return [返回值] #可以不写,无return就无返回

  • 函数名
    • 函数名命名规则和变量一样,可以为字母、数字、下划线,不可以数字开头;
  • 函数参数
    • 函数的参数作为输入输出使用,对于不可变数据类型只能作为输入使用,可变数据类型可以传递输出;
    • 注意参数后面有个冒号:限定代码块范围属于函数
    • 形参与实参:
      • 函数定义时,括号内的参数为形参;函数调用时,括号内的参数为实参
  • 函数代码块
    • 函数功能的具体实现
  • 返回值
    • 返回值根据需要选择,可以有,也可以无, 无返回值时,函数中的值无法传递到函数外。
    • 如果返回值有多个,以元组的形式返回
    • return关键字有终止函数运行的功能,return关键字后面的代码不会被执行

举个例子:

# 函数的定义
def multiply(x, y):
    z = x * y
    return z

# 函数的调用
a = multiply(5, 6)
a

5

3.2 函数参数详解

3.2.1 位置参数

根据函数定义时参数的位置一一对应。

  • 函数调用时,位置参数传入顺序必须和定义时一致;
  • 函数调用时,参数的数量必须和定义时一致;
  • 斜杠“/”之前的变量,只能通过位置方式传递参数,不能通过关键字形式传递参数,例如:
# 函数的定义
def multiply(x, y):
    print(f"x={x}, y={y}")
    
    z = x * y
    return z

# 函数的调用
a = multiply(5, 6)
a

# 输出
x=5, y=6
30

交换位置:

# 函数的定义
def multiply(x, y):
    print(f"x={x}, y={y}")
    
    z = x * y
    return z

# 函数的调用
a = multiply(6, 5)
a

# 输出
x=6, y=5
30

可以看到参数的取值变了。

深度学习的一些函数,,例如Conv2D,参数特别多,位置写错,可能训练结果差之千里,一定要注意顺序。

tf.keras.layers.Conv2D(
    filters,
    kernel_size,
    strides=(1, 1),
    padding='valid',
    data_format=None,
    dilation_rate=(1, 1),
    groups=1,
    activation=None,
    use_bias=True,
    kernel_initializer='glorot_uniform',
    bias_initializer='zeros',
    kernel_regularizer=None,
    bias_regularizer=None,
    activity_regularizer=None,
    kernel_constraint=None,
    bias_constraint=None,
    **kwargs
)

斜杠“/”之前的变量,只能通过位置方式传递参数,不能通过关键字形式传递参数,例如:

with open(file="demo1.txt", mode="r", encoding='gbk') as f:
    f.readline??

Signature: f.read(size=-1, /)
Docstring:
Read at most n characters from stream.

Read from underlying buffer until we have n characters or we hit EOF.
If n is negative or omitted, read until EOF.
Type:      builtin_function_or_method

可以看到函数描述中,参数末尾有一个斜杠,表示斜杠前只能使用位置参数,如果使用关键字会报错:

with open(file="demo1.txt", mode="r", encoding='utf-8') as f:
    print(f.readline(size=4))

---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
/media/zhoushimin/Work/220_StudyUpUp/新媒体博客同步空间/BaiduSyncdisk/新媒体/Python快速入门系列/python14-file.ipynb Cell 37' in <cell line: 1>()
      1 with open(file="demo1.txt", mode="r", encoding='utf-8') as f:
----> 2     print(f.readline(size=4))

TypeError: readline() takes no keyword arguments

3.2.2 默认参数

默认参数用于在函数未传递参数时使用默认值处理,例如输入的数据集某一个样本某个特征数据缺失,就可以使用一个默认值填充,然后进行后续处理。

举个例子, 当乘法函数只输入一个参数时,y默认取1:

# 函数的定义
def multiply(x, y=1):
    print(f"x={x}, y={y}")
    
    z = x * y
    return z

# 函数的调用
a = multiply(5)
a

# 输出
x=5, y=1
5

注意事项:

  • 默认参数必须位于参数列表的最右端,默认参数的后面不能有位置参数,否则解释器会无法识别报错。
# 函数的定义
def multiply(y=1, x):
    print(f"x={x}, y={y}")
    
    z = x * y
    return z

# 函数的调用
a = multiply(5)     # 5到底是y呢,还是x,无法解释
a

提示错误为:默认参数后跟着非默认参数。

  • 默认参数定义时一定要使用不可变数据类型,否则结果可能会超出预期;
# 函数的定义
def list_add(x=[]):
"""
参数x为默认参数,目的是没有参数传入时,返回一个[0]
"""
print(f"x={x}")

x.append(0)

return x

# 函数的调用
a = list_add()     
print("return:", a)
a = list_add()     
print("return:", a)
a = list_add()     
print("return:", a)

# 输出
x=[] 
return: [0] 
x=[0] 
return: [0, 0] 
x=[0, 0] 
return: [0, 0, 0]

可以看到x的输入值发生了变化,第一次被调用时,x增加了一个[0],第二次作为输入时,已经不是[]了。

出现这种问题的原因是什么呢?

首先来看一下变量参数传递的机制:首先变量作为参数传递到函数中时,函数会产生一个变量的副本,在函数中对参数的操作,其实都是针对变量的副本的操作,举例说明:

# 函数的定义
def echo(x):
print(f"2.x = {x}, id(x):{id(x)}")
# x的值在函数中已经发生了变化
x = 6
print(f"3.x = {x}, id(x):{id(x)}")

return x

k = 5
print(f"1.k = {k}, id(k):{id(k)}")
# 函数的调用
a = echo(x = k)    

# 在函数外打印k的值,发现k的值没有发生变化
print(f"4.k = {k}, id(k):{id(k)}")

1.k = 5, id(k):140721390874496		# k原始值和直接引用的对象地址
2.x = 5, id(x):140721390874496		# k作为参数传递给函数后,其值及直接引用的对象地址,发现没有变化
3.x = 6, id(x):140721390874528		# 在函数中修改参数,其值及直接引用的对象地址,发生了变化
4.k = 5, id(k):140721390874496		# 出函数后,发现k值没有发生变化

从上面的结果可以看到,以不可变类型作为参数传递,其在函数内操作的其实是另外一个变量,在函数内对参数的修改不会影响到它的值。

而可变数据类型为参数传递时,虽然变量作为参数传递到函数中也会产生一个变量的副本,但是其是间接引用的,它们会间接引用到内存堆中同一个列表/字典对象,因此在函数中对可变数据类型的操作都会反馈到函数外的可变数据变量上。

如何避免这种情况呢,默认参数使用不可变数据类型.

# 函数的定义
def list_add(x=None):
"""
参数x为默认参数,目的是没有参数传入时,返回一个[0]
"""
print(f"x={x}")

if x == None:
x = [0]

return x

# 函数的调用
a = list_add()     
print("return:", a)
a = list_add()     
print("return:", a)
a = list_add()     
print("return:", a)

# 输出
x=None 
return: [0] 
x=None 
return: [0] 
x=None 
return: [0]

3.2.3 命名关键参数

命名关键参数主要体现在函数调用的时候:函数调用的时候,指定参数名的参数

调用函数时,命名关键参数可以和位置参数一起用,但是命名关键参数必须在位置参数的后面

另外在函数定义阶段,在关键字参数前增加一个”*”,表明后续的参数都是命名关键字参数,强制性必须按照命名关键字参数的用法使用,不可省略。

# 命名关键参数

def multiply(x, *, y):		# *号后面的是命名关键字参数,
    z = x * y
    return z

# 函数的调用
a = multiply(5, y=6)
a

# 输出
30

错误用法示例:命名关键参数在位置参数的前面

# 命名关键参数-错误用法

def multiply(x, y):
    z = x * y
    return z

# 函数的调用
a = multiply(y=6, 5)
a

  File "<ipython-input-30-d5b8c3d32826>", line 8
    a = multiply(y=6, 5)
                     ^
SyntaxError: positional argument follows keyword argument

提示语法错误:命名关键参数在位置参数的前面

3.2.4 可变参数

Python函数提供了可变参数,来方便进行参数个数未知时的调用。可变参数将以tuple形式传递。

格式: *参数 (即在参数前加*号)

def getsum(*num):
    sum = 0
    for n in num:
        sum += n
    return sum

list = [2, 3, 4]

print(getsum(1, 2, 3))
print(getsum(*list))
#结果:6 9

序列的打包:当定义函数的时候,在函数参数的前面加 * ,将元素打包成元组的形式

序列的拆包:当函数执行的时候,在实际参数的前面加 * ,将序列进行拆包

def getsum(*num):   # 参数序列的拆包
    print(num)
    sum = 0
    for n in num:
        sum += n
    return sum

list = [2, 3, 4]

print(getsum(1, 2, 3))
print(getsum(*list))        # 参数序列的打包
#结果:6 9

# 输出
(1, 2, 3)
6
(2, 3, 4)			# 对列表整个打包成元组作为参数传递进来
9

3.2.5 关键参数

Python的可变参数以tuple形式传递,而关键字参数则是以dict形式传递。 即可变参数传递的是参数值,关键字参数传递的是参数名:参数值键值对。

形式:**kw 这是惯用写法,建议使用,容易被理解

def personinfo(name, age, **kw):
    print('name:', name, 'age:', age, 'ps:', kw)
    
personinfo('Steve', 22)
personinfo('Lily', 23, city = 'Shanghai')
personinfo('Leo', 23, gender = 'male',city = 'Shanghai')

3.2.6 各种参数之间组合

一次函数调用可以传递以上所述任何一种参数或者多种参数的组合,当然也可以没有任何参数。正如默认参数必须在最右端一样,使用多种参数时也对顺序有严格要求,也是为了解释器可以正确识别到每一个参数。

顺序:位置参数、默认参数、可变参数、命名关键字参数和关键字参数。

def function(a, b, c=0, *, d, **kw):
    print(f'a = {a}, b ={b}, c = {c}, d = {d}, kw = {kw}')

function(5, 6, d=36, e=99, f=27)

a = 5, b =6, c = 0, d = 36, kw = {'e': 99, 'f': 27}

4. 函数的注释

举个例子:

# 函数的定义
def multiply(x, y):
    """函数注释:乘法的实现

 Args:
 x (\_type\_): 乘数
 y (\_type\_): 被乘数

 Returns:
 \_type\_: 乘积
 """
    z = x \* y
    return z

# 函数的调用
a = multiply(5, 6)
a	

# 输出
30

img
img

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!



a = 5, b =6, c = 0, d = 36, kw = {‘e’: 99, ‘f’: 27}


## 4. 函数的注释


举个例子:



函数的定义

def multiply(x, y):
“”"函数注释:乘法的实现

Args:
x (_type_): 乘数
y (_type_): 被乘数

Returns:
_type_: 乘积
“”"
z = x * y
return z

函数的调用

a = multiply(5, 6)
a



输出

30



[外链图片转存中...(img-f77hPXJf-1715400869280)]
[外链图片转存中...(img-giEc1L33-1715400869280)]

**网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。**

**[需要这份系统化资料的朋友,可以戳这里获取](https://bbs.csdn.net/forums/4f45ff00ff254613a03fab5e56a57acb)**


**一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**

  • 5
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值