首页 > 技术文章 > (董付国)Python 学习笔记---Python函数设计与使用(1)

erice-he 2019-08-30 20:53 原文

5.1 函数定义

  • 将可能需要反复执行的代码封装为函数,并在需要该功能的地方进行调用,不仅可以实现代码复用,更重要的是可以保证代码的一致性,只需要修改该函数代码,所有调用均会受影响。
  • 设计函数时,应该注意提高模块的内聚性,同时降低模块之间的隐式耦合(就是规函数之间只是通过模块之间建立联系)
  • 在实际项目开发中,往往会把一些通用的函数封装到一个模块中,并把这个通用模块文件放到顶层文件夹中,这样更方便管理。
  • 在编写函数时,应尽量减少副作用,尽量不要修改参数本身,不要修改除返回值以外的其他内容。
  • 应充分利用Python函数式编程的特点,让自己定义的函数尽量符合纯函数式编程的要求,例如保证线程安全、可以并行运行等等(充分发挥计算机多核的性能)。

函数定义语法:
def 函数名([参数列表]):
▢‘’‘注释’‘’
▢函数体

  • 注意事项:
    1.函数形参不需要声明其类型,也不需要指定函数返回值类型
    2.即使该函数不需要接受任何参数,也必须保留一对空的圆括号
    3.括号后面的冒号必不可少
    4.函数体相对于def关键字必须保持一定的空格缩进
    5.Python允许嵌套定义函数(尽量避免嵌套)

  • 生成斐波那契数列的函数定义和调用

>>> def fib(n):
...     a,b = 0,1
...     while a<n:
...             print(a,end = ' ')
...             a,b = b,a+b
...     print()
...
>>> fib(100)
0 1 1 2 3 5 8 13 21 34 55 89
>>>
>>>
>>> def fib(n):
...     a,b = 1,1
...     while a<n:
...             print(a,end = ' ')
...             a,b = b,a+b
...     print()
...
>>> fib(100)
1 1 2 3 5 8 13 21 34 55 89
  • Python是一种高级的动态编程语言,变量类型是可以随时改变的。Python中的函数和自定义对象的成员也是可以随时发生改变的,可以为函数和自定义对象动态增加新成员。
>>> def func():
...     print(func.x)                   #查看函数func的成员x
...
>>> func()                              #目前还没有成员x,出错
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 2, in func
AttributeError: 'function' object has no attribute 'x'
>>> func.x = 3                          #动态为函数增加新成员
>>> func()
3
>>> func.x
3
>>> del func.x
>>> func.x
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'function' object has no attribute 'x'
>>> dunc()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'dunc' is not defined
  • 函数的递归调用是函数调用的一种特殊情况,函数调用自己,自己再调用自己,自己再调用自己,…,当某个条件得到满足的时候就不再调用了,然后再一层一层地返回直到该函数第一次调用的位置(当调用层次过深就会导致栈的崩溃)。
    在这里插入图片描述
    5.2 形参与实参
  • 函数定义时括弧内为形参,一个函数可以没有形参,但是括弧必须要有,表示该函数不接受参数。
  • 函数调用时间向其传递实参,将实参引用(不同于C,C++)传递给形参。
  • 在定义函数时,对参数个数并没有限制,如果有多个形参,需要使用逗号进行分割。
  • 对于绝大多数情况下,在函数内部直接修改形参的值不会影响实参,而是创建一个新变量。例如:
>>> def addOne(a):
...     print(id(a),' : ',a)
...     a+=1
...     print(id(a),' : ',a)
...
>>> v = 3
>>> id(v)
140709150224704
>>> addOne(v)
140709150224704  :  3
140709150224736  :  4
>>> v
3
>>> id(v)
140709150224704

列表、字典、集合内的数据是可变的~

  • 在有些情况下,可以通过特殊的方式在函数内部修改实参的值,例如下面的代码:
>>> def modify(v):              #修改列表元素的值
...     v[0] = v[0] + 1
...
>>> a = [2]
>>> modify(a)
>>> a
[3]
>>> def modify(v,item):         #为列表增加元素
...     v.append(item)
...
>>> a = [2]
>>> modify(a,3)
>>> a
[2, 3]
  • 也就是说,如果传递给函数的是可变序列(列表、字典、集合),并且在函数内部使用下标或可变序列自身的方法(append)增加、删除元素或修改元素时,实参也得到相应的修改。
