《零基础入门学习python》学习过程(五)

时间:2023-02-08 19:58:36

学习时间:2017/10/17

第18、19、20、21、22、23课:函数

18课知识点汇总:
创建和调用函数:

>>> def Function_name(parameters):   #函数创建
        function body

>>> Function_name(parameters)    #函数调用

18课课后题汇总:
1. 什么是DRY?DRY是程序员们公认的指导原则:Don’t Repeat Yourself. 不要再去重复拷贝一段代码了。使用函数的好处:降低代码量、降低维护成本、易读。

2.以下函数有几个参数?

>>> def MyFun((x,y),(a,b)):
        return (x*y-a*b)

答案为0个,因为类似于这样的写法是错误的。函数的参数需要的是变量,而这里试图用“元祖”的形式来传递是不可行的。
如果要表达传递元组可用以下方式:

>>> def MyFun(x,y):
    return (x[0]*x[1]-y[0]*y[1])

>>> MyFun((3,4),(1,2))
10

3.请问调用以下这个函数会打印什么内容?

>>> def hello():
        print('Hello World!')
        return
        print('Welcome To FishC.com!')

会打印

>>> hello()
Hello World!

因为当Python执行到return语句的时候,Python认为函数到此结束,需要返回了(尽管没有任何返回值)。

4.编写一个将十进制转换为二进制的函数,要求采用“除2取余”[概念链接]的方式,结果与调用bin()一样返回字符串形式。

>>> def Dec2Bin(dec):
    temp=[]
    result=''
    while dec:
        quo=dec%2
        dec=dec//2
        temp.append(quo)
    while temp:
        result+=str(temp.pop())
    return result

>>> print(Dec2Bin(62))
111110

5.编写一个函数,利用欧几里得算法[概念链接]求最大公约数,例如gcd(x, y)返回值为参数x和参数y的最大公约数。
欧几里得算法:两个数的最大公约数是指能同时整除它们的最大正整数。
设两数为ab(a≥b),求ab最大公约数(a,b)的步骤如下:
(1)用a除以b(a≥b),得a÷b=q...r1(r1≥0)
(2)若 r1=0 ,则 (a,b)=b
(3)若 r1≠0 ,则再用b除以r1 ,得b÷r1=q...r2(r2≥0) .
(4)若 r2=0 ,则 (a,b)=r1 ;若r2≠0,则继续用 r1 除以 r2 ,……,如此下去,直到能整除为止。
其最后一个余数为0的除数即为 (a,b)的最大公约数。

>>> def gcd(x,y):
    while y:
        remainder = x % y
        x = y
        y = remainder 
    return x
#本应返回的是除数,但是由于每一次while循环中已经将y赋给x,故返回x

>>> print(gcd(4,6))
2
>>> print(gcd(6,4))
2

易错处:在这里,不用判断两个参数的大小,因为小数除以大数比大数除以小数就多一次while循环,这次while循环中只是将两个数字位置调换,并无其他影响。

19课知识点汇总:
1.形参和实参

>>> def MyFirstFunction(name):
        '函数定义过程中的name叫形参'
        #因为它知识一个形式,表示占据一个参数位置
        print('传递进来的'+name+'叫做实参,因为它是具体的参数值')

>>> MyFirstFunction('小甲鱼')
传递进来的小甲鱼叫做实参,因为它是具体的参数值

2.函数文档
以上1中程序中‘函数定义过程中的name叫形参’这个用单引号括起来的内容叫做函数文档,用来解释整个函数的内容梗概,可用function_name.__doc__查看该函数文档(或用help()查看)。它与注释功能相同,但严格来讲,注释只是解释说明部分代码的信息。

>>> MyFirstFunction.__doc__
'函数定义过程中的name叫形参'

>>> help(MyFirstFunction)
Help on function MyFirstFunction in module __main__:

MyFirstFunction(name)
    函数定义过程中的name叫形参

3.关键词参数
函数有多个参数时,用位置索引参数时可能会发生参数顺序引起的BUG,而用关键字索引参数就可以指定具体调用的是哪个参数,不用按照形参顺序去传递实参。例如:

>>> def SaySome(name,words):
    print(name+'->'+words)


>>> SaySome('小甲鱼','让编程改变世界')
小甲鱼->让编程改变世界
>>> SaySome(words='让编程改变世界',name='小甲鱼')
小甲鱼->让编程改变世界

4.默认参数
默认参数是为形参定义了默认值,当函数调用时未传递实参也不会报错,默认使用形参的初始值代替。

>>> def SaySome(name='小甲鱼',words='让编程改变世界'):
    print(name+'->'+words)


