08. Python语言的核心编程 · 第八章 Python语言的函数(上)

1. 函数简介

  函数也是一个 对象
  函数用来保存一些可执行的代码,并且可以在需要时,对这些语句进行 多次调用
  函数能提高应用的模块性,和代码的重复利用率。你已经知道Python提供了许多内建函数,比如print()。但你也可以自己创建函数,这被叫做用户自定义函数。

1.1定义一个函数

  你可以定义一个由自己想要功能的函数,以下是定义函数的 基础规则
   1) 函数代码块以 def 关键词开头,后接 函数标识符名称圆括号 ()
   2) 函数的命名要符合 标识符命名规范
   3) 任何 传入参数自变量 必须放在圆括号中间,圆括号之间可以用于 定义参数;圆括号中的参数与参数之间用 , 进行分隔。
   4) 函数的第一行语句可以 选择性地使用文档字符串,—用于存放函数说明。
   5) 函数内容以冒号后 换行起始,并且要和对应的函数名保持 缩进
   6) return [表达式] 结束函数选择性地返回一个值给调用方不带表达式的 return 相当于返回 None(这一点很重要,在后面“14章 模块的使用”中我会给出一个我当时在测试调用不带 return 的模块函数时不知道为什么返回 None 的案例)。

  Python 定义函数使用 def 关键字,一般格式如下:

# 语法:

def 函数名([形参1,形参2,形参3....]):
    代码块

  默认情况下,参数值参数名称按函数声明 中定义的顺序匹配起来的。

注意:
  函数名必须符合标识符的规范(可以包含字母、数字、下划线但是不能以数字开头)
  print 是函数对象;print() 是调用函数。

1.2 操作函数

  让我们使用函数来输出"Hello World!":

def hello() :
   print("Hello World!")

hello 				# 
print(hello)  		# <function hello at 0x00000243592BE160>
hello()				# Hello World!

  通过以上实例发现
   1) 函数中保存的代码 不会立即执行,需要 调用函数 代码才会执行;
   2) 调用函数方法 : 函数名x()
   3) 单纯的一个 函数名x 没什么特别的意义,它只是一个 函数对象,它就是一个字符串;函数名后面跟一个 () 的意思是:实现 函数名x 的功能

  

2. 函数的参数

  接下来我们通过实现 “定义一个函数,函数功能是:求 任意 两个数。” 这个任务要求来理解什么是 函数的参数

  那怎么实现 “定义一个函数,函数功能是:求任意两个数的和。” ?
  首先我们先定义一个 求和函数,案例一:

def an():
    print(1+3)

an()		# 4

  通过上面这个案例我们确定了一个 求和函数 怎么定义的;但上面案例输出的结果并不正式的算 两个数的和,和 任意 完全没哈关系。那接下来,我们通过尝试着 定义两个变量 来试试看能否补充这两点,案例二:

def an():
    a = 3
    b = 4
    print(a + b)

an()		# 7

  虽然最后输出的结果是不一样了,但说实话,第二个案例虽然比第一个案例多了两个变量,而且 print() 的内容也变了;但细琢磨,其实和第一个案例 没啥区别,换汤不换药;就是把两个数先拉出来放在外面,打印出来的函数的计算结果是两个固定变量里面值的和,不能改变的还是不能改变。那我们怎么来实现 任意 这个效果?

  在思考怎么来实现 任意 这个效果前,我们要先确定:在调用函数时,print() 里的变量是 在 定义函数 时候 赋值 呢 还是 在 调用函数 的时候 赋值 呢?这个”求任意两个数的和“的函数希望它实现怎么一个功能呢?
  那么我们首先要理解清晰:计算的两个数 能确定吗?是不是 不能确定。换个思路是不是就是 调用函数的时候赋值。那怎么实现这个 调用函数的时候赋值?这个时候,我们就需要使用到函数的参数。来看案例三:

def an(a,b):
    # a = 3
    # b = 4
    print(a + b)

an(100,2)		# 102

  在定义函数的时候,可以在函数后面的 () 里定义 数量不等形参,多个形参之间使用 , 分隔开。

2.1 形参和实参

  形参(形式参数) 定义:形参就相当于在函数内部声明了 变量,但是 并不是赋值

def an(a,b):
    a = None
    b = None
    print(a + b)

an()		# TypeError: an() missing 2 required positional arguments: 'a' and 'b'

  实参(实际参数) 指定对应 了函数在 创建时定义形参。在调用函数时必须传递 实参实参将会赋值给对应的形参,简单来说:有几个形参就要有几个实参

  实参与形参之间传递的一些操作:

def ints(a,b,c,d):

    print(a,b,c,d)

