• Python学习5——抽象,涉及抽象和结构、函数的自定义、参数、作用域、递归


    此处将抽象和结构、自定义函数、参数的使用、作用域、递归放在一起学习,看起来很怪是不是?

    但实际上这几者之间是有紧密联系的,不然Python基础教程(第三版)的作者为什么会把它们放在一起哪?手动滑稽

    好了,不说废话了,不乱想了,上硬货!!!

    1、抽象和结构

    抽象的目的是节省人力,实际上,抽象虽然看起来更高,但实际上抽象是程序能被人们更好地理解的关键所在。

    page=download_page()
    freqs=compute_frequence(page)
    for word ,freq in freqs:
        print(word, freq)

    这些代码,一看上去就知道要干什么,但具体如何去做,你什么也没说,只是让计算机去下载网页并计算使用频率。至于这些操作的细节,将在其他地方(函数的定义)中给出,之看上去非常易懂,更好理解。

    2、自定义函数

    函数执行特定的一些操作序列,或许还会返回一个值,你可以调用这个函数(有时候需要提供一些参数,放在函数的参数列表中)。

    有的时候,你想调用某个对象,但这可能是非法的,你可以通过callable函数来判断这个对象是否可以被调用。

    >>> x=1
    >>> callable(x)#x无法被调用
    False

    函数是结构化编程的核心。但如何去定义函数哪?,使用 def 语句!

    最简单的自定义函数示例:

    >>> def hello(name):
        return 'Hello,'+name+'!'
    
    >>> print(hello('jiameng'))
    Hello,jiameng!

    再稍微复杂一点:

    >>> def fibs(num):
        result=[0,1]
        for i in range(num-2):
            result.append(result[-2]+result[-1])#f(n)=f(n-1)+f(n-2),此处获取的是数列,因此要把新的加上,而且最后连个的索引是-1和-2
        return result
    
    >>> fibs(8)
    [0, 1, 1, 2, 3, 5, 8, 13]
    >>> fibs(20)
    [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597, 2584, 4181]

    #解释一下上边的程序:获取斐波那契数列,获取的列表为result ,

    2.1给函数编写文档

    有的时候编写的函数并不是那么的简单易懂,这个时候就需要给函数增加一些解释性的语句,以方便理解和使用。

    一种方法是添加注释(以#字打头的语句)

    另一种方法则是添加独立的字符串,放在函数开头的字符串称为文档字符串,将作为函数的一部分存储起来。

    示例

    >>> def square(n):
        'Calcuates the square of the number n'
        return n*n
    
    >>> square.__doc__#注意是双下划线
    'Calcuates the square of the number n'
    >>> help(square)#help是内置函数,可使用它来获取有关函数的信息
    Help on function square in module __main__:
    
    square(n)
        Calcuates the square of the number n

    3、神奇的参数

    好了,函数的使用和自定义也不过尔尔嘛,但一用上参数,要想理解参数的工作原理就很困难了!

    3.1对参数的修改

    函数通过参数获取一系列的值,但是参数可以进行修改吗?

    参数也不过是变量而已,在函数内部给参数赋值对外部没有任何的影响,看一个例子:

    >>> def change(name):
        nmae='jiameng'
    
        
    >>> name='Mrs.jia'
    >>> change(name)
    >>> name
    'Mrs.jia'

    参数存储在局部作用域中,关于作用于的内容将在后续内容中继续学习。

    对于字符串和元组来说,他们是不可变的,这意味着他们无法被修改(只能替换为新值)。所以它们做参数没有什么需要讨论的,但如果是可变的数据类型,例如列表哪?看一个例子:

    >>> def change(n):
        n[0]='Mr.jia'
    
        
    >>> names=['Mrs.Wang','Mrs.Li']
    >>> change(names)
    >>> names
    ['Mr.jia', 'Mrs.Li']

    这个例子中,也是在函数中修改了参数,但结果却对外部数据产生了修改,这是为什么哪?实际上,这个例子和上一个示例存在着根本的区别。这个示例中,修改了变量关联的列表。为什么会这样哪?实际上,上面的示例等同于下方不使用函数的示例:

    >>> names=['Mrs.Wang','Mrs.Li']
    >>> n=names   #此处假装传递名字作为参数
    >>> n[0]='Mr.jia'#修改列表
    >>> names
    ['Mr.jia', 'Mrs.Li']

    这种情况就是将同一个列表赋值给两个变量时,这两个变量将同时指向这个列表。因此,修改任何一个都会导致这个列表发生变化。要避免这种情况,就要创建列表的副本。在对序列进行切片操作时,返回的切片都是副本,因此可以创建一个覆盖整个列表的切片,得到的就是列本的副本。下方示例就是对列表副本的操作:

    >>> names=['Mrs.Wang','Mrs.Li']
    >>> n=names[:]
    >>> n
    ['Mrs.Wang', 'Mrs.Li']
    >>> n[0]='Mr.jia'
    >>> n
    ['Mr.jia', 'Mrs.Li']
    >>> names
    ['Mrs.Wang', 'Mrs.Li']
    >>> 

    使用函数对副本进行操作:

    >>> change(nmaes[:])
    >>> names
    ['Mrs.Wang', 'Mrs.Li']

    这下好了,不会对元件产生影响。

    3.2为什么要修改参数

    好了,现在已经对基本的参数修改有了了解,但我们为什么要修改参数哪?这是个问题......

     在提高程序的抽象程度方面,使用函数来修改数据结构(如列表或字典)是一种不错的方式。

    但如果参数是不可变的(如数)哪?

    那不好意思,在这种情况下,应从函数返回有需要的值(如果要返回多个值,可以以元组的形式返回它们)。

    如果一定要返回参数,可以玩的花一点——比如将数放在列表中

    >>> def inc(x):
        x[0]=x[0]+1
       
    >>> foo=[10]
    >>> inc(foo)
    >>> foo
    [11]

    3.3关键字参数和默认值

    前面使用的参数都是位置参数,因为它们的位置至关重要——事实上比名称还关键,此处介绍的内容能够让你完全忽略位置。

    >>> def hello_1(name,gretting):
        print('{},{}!'.format(gretting,name))
    
        
    >>> def hello_2(name,gretting):
        print('{},{}!'.format(name,gretting))
    
        
    >>> hello_1('jiameng','hello')
    hello,jiameng!
    >>> hello_2('hello','jiameng')
    hello,jiameng!

    上边的两个函数功能一样,区别只在于参数的位置不同而已。

    有的时候参数数量很多,无法完全记住参数的顺序,这个时候极易出错,所以为了简化函数的调用,可以指定参数的名称。

    >>> hello_1(gretting='hello',name='jiameng')
    hello,jiameng!

    上方代码对于函数的调用,在这里,参数的顺序无所谓,不过名称很重要。

    像这样使用名称指定的参数称为关键字参数,主要优点是有助于澄清各个参数的作用,虽然输入可能会复杂一些,但每个参数的作用会更加明了。

    但是,关键字参数最大的作用是可以指定默认值

    >>> def hello_3(gretting='hello',name='jiameng'):
        print('{},{}!'.format(gretting, name))
    
        
    >>> hello_3()
    hello,jiameng!

    你可以像上方代码中函数一样指定默认值之后,调用函数时可以不提供它!根据需要可以一个参数值也不提供,也可以提供部分参数值或者全部参数值。

    >>> def hello_3(gretting='hello',name='jiameng'):
        print('{},{}!'.format(gretting, name))
    
        
    >>> hello_3('Hello')
    Hello,jiameng!
    >>> hello_3('Hello','JiaMeng')
    Hello,JiaMeng!

    有的时候你甚至可以结合使用位置参数和关键字参数,但通常你不应这样做,除非你知道这样做的结果。一般而言,除非必不可少的参数很少,而带默认值的可选参数很多,否则不应结合使用位置参数和关键字参数。

    >>> def hello_4(name,gretting='hello',punctuation='!'):
        print('{},{}{}'.format(gretting,name,punctuation))
    
        
    >>> hello_4('jiameng')
    hello,jiameng!
    >>> hello_4('jiameng','HELLO')
    HELLO,jiameng!
    >>> hello_4('jiameng','HELLO','!!!!')
    HELLO,jiameng!!!!
    >>> hello_4('jiameng',gretting='Top of the morning to ya')
    Top of the morning to ya,jiameng!

    这样使用是非常灵活的。

    3.4收集参数

    实际上我们之前就已经使用到了关于参数的收集,*params,是不是还有印象?

    对的,这里就是使用了带星号的参数来进行对参数的收集。

    不过这和之前赋值时带星号的变量收集多余的值不同的是,这里将收集到的值存放在元组而不是列表中去。下面看一下例子:

    >>> def print_params(title,*params):
        print(title)
        print(params)
    
        
    >>> print_params('params','1,2,3,4,5,6')
    params
    ('1,2,3,4,5,6',)
    >>> print_params('Nothing:')#无参数可收集,将是一个空元组
    Nothing:
    ()

    同时与赋值一样,带星号的参数也可以放在其他位置,而不一定非要放在末尾,不过,这种情况下,你需要多做一些工作:使用名称来指定后续参数。

     下边的代码给出了错误示例以及正确示例:

    >>> def in_the_middle(x,*y,z):
        print(x,y,z)
    
        
    >>> in_the_middle(1,2,3,4,5,6,7,8,9)
    Traceback (most recent call last):
      File "<pyshell#74>", line 1, in <module>
        in_the_middle(1,2,3,4,5,6,7,8,9)
    TypeError: in_the_middle() missing 1 required keyword-only argument: 'z'
    
    
    >>> in_the_middle(1,2,3,4,5,6,z=7,8,9)
    SyntaxError: positional argument follows keyword argument
    
    
    >>> in_the_middle(1,2,3,4,5,6,7,8,z=9)
    1 (2, 3, 4, 5, 6, 7, 8) 9

    星号不会收集关键字参数,要收集关键字参数,可以使用两个星号。

    >>> def print_params_3(**params):
        print(params)
    
        
    >>> print_params_3(x=1,y=2,z=3)
    {'x': 1, 'y': 2, 'z': 3}

    如代码运行结果得到的不是一个元组,而是一个字典

    综合运用获取参数的技术,我们给出了以下示例:

    >>> def print_params(x,y,z=3,*pospar,**keypar):
        print(x,y,z)
        print(pospar)
        print(keypar)
    
        
    >>> print_params(1,2,3,4,5,6,foo=1,bar=2)
    1 2 3
    (4, 5, 6)
    {'foo': 1, 'bar': 2}

    3.5分配参数 

    前面介绍了如何将参数收集到元组和字典中,但同样使用两个运算符(*和**)同样可以执行相反的操作(将元组或者字典中的对象分配给参数)

    >>> def add(x,y):
        return x+y
    
    >>> params=[1,2]
    >>> add(*params)
    3

    对于字典也同样适用

    >>> def hello_3(gretting='hello',name='jiameng'):
        print('{},{}!'.format(gretting, name))
    
        
    >>> params={'name':'Mrs.Wang','gretting':"I'm glad to see you"}
    >>> hello_3(**params)
    I'm glad to see you,Mrs.Wang!

    如果在定义和调用时都使用*或者**,将只传递元组或者字典,这样还不如不使用它们,这样还会省去很多麻烦。

    因此,只有在定义函数(允许可变数量的参数)或调用函数(拆分字典或者序列)时使用,星号才会发挥作用。

    >>> def with_start(**keds):
        print(keds['name'],'is',keds['age'],'year old.')
    
        
    
    >>> def without_start(keds):
        print(keds['name'],'is',keds['age'],'year old.')
    
    
    >>> args={'name':'jiameng','age':20}
    >>> with_start(**args)
    jiameng is 20 year old.
    >>> with_start(args)
    Traceback (most recent call last):
      File "<pyshell#118>", line 1, in <module>
        with_start(args)
    TypeError: with_start() takes 0 positional arguments but 1 was given
    
    >>> without_start(args)
    jiameng is 20 year old.

    使用这些拆分运算符来传递参数很有用,因为这样无需担心参数个数的问题。这在调用超类的构造函数时很有用,这些具体的内容将在后续章节继续学习!!!

    4、作用域

     变量到底是什么?可以将变量视为指向值的名称。

    执行赋值语句x=1,名称x指向值1,这几乎和使用字典一模一样,只是你使用的是看不见的“字典”,有一个名为vars的内置函数,它返回这个看不见的字典:

    >>> x=1;
    >>> scope=vars()
    >>> scope['x']
    1
    >>> scope['x']+=1
    >>> x
    2

    这种”看不见的字典“称为命名空间或者作用域。那有多少个命名空间哪?除全局作用域外,每一个函数调用都将创建一个。

    在函数内部使用的变量称为局部变量

    如果要在函数内部访问全局变量,如果只是简单地读取这个变量的值,而不重新关联他,通常不会有任何问题。

    >>> def combine(parameter):
        print(parameter+exteral)
    
        
    >>> exteral='berry'
    >>> combine('shrub')
    shrubberry
    >>> exteral
    
    #像这样访问全局变量通常是bug的根源。一定要谨慎使用全局变量

    变量还存在遮盖问题:及局部变量遮盖全局变量

    重现关联全局变量(使其指向新值)是另一码事。在函数内部给变量赋值时,该变量默认是局部变量,除非你声明它是全局变量,那么如何告诉Python它是全局变量哪?很简单

    >>> def change():
        global x
        x+=1
    
        
    >>> change()
    >>> x
    2

    是不是感觉很怪异,但事实上就是这样!!!

    另一个就是作用于的嵌套问题,Python函数可以嵌套,嵌套通常作用不大,但有一个很突出的作用就是:使用一个函数来创建另一个函数,可以看一下下面这个例子:

    >>> def multiplier(factor):
        def multiplierByFactor(number):
            return number*factor
        return multiplierByFactor
    
    >>> double =multiplier(2)
    >>> double(5)
    25
    >>> triple=multiplier(3)
    >>> triple(3)
    9
    >>> multiplier(5)(4)
    16
    
    >>> multiplier(5)(4)
    20

    在这里,一个函数位于另一个函数内布,且外边的函数返回里边的函数。也就是说返回也个函数而不是调用它。重要的是,返回的函数能够访问其定义所在的作用域,换而言之,他携带者自己所在的环境(和相关的局部变量)。

    像  multiplierByFactor  这样存储其所在作用于的函数称之为闭包

    随之而来的就是作用于的问题,实际上作用域也是可以嵌套的,存储在

    5、递归:递归的使用太多了,这里就不再详细讲述递归的定义等,此处给出二分查找的Python代码

    >>> def search(sequence, number, lower=0, upper=None):
        if upper==None:
            upper=len(sequence)-1
        if lower==upper:
            assert number==sequence[upper]
            return upper
        else :
            middle=(lower+upper)//2
            if(number>sequence[middle]):
                return search(sequence, number, middle+1, upper)
            else :
                return search(sequence, number, lower, middle)
    
            
    >>> list1=[1,3,5,7,9,11,23,25,59]
    >>> search(list1,7)
    3
    >>>

      >>> search(2)
      Traceback (most recent call last):
      File "<pyshell#187>", line 1, in <module>
         earch(2)
      TypeError: search() missing 1 required positional argument: 'number'

    事实上这里仍有几个需要注意的点:

    (1)、//,整除

    (2)、如果查找不到怎么办?会出现什么样的结果?为什么会这样哪?

    函数式编程:

    至此,你可能已经熟悉使用函数来完成自己的任务。

    Python提供了一些有助于进行这种函数式编程的函数,包括:map 、 filter和reduce。

    你可以使用map将序列中所有元素传递给函数

    你也可以使用filter个根据布尔函数的返回值来对元素进行过滤

    你也可以使用reduce来将序列的前两个元素合二为一,再将结果与第三个元素合二为一,直至处理完整个序列并得到一个结果。

    >>> list(map(str,range(10)))#与[str(i) for i in range(10)]等效
    ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']
    >>> def func(x):
        return x.isalnum()
    
    >>> seq=["foo","x42","?!","***"]
    >>> list(filter(func,seq))
    ['foo', 'x42']
    #就这个示例来言,使用列表推导更为简单
    >>> [x for x in seq if x.isalnum()]
    ['foo', 'x42']

    事实上,Python提供了一个名为lambda的内置函数,用来创建内嵌的简单的函数(主要供map 、filter、reduce来使用)

    >>> number=[1,2,3,4,5,6,7,8,9,10,11,12,13]
    >>> from functools import reduce
    >>> reduce(lambda x,y:x+y,number)
    91

    小结:抽象、函数定义、参数、作用域、递归、函数式编程

    新学函数:

    函数 描述
    map(func,seq[, seq, ...]) 对序列中所有元素执行函数
    filter(func , seq) 返回一个列表,其中包含对其执行函数时结果为真的所有元素
    reduce(func, seq[, initial]) 等价于func(func(func(seq[0], seq[1]), seq[2]) ,...)
    sum(seq) 返回seq所有元素的和
    apply(func[, args[, kwargs]]) 调用函数(还提供要传递给函数的参数)

     

  • 相关阅读:
    Windows服务器上Mysql为设置允许远程连接提示:not allowed to connect to this MySQL server
    Java中怎样遍历两个Date日期之间的每一天
    ElementUI中el-table设置指定列固定不动,不受滚动条影响
    APP测试时不可忽视搭建代理服务器抓包测试的必要性
    ctype库试运行
    django-grappelli 安装配置
    windows下django1.7 +python3.4.2搭建记录2
    windows下django1.7 +python3.4.2搭建记录1
    【2020-11-15】大心境并不能自动化解小结
    【2020-11-14】对自己责任心的煎熬考验
  • 原文地址:https://www.cnblogs.com/jiameng991010/p/11227584.html
Copyright © 2020-2023  润新知