>>> SaySome('大甲鱼')
大甲鱼->让编程改变世界
>>> SaySome(words='哈哈哈哈')
小甲鱼->哈哈哈哈

5.收集参数
参数个数未知时,可使用收集参数来代替,即在形参前面加*号。

>>> def test(*parameters):
    print('参数的长度为:',len(parameters))
    print('第二个参数为:',parameters[1])


>>> test(1,'小甲鱼',3.14,5,6)
参数的长度为: 5
第二个参数为: 小甲鱼

原理为python将多个实参打包成一个元组传入形参中,值得注意的是,如果在已有收集参数的情况下还需加入其他自定义的形参,就必须使用关键字参数,而不能直接添加。

>>> def test(*parameters,num):
    print('参数的长度为;',len(parameters))
    print('第二个参数为:',parameters[1])
    print('num参数为',num)


>>> test(1,'小甲鱼',3.14,5,6,num=7)  #使用关键字参数
参数的长度为; 5
第二个参数为: 小甲鱼
num参数为 7

19课课后题汇总:
使用help(print)查看print()这个BIF有哪些默认参数?分别起到什么作用?

>>> help(print)
Help on built-in function print in module builtins:

print(...)
    print(value, ..., sep=' ', end='\n', file=sys.stdout, flush=False)

    Prints the values to a stream, or to sys.stdout by default.
    Optional keyword arguments:
    file:  a file-like object (stream); defaults to the current sys.stdout. 
    #文件类型对象,默认是sys.stdout(标准输出流)
    sep:   string inserted between values, default a space.
    #第一个参数如果有多个值(第一个参数是收集参数),各个值之间默认用空格(space)隔开
    end: string appended after the last value, default a newline.
    #打印最后一个值之后默认参数一个新行标识符(‘\n’)
    flush: whether to forcibly flush the stream.
    #是否强制刷新流

20课知识点汇总:
1.函数与过程
函数(Funcition):有返回值。
过程(Procedure):是简单,特殊且没有返回值的。
python中只有函数没有过程,因为就算在定义函数时未定义return内容,函数也会返回一个None对象。

>>> def Hello():
    print('Hello World')


>>> temp=Hello()
Hello World
>>> print(temp)
None
>>> type(temp)
<class 'NoneType'>

2.函数返回值
不同于C语言要定义返回值的类型,python动态确定变量类型,且可通过列表或元组返回多个值。

>>> def test1():
    return [1,'小甲鱼',3.14]

>>> test1()
[1, '小甲鱼', 3.14]

>>> def test2():
    return 1,'小甲鱼',3.14

>>> test2()
(1, '小甲鱼', 3.14)

3.函数变量的作用域问题(局部变量和全局变量)
局部变量:函数内定义的变量只作用于函数内,在函数外调用都是无效的。
全局变量:定义于函数外,作用域在整个代码段,函数内可调用但不可修改。
为确保代码可读性和安全性,尽量少用全局变量。

20课课后题汇总:
1.编写一个函数,分别统计传入字符串参数(可能不止一个参数)的英文字母,空格,数字和其他字符的个数。

def count(*parameters):
    length=len(parameters)
    for i in range(length):  #注意range(num)函数代表从0到num-1,不包括num
        letters=0
        space=0
        digit=0
        others=0
        for each in parameters[i]:
            if each.isalpha():
                letters+=1
            elif each.isdigit():
                digit+=1
            elif each.isspace():
                space+=1
            else:
                others+=1
        print('该字符串*有%d个英文字母,%d个空格,%d个数字,%d个其他字符'
              %(letters,space,digit,others))
count('i love 123','i love you !')

2.编写一个函数,判断传入的字符串参数是否为“回文联”(回文联即用回文形式写成的对联,即可顺读,也可倒读,例如:海上自来水来自海上)

def Palindrome(desStr):
    list1=list(desStr)
    list1.reverse()  
    #注意reverse()函数和reversed()函数的区别,可改写为list2=reversed(list1)
    if list1==list(desStr):
        print('是回文联')
    else:
        print('不是回文联')

print('==============RESTART==============')
desStr=input('请输入一句话:')
Palindrome(desStr)

21课知识点汇总:
1.内嵌函数和闭包
python屏蔽(shadowing)机制:全局变量在局部作用域被修改时,python会在局部作用域内创建一个与全局变量同名的局部变量以起到保护全局变量的作用,即全局变量只可在与它定义时的同级作用域中被修改。
global关键字:可以实现在函数内修改全局变量的值

>>> def test():
    global x
    print('未改变的全局变量x为',x)
    x=5
    print('在函数内修改x为',x)