>>> def modify(d):              #修改字典元素或为字典增加元素
...     d['age'] = 38
...
>>> a = {'name':'Dong','age':37,'sex':'Male'}
>>> modify(a)
>>> a
{'name': 'Dong', 'age': 38, 'sex': 'Male'}

5.3 参数类型

  • 在Python中,函数参数有很多种:可以为普通参数、默认值参数、关键参数、可变长度参数等等。
  • Python在定义函数时不需要指定形参的类型,完全由调用者传递的实参类型以及Python解释器的理解和判断来决定,类似于于重载和泛型。
  • Python函数定义时也不需要指定函数的类型,这将由函数中的return语句来决定,如果没有return语句或者return没有得到执行,则认为返回空值None。
  • Python支持对函数参数和返回值类型的标注,但实际上并不起任何作用,只是看起来方便。
>>> def test(x:int,y:int) -> int:
...     '''x and y must be integers,return an integer x+y'''
...     assert isinstance(x,int),'x must be integer'
...     assert isinstance(y,int),'y must be integer'
...     z = x+y
...     assert isinstance(z,int),'z must be integer'
...     return z
...
>>> test(1,2)
3
>>> test(1,3.0)                 #类型不符合,assert函数会抛出异常
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 4, in test
AssertionError: y must be integer
  • 位置参数(position arguments)是比较常用的形式,调用函数时实参和形参的顺序必须严格一致,并且实参和形参的数量必须相同。
>>> def demo(a,b,c):
...     print(a,b,c)
...
>>> demo(1,2,3)
1 2 3
>>> demo(1,2,3,4)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: demo() takes 3 positional arguments but 4 were given

5.3.1 默认值参数

  • 默认值参数必须出现在函数参数列表的最右端,任何一个默认值参数右边不能有非默认值参数。调用带有默认值参数的函数时,可以不对默认值参数进行赋值,也可以为其赋值,具有很大的灵活性。
>>> def say(message,time = 1):
...     print(message * time)
...
>>> say('hello')
hello
>>> say('6',6)
666666
  • 下面的函数使用指定分隔符将列表中所有字符串元素连接成一个字符串。
>>> def join(List,sep = None):
...     return (sep or ' ').join(List)
...
>>> aList = ['a','b','c']
>>> join(aList)
'a b c'
>>> join(aList,',')
'a,b,c'
  • 默认值参数如果使用不当,会导致很难发现的逻辑错误,例如:
>>> def demo(newitem,old_list = []):
...     old_list.append(newitem)
...     return old_list
...
>>> print(demo('5',[1,2,3,4]))
[1, 2, 3, 4, '5']
>>> print(demo('aaa',['a','b']))
['a', 'b', 'aaa']
>>> print(demo('a'))
['a']
>>> print(demo('b'))
['a', 'b']

这是为什么呢?

  • 原因在于默认值参数的赋值只会在函数定义时被解释一次。当使用可变序列作为参数默认值时,一定要谨慎操作。
  • 如何改正呢?
def demo(newitem,old_list = None):		#如果是空值
    if old_list is None:				#则给他创建一个空列表
        old_list = []
    new_list = old_list[:]				#把一个切片赋给new_list
    new_list.append(newitem)
    return new_list

print(demo('5'),[1,2,3,4])
print(demo('aaa',['a','b']))
print(demo('a'))
print(demo('b'))
['5'] [1, 2, 3, 4]
['a', 'b', 'aaa']
['a']
['b']
  • 注意:
    1.默认值参数只在函数定义时被解释一次
    2.可以使用“函数名 .__ defaults __”查看所有默认参数的当前值
>>> i = 3
>>> def f(n = i):		#参数n的值仅取决于i的当前值
...     print(n)
...
>>> f()
3
>>> i = 5				#函数顶以后修改i的值不影响参数n的默认值
>>> f()
3
>>> f.__defaults__		#查看函数默认值参数的值不影响参数n的默认值
(3,)

推荐阅读