ints(1,4,7,5)			# 1 4 7 5

#--------------------------------------------------------

def ints(a,b,c,d):

    print(a,b,c,d)

ints(1,4,7,)			# TypeError: ints() missing 1 required positional argument: 'd'

#--------------------------------------------------------

def ints(a,b,c,d):

    print(a,b,c,d)

ints(1,4,7,5,8,9)		# TypeError: ints() takes 4 positional arguments but 6 were given

#--------------------------------------------------------

def ints(a,b,*c):

    print(a,b,c)

ints(1,4,7,5,8,9)		# 1 4 (7, 5, 8, 9)

#--------------------------------------------------------

def ints(*a,b,c):

    print(a,b,c)

ints(1,4,7,5,8,9)		# TypeError: ints() missing 2 required keyword-only arguments: 'b' and 'c'

#--------------------------------------------------------

def ints(**a,b,c):

    print(a,b,c)

ints(1,4,7,5,8,9)		# SyntaxError: invalid syntax

2.2 函数的传递方式

  定义形参时,可以为形参指定默认值。指定了默认值以后,如果用户传递了参数则默认值 不会生效。如果用户没有传递,则默认值 会自动生效
  位置参数:位置参数就是将对应位置的实参赋值给对应位置的形参;
  关键字参数 : 关键字参数可以不按照形参定义的顺序去传递,而根据参数名进行传递;
  混合使用位置参数和关键字参数的时候,必须将位置参数写到关键字参数前面去

def fn(a=10, b=20, c=30):		# 默认值传参
    print('a =', a)
    print('b =', b)
    print('c =', c)
    print('a + b + c =', a + b + c)

fn()
# a = 10
# b = 20
# c = 30
# a + b + c = 60

fn(40, 60, 80)					# 位置传参
# a = 40
# b = 60
# c = 80
# a + b + c = 180

fn(b=90, c=80, a=70)			# 关键字传参
# a = 70
# b = 90
# c = 80
# a + b + c = 240

fn(40, c=60)					# 混合传参
# a = 40
# b = 20
# c = 60
# a + b + c = 120

fn(c=60, 80)					# 混合传参
#   File "G:/lianxi/测试.py", line 10
#     fn(c=60, 80)			# 混合传参
#              ^
# SyntaxError: positional argument follows keyword argument

2.3 实参的类型

  计算机数据有很多类型,如:整型(int)、浮点型(float)、字符串类型(str)、列表类型(list)、字典类型(dict)、布尔类型(bool) 等等;那 实参 能传递那些类型呢?不同类型之间的传递又有哪些限制和注意呢?

  1. 传递一个整数(int):
def fn2(a):
    print('a =',a)

fn2(123)			# a = 123
  1. 传递一个变量:
def fn2(a):
    print('a =',a)
    
b = 456
fn2(b)			# a = 456
  1. 传递一个 布尔值(bool)
def fn2(a):
    print('a =' ,a)

b = False
fn2(b)			# a = False
  1. 传递一个 空值(None)
def fn2(a):
    print('a =' ,a)

b = None
fn2(b)			# a = None
  1. 传递一个 字符串(str)
def fn2(a):
    print('a =' ,a)

b = 'Python'
fn2(b)			# a = Python
  1. 传递一个 方法(fn)
def fn(a=10, b=20, c=30):
    print('a =', a)
    print('b =', b)
    print('c =', c)
    print('a + b + c =', a + b + c)


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


fn2(fn)			# a = <function fn at 0x00000215E875E160>
				# 记住:<> 的意思是“对象”。
				# 打印返回的内容解释的意思是:返回对象的类型是一个 function ,这个 function 的名字叫 fn ,这个对象所在的内存位置是 0x00000215E875E160(十六进制地址)
  1. 老生常谈,不同的数据类型之间不能进行数据运算;
def fn2(a,b):
    print(a + b)


fn2(1,'2')      # TypeError: unsupported operand type(s) for +: 'int' and 'str'

  1. 可变对象不可变对象
def fn1(a):

    print('a =', a)

c = 10

fn1(c)				# a = 10
print('c =', c)		# c = 10

#---------------------------------------------------------

def fn1(a):
    
    a = 40			# 在函数中对形参进行赋值,不会影响到其他的变量。
    print('a =', a)

c = 10

fn1(c)				# a = 40
print('c =', c)		# c = 10

#---------------------------------------------------------

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

c = [1,2,3]

fn1(c)				# a = [1, 2, 3]
print('c =', c)		# c = [1, 2, 3]

#---------------------------------------------------------