>>> x=100
>>> test()
未改变的全局变量x为 100
在函数内修改x为 5
>>> print('修改后,全局变量x为',x)
修改后,全局变量x为 5

2.内嵌函数
python允许在一个函数中定义另一个函数。值得注意的是,内嵌函数的作用域只在包含它的外部函数中。

3.闭包
关于闭包的一个非常容易理解的链接:Python 快速教程(深入篇04):闭包
闭包(closure)是函数式编程的重要的语法结构,Python也支持这一特性。
python闭包从表现形式上定义为:如果在一个内部函数中,对在外部作用域(但不是全局作用域)的变量进行引用,该变量称为内部函数的环境变量,那么由这个内部函数和与其相关的引用环境组合而成的实体就叫做闭包。环境变量不同,后面再执行相同参数的函数后得到的结果也不同。

闭包的原理:

Python中通过提供 namespace 来实现重名函数/方法、变量等信息的识别,其一共有三种 namespace,分别为:
- local namespace: 作用范围为当前函数或者类方法
- global namespace: 作用范围为当前模块
- build-in namespace: 作用范围为所有模块
当函数/方法、变量等信息发生重名时,Python会按照 “local namespace -> global namespace -> build-in namespace”的顺序搜索用户所需元素,并且以第一个找到此元素的 namespace 为准。
同时,Python中的内建函数locals()和globals()可以用来查看不同namespace中定义的元素。

s='string in global'
num=99

def numFunc(a,b):
    num=100
    print ('print s in numFunc:',s)

    def addFunc(a,b):
        s='string in addfunc'
        print ('print s in addFunc:',s)
        print ('print num in addFunc:',num)
        print ('locals of addFunc:',locals())
        print
        return '%d+%d=%d'%(a,b,a+b)

    print ('locals of numFunc:',locals())
    print
    return addFunc(a,b)

numFunc(3,6)
print ('globals:',globals())

结果为:

print s in numFunc: string in global
locals of numFunc: {'addFunc': <function numFunc.<locals>.addFunc at 0x000001E71B0B3F28>, 'b': 6, 'a': 3, 'num': 100}
print s in addFunc: string in addfunc
print num in addFunc: 100
locals of addFunc: {'s': 'string in addfunc', 'b': 6, 'a': 3, 'num': 100}
globals: {'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <class '_frozen_importlib.BuiltinImporter'>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>, '__file__': 'C:/Users/AppData/Local/Programs/Python/Python36/closure_test.py', 's': 'string in global', 'num': 99, 'numFunc': <function numFunc at 0x000001E71BD21510>}

代码中通过locals()和globals()获取了不同namespace中定义的元素,当在”numFunc”函数中访问s变量的时候,由于local namespace中没有定义s,所以会找到global中的s;但是,由于”addFunc”的local namespace中有s,所以可以直接使用。

python创建闭包:
在pyhon中创建一个闭包可以归结为以下三点:
- 闭包函数必须有内嵌函数
- 内嵌函数需要引用该嵌套函数上一级namespace中的变量
- 闭包函数必须返回内嵌函数
注意:
当外部函数作用域的变量在内部函数中被直接调用并修改时,python会使用屏蔽(shadowing)机制来保护全局变量(相对的),就会出现UnboundLocalError: local variable 'x' referenced before assignment的BUG,即变量在未定义前被引用:

>>> def Func1():
    x=5
    def Func2():
        x*=x
        return x
    return Func2()

>>> Func1()
Traceback (most recent call last):
  File "<pyshell#280>", line 1, in <module>
    Func1()
  File "<pyshell#279>", line 6, in Func1
    return Func2()
  File "<pyshell#279>", line 4, in Func2
    x*=x
UnboundLocalError: local variable 'x' referenced before assignment

python3中,引入关键字nonlocal解决问题,它的作用是在内部函数中修改外部函数的局部变量(相对于模块中的全局变量来说),原理相当于它告诉python程序,我的这个x变量是在外部定义的,你去外面找吧。然后python就去外层函数找,然后就找到了x=5这个定义和赋值,程序就能正常执行了。

>>> def Func1():
    x=5
    def Func2():
        nonlocal x
        x*=x
        return x
    return Func2()

>>> Func1()
25

21课课后题汇总:
1.找区别:
程序一:

>>> def FuncOut():
    def FuncIn():
        print('访问内部函数成功')
    return FuncIn

>>> FuncOut()()  
访问内部函数成功

>>> go=FuncOut()
>>> go()
访问内部函数成功

程序二:

>>> def FuncOut():
    def FuncIn():
        print('访问内部函数成功')
    return FuncIn()

>>> FuncOut()
访问内部函数成功

当程序一中的FuncOutreturn一个内部函数名时,调用FuncOuture()函数返回的是一个函数对象,程序二中return函数名加括号则返回的是内部函数调用。

22课知识点汇总:
1.匿名函数
语法:lambda [arg1 [,arg2,.....argn]]:expression
lambda只是一个表达式,函数体比def简单很多。它的主体是一个表达式,而不是一个代码,仅仅能在lambda表达式中封装有限的逻辑进去。
lambda表达式的作用:
- python写一些执行脚本时,使用lambda就可以省下定义函数过程,使代码更加精简。

>>> def add(x,y):
    return x+y

>>> add(3,4)
7

>>> sum1=lambda x,y:x+y
>>> sum1(3,4)
7
  • lambda什么时候使用:对于一些比较抽象并且整个程序执行下来只需要调用一两次的函数,使用lambda就不需要考虑命名的问题了。
  • 简化代码的可读性
    2.filter()函数:过滤器
    语法:filter(function or None, iterable) --> filter object,按照传入的第一个参数(即函数或None)的标准依次过滤第二个参数中的每个元素,然后保留返回值True,丢弃返回值False
>>> filter(None,[1,0,False,True])
<filter object at 0x0000023464D6EF60>
>>> list(filter(None,[1,0,False,True]))
[1, True]
>>> def odd(x):
    return x%2

>>> temp=range(10)
>>> result=filter(odd,temp)  #第一个参数若是函数,调用时不加括号
>>> list(result)
[1, 3, 5, 7, 9]

使用lambda简化上述程序,则一行就可以解决。

>>> list(filter(lambda x:x%2,range(10)))
[1, 3, 5, 7, 9]

3.map():映射函数
语法:map(func, *iterables) --> map object,将第二个参数中的每个元素使用第一个参数的函数得到相映射的值。

>>> list(map(lambda x:x*2,range(10)))
[0, 2, 4, 6, 8, 10, 12, 14, 16, 18]

22课课后题汇总:
1.用filter()lambda表达式快速求出100以内所有3的倍数:

>>> list(filter(lambda x:None if x%3 else x,range(100)))
[3, 6, 9, 12, 15, 18, 21, 24, 27, 30, 33, 36, 39, 42, 45, 48, 51, 54, 57, 60, 63, 66, 69, 72, 75, 78, 81, 84, 87, 90, 93, 96, 99]
>>> list(filter(lambda x:not(x%3),range(100)))
[0, 3, 6, 9, 12, 15, 18, 21, 24, 27, 30, 33, 36, 39, 42, 45, 48, 51, 54, 57, 60, 63, 66, 69, 72, 75, 78, 81, 84, 87, 90, 93, 96, 99]

2.还记得zip吗?使用zip会将两数以元组的形式绑定在一起,例如:

>>> list(zip([1,3,5,7,9],[2,4,6,8,10]))
[(1, 2), (3, 4), (5, 6), (7, 8), (9, 10)]

但是希望打包的形式是灵活多变的列表而不是元组([[1, 2], [3, 4], [5, 6], [7, 8], [9, 10]]这种形式)可以实现吗,(采用maplambda

>>> list(map(lambda x:list(x),zip([1,3,5,7,9],[2,4,6,8,10])))
[[1, 2], [3, 4], [5, 6], [7, 8], [9, 10]]

也可以给map()函数赋多个参数:

>>> list(map(lambda x,y:[x,y],[1,3,5,7,9],[2,4,6,8,10]))
[[1, 2], [3, 4], [5, 6], [7, 8], [9, 10]]

3.还记得列表推导式吗,完全可以使用列表推导式代替filter()lambda组合?
例如,第一题中求出100以内所有3的倍数,可以转为列表推导式为:

>>> {i for i in range(100) if not(i%3)}
{0, 3, 6, 9, 12, 15, 18, 21, 24, 27, 30, 33, 36, 39, 42, 45, 48, 51, 54, 57, 60, 63, 66, 69, 72, 75, 78, 81, 84, 87, 90, 93, 96, 99}

23课知识点汇总:
递归:调用函数自身,且有正确的返回值,即参数会有限的递减或递增。

>>> def factorial(n):
    if (n==1):
        return 1
    else:
        return n*factorial(n-1)


>>> print(factorial(5))
120

递归的缺点:用于求阶乘的简单程序时,迭代比递归更有效率,减少了时间和空间上的消耗,因为递归中函数的调用都需要进行压栈,弹栈,保存和恢复寄存器的栈操作。
递归的优点:在解决汉诺塔问题,目录索引(因为你永远不知道这个目录里面是否还有目录),快速排序(二十世纪十大算法之一),树结构的定义问题时,使用递归会事半功倍,更精简代码。