# 一个列表一个对象,当我们通过 形参 去修改对象的时候,将会影响到所有指向该对象的 变量。
# 改变量,改的只是变量内部的值;改变量 值会变,id不变。
# 改对象,改的是整个对象的 存储id。改对象 真正的值不会变,id改变。与其说是“改对象”不如说是:基于原先的数据基础新建一个对象。

def fn1(a):
    
    a[0] = 40			# a 是一个列表,a 修改列表中的一个元素,a 修改了一整个对象
   						
    print('a =', a)

c = [1,2,3]

fn1(c)				# a = [40, 2, 3]
print('c =', c)		# c = [40, 2, 3]

#---------------------------------------------------------

# 如果我们传递的是一个 可变对象 时,而我们又不希望因为函数内部的操作而影响到了函数外部的 可变对象 ,那么我们可以使用 copy() 或 切片 等方法制造可变对象·副本来保持 可变对象 原本的值。

def fn1(a):
    a[0] = 40
    print('a =', a, id(a))


c = [1, 2, 3]

fn1(c[:])                   # a = [40, 2, 3] 2272265997376
fn1(c.copy())               # a = [40, 2, 3] 2272265997376
print('c =', c, id(c))      # c = [1, 2, 3] 2272265769600

  

3. 不定长参数

  你可能需要 一个函数能处理比创建函数是 声明更多的参数。这个参数叫做 不定长参数,和上述几种参数不同,声明时 不定长参数不会命名。而是在形参前面加一个 * ,这样这个形参可以获取到所有的实参,它会将所有的实参保存到一个元组中。这种方法简称为:装包。

  基本语法如下:

def fun(形参1, 形参2, *形参3):
   代码块

  语法实例:

def fun(*a):
   print ('a = ',a,type(a))

fun(1,2,3,4,5)		# a =  (1,2,3,4,5) <class 'tuple'>

  带 * 号的形参只能有一个,可以和其他参数配合使用

def fun(a, b, *c):
   print ('a = ',a)
   print ('b = ',b)
   print ('c = ',c)

fun( 70, 60, 50, 40, 30, 20, 10 )

# a =  70
# b =  60
# c =  (50, 40, 30, 20, 10)

#--------------------------------------------------------------------------

def fun(*a):
    # 定义一个变量,用来保存结果
    result = 0
    # 遍历元组,并将元组中的元素进行累加
    for x in a:
        result += x
		# print(result)
    print(result)           # 在循环外打印最后的结果,和在循环内打印结果是不一样的。不理解的自己尝试。

fun(1,2,3,4,5,6,7)			# 28

   * 形参只能接受位置参数,不能接受关键字参数

def ints(*a):
    print('a =',a)

ints(b=8,c=9,d=7)			# TypeError: ints() got an unexpected keyword argument 'b'

   最后就是注意了。
    注意1. 带 * 号的参数只能有一个,如果出现多个就会报错:

def ints(a,*b,*c):

    print(a,b,c)

ints(1,4,7,5,8,9)		# SyntaxError: invalid syntax

    注意2. 不定长参数不一定都写在最后面,但是要注意带 * 的参数后面的所有参数必须以关键字的形式来传递,否则 带 * 的参数只能做最后一个参数:

def ints(a,*b,c):

    print(a,b,c)

ints(1,4,7,5,8,9)		# TypeError: ints() missing 1 required keyword-only argument: 'c'

#--------------------------------------------------------------------------

def ints(*a,b,c):

    print(a,b,c)

ints(1,4,7,5,8,9)		# TypeError: ints() missing 2 required keyword-only arguments: 'b' and 'c'

#----------------------      but      ------------------------------------

def ints(a,*b,c):

    print(a,b,c)

ints(1,4,7,5,8,c=9)		# 1 (4, 7, 5, 8) 9

#----------------------      but      ------------------------------------

def ints(*a,b,c):

    print(a,b,c)

ints(1,4,7,5,b=8,c=9)		# (1, 4, 7, 5) 8 9

#--------------------------------------------------------------------------

def ints(*a,b,c):

    print(a,b,c)

ints(1,4,b=7,5,8,c=9)
#   File "G:/lianxi/测试.py", line 5
#     ints(1,4,b=7,5,8,c=9)		#
#                  ^
# SyntaxError: positional argument follows keyword argument

  还有一种就是参数带两个星号 **基本语法如下:

def ints(**a):
    print('a =',a,type(a))

ints(b=8,c=9,d=7)			# a = {'b': 8, 'c': 9, 'd': 7} <class 'dict'>

  ** 形参可以接收其他的 关键字参数,它会将这些参数统一保存到 字典 当中。字典的 键(key) 就是参数的名字,字典的 值(value) 就是参数的值

  带 ** 的形参只有一个,并且必须写在所有参数的后面。

  总结: *a 处理的是位置参数,**a 处理的是关键字参数。

  最后还有附加一点,声明函数时,参数中星号 * 可以单独出现,例如:

def f(a,b,*,c):
    return a+b+c

  如果单独出现星号 * 后的参数必须用关键字传入。

def f(a,b,*,c):
    print(a+b+c)

# f(1,2,3)		# TypeError: f() takes 2 positional arguments but 3 were given

f(1,2,c=3)

  

4. 参数的解包

  前面提到过一个词,叫 装包。即然有 装包,那么就一定有 解包 了。接下来我们说一说 参数的解包

def fn(a,b,c):
    print('a =', a)
    print('b =', b)
    print('c =', c)

# 创建一个元组
t = (20,30,40)

fn(t)			# TypeError: fn() missing 2 required positional arguments: 'b' and 'c'

#--------------------------------------------------------------------------

def fn(a,b,c):
    print('a =', a)
    print('b =', b)
    print('c =', c)

# 创建一个元组
t = (20,30,40)

fn(t[0],t[1],t[2])
# a = 20
# b = 30
# c = 40

#--------------------------------------------------------------------------

def fn(a,b,c):
    print('a =', a)
    print('b =', b)
    print('c =', c)

# 创建一个元组
t = (20,30,40)

fn(*t)
# a = 20
# b = 30
# c = 40

  传递实参时,也可以 在序列类型的参数前 添加 * 星号,这样它会 自动 的将序列中元素依次作为参数传递;
  并且添加 * 星号的序列类型的参数中 序列的元素个数 必须和 形参的个数 保持一致。

def fn(a,b,c):
    print('a =', a)
    print('b =', b)
    print('c =', c)

# 创建一个元组
t = (20,30,40,50)

fn(*t)			# TypeError: fn() takes 3 positional arguments but 4 were given

#--------------------------------------------------------------------------

def fn(a,b,*c):
    print('a =', a)
    print('b =', b)
    print('c =', c)

# 创建一个元组
t = (20,30,40,50)

fn(*t)
# a = 20
# b = 30
# c = (40, 50)

  除了 *t 对元组的解包外,还有 **d 对字典的解包操作:

def fn(a,b,c):
    print('a =', a)
    print('b =', b)
    print('c =', c)

# 创建一个字典
d = {'a':1,'b':2,'c':3}

fn(**d)
# a = 1
# b = 2
# c = 3

  老生常谈的解包注意点:添加 ** 星号的序列类型的参数中 字典的键·值对个数 必须和 形参的个数 保持一致。

  

总结小便条

本篇文章主要讲了以下几点内容:

  1. 函数简介
     函数也是一个对象。
     函数用来保存一些可执行的代码,并且可以在需要时,对这些语句进行多次调用。
    注意:
     函数名必须符合标识符的规范(可以包含字母、数字、下划线但是不能以数字开头);
     print 是函数对象,而 print() 是调用函数。
  2. 函数的参数
    2.1 形参 和 实参
     形参(形式参数) 定义形参就相当于在函数内部声明了变量,但是并不是赋值;
     实参(实际参数)指定了形参,那么在调用函数时必须传递实参,实参将会赋值给对应的形参,简单来说有几个形参就要有几个实参。
    2.2 函数的传递方式
     定义形参时,可以为形参指定默认值。指定了默认值以后,如果用户传递了参数则默认值不会生效。如果用户没有传递,则默认值就会生效;
     位置参数:位置参数就是将对应位置的实参赋值给对应位置的形参;
     关键字参数 : 关键字参数可以不按照形参定义的顺序去传递,而根据参数名进行传递;
     混合使用位置参数和关键字参数的时候必须将位置参数写到关键字参数前面去。
  3. 不定长参数
     定义函数时,可以在形参前面加一个 * ,这样这个形参可以获取到所有的实参,它会将所有的实参保存到一个元组中。
     带 * 号的形参只能有一个,可以和其他参数配合使用;
      * 形参只能接受位置参数,不能接受关键字参数;
      ** 形参可以接收其他的关键字参数,它会将这些参数统一保存到字典当中。字典的 键(key) 就是参数的名字,字典的 值(value) 就是参数的值;
      ** 形参只有一个,并且必须写在所有参数的后面。
  4. 参数的解包
     传递实参时,也可以在序列类型的参数前添加星号 * 或双星号 ** ,这样它会自动的将序列中元素依次作为参数传递;
     要求序列中的元素的个数必须和形参的个数一致。

  本章回顾暂时就到这了,如果还有点晕,那就把文章里所有引用的案例代码再敲几遍吧。拜拜~

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章