• python学习笔记24:基础语法之函数


    1. 相关概念

    以如下一段程序为例

    def add(a,b=0):   # a和b是形式参数,形式参数可以定义默认值比如b=0
        c = a+b       # c是局部变量
        global d, e   # d和e是全局变量
        d = a*b
        e = a+b
    
    x=8
    y=9
    add(x, y)  # x和y是实际参数
    

    1.1 形式参数/实际参数

    形式参数: 函数定义时写的,系统没有为其分配内存空间,但是在定义里面可以使用的参数。例如:def(a, b=0)。
    实际参数:函数调用的时候传给函数的变量。这个是系统实实在在分配了内存空间的变量。

    1)形参可以定义默认值 def func(a, b=5, c=10), 但只有在形参表末尾的那些参数可以有默认参数值,例如def func(a, b=5)是有效的,def func(a=5, b)是无效的;
    2)带默认值的参数要出现在参数列表后面,如果出现在无默认值参数的前面会报错;

    1.2 位置参数/关键字参数

    位置参数:传入的值按照位置顺序依次赋给函数的形参。比如power(x, n),
    关键字参数:通过命名为参数赋值的一种参数,不用考虑位置。比如func(25, c=24);

    使用关键字参数的好处:
    1)不用担心参数顺序;
    2)如果其它参数都有默认值,我们可以只给我们想要的参数赋值;

    1.3 参数数目可以不确定

    1.3.1 使用星号*

    定义函数时,在参数前加一个*号,则参数接收到的是一个tuple

    >>> def calc(*numbers):
    ...     s = 0
    ...     for n in numbers: # numbers是一个tuple,遍历它,不能使用星号
    ...         s += n**2
    ...     return s
    ...
    >>> L0 = [0,1,2,3]
    >>>
    >>> # 调用函数时:在list/tuple前加一个*号,把list/tuple元素变成可变参数传入
    >>> calc(*L0)
    14
    >>> # 直接传一个List,在参数解析时没问题,但List整体作为一个参数,不符合预期
    >>> # 在函数内部对参数做加法时会报告list不能与int做幂运算。
    >>> calc(L0)
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
      File "<stdin>", line 4, in calc
    TypeError: unsupported operand type(s) for ** or pow(): 'list' and 'int'
    

    在普通语句中不能使用星号

    >>> *L0
      File "<stdin>", line 1
    SyntaxError: can't use starred expression here
    >>> a,b,c,d = *L0
      File "<stdin>", line 1
    SyntaxError: can't use starred expression here
    >>> a,b,c,d = (*L0)
      File "<stdin>", line 1
    SyntaxError: can't use starred expression here
    >>> (a,b,c,d) = (*L0)
      File "<stdin>", line 1
    SyntaxError: can't use starred expression here
    >>>
    

    注意:

    1. *numbers 这种写法只能出现在两种语句中:函数参数列表、调用函数时传参。普通语句(包括函数内部语句)中使用星号会报错。
    2. 用在函数参数列表中:表示这个地方实际上有多个参数,但在函数内部合并到一个tuple中使用。
    3. 用在调用函数传参时使用:表示将待传递增的List/Tuple打散后再传,因为函数的参数不是List/Tuple而是它的元素。
    4. *numbers写在普通语句中,会报告语法错误: can’t use starred(标星号的) expression here。

    1.3.2 使用双星号**

    使用**kw, 传入0个或多个含参数名的参数,这些关键字参数在函数内部组装为一个dict。
    使用关键字参数可以扩展函数的功能;

    >>> def person(name, age, **kw):  
    ...    print(‘name:’, name, ‘, age’, age, ‘, others:’ kw)  
    ...  
    >>> person(‘Michael’, 30) #只传入关键字参数  
    name:Michael, age:30, others:{}  
    >>>  
    >>> person(‘Adam’, 45, gender=’M’, job=’Engineer’) # 后面两个参数会组装成一个dict。  
    name:Adam, age:45, others:{‘gender’:’M’, ‘job’:’Engineer’}  
    >>>  
    >>> extra = {‘gender’:’M’, ‘job’:’Engineer’}  
    >>> person(‘Adam’, 45, **extra) #在dict前加**,把dict变成关键字参数传入;  
    name:Adam, age:45, others:{‘gender’:’M’, ‘job’:’Engineer’}  
    

    1.3.3。 *与**的作用

    一般*与**只用在给函数传参数时,在函数参数外使用作用不大(还可能报错)

    比如:
    foo(1, 2, 3) 相当于foo(*[1, 2, 3])
    bar(a=1, b=2, c=3) 相当于bar(**{‘a’:1, ‘b’:2, ‘c’:3})

    *L0 可以认为是把列表(元组)L0拆散后的结果 。

    *的作用:将列表拆散为元素。
    L0 = [0, 1, 2, 3] # L0是list,是一个整体,把L0传递给函数,函数得到的是1个参数。
    *L0 # L0是4个元素,把L0传递给函数,函数得到的是4个参数。

    **的作用,将dict拆散为一系列键值对。
    D0 = {‘a’:0, ‘b’:1} # D0是dict,直接传递给函数, 函数得到的是1个参数。
    D0 # 是两个键值对, 把D0传递给函数,函数得到的是2个参数。

    1.4. 命名关键字参数

    使用**kw:可以传递任意名称的关键字作为参数;
    使用*, city, job:可以限制关键字参数的名字,比如只接收city和job作为关键字参数;

    1. 对应的参数传参时必须传入参数名(使用’city’=’Chengdu’这种方式传参),没有参数名时,会被认为是位置参数,可能导致Error;
    2. 如果在定义函数时给默认值,调用时就可以不传这个参数。
    >> def person(name, age, *, city, job):  
    ..      print(name, age, city, job)  
    ..  
    >> person(‘Jon’, 30, city=’Chengdu’, job=’Engineer’) # 通过参数名传参数,正常运行;  
    on 30 Chendu, Engineer  
    >>  
    >> extra = {‘city’:’Shenzhen’, ‘job’:’Art’}  
    >> person(‘Bill’, 9, **extra) # 通过dict传入参数,正常运行;  
    ill 9 Shenzhen Art  
    >>>  
    >>> extra ={‘job’:’Art’}  
    >>> person(‘Bill’, 9, **extra) # 通过dict传入参数,会报错:缺少city参数;  
    TypeError: person() missing 1 required keyword-only arg: ‘city’  
    >>>  
    >>> extra = {‘length’:6, ‘job’:’Art’}  
    >>> person(‘Bill’, 9, **extra) # 通过dict传入参数,会报错:不能使用length这个参数;  
    TypeError: person() got a unexpected keyword arg: ‘length’  
    

    1.5. 参数组合

    Python函数的5种参数可以任意组合使用, 参数定义的顺序必须为:

    1. 必选参数
    2. 默认参数
    3. 可变参数
    4. 命名关键字参数
    5. 关键字参数

    比如:

    def f1(a, b, c=0, *args, **kw):
        pass
    
    def f2(a, b, c=0, *, d, **kw):
        pass
    

    任意函数,都可以通过类似func(*args, **kw)的形式(一个元组和一个字典)调用它,无论它的参数是如何定义的。

    1.6. 返回值

    1. 使用return从函数返回(跳出),也可以返回一个值。
    2. python程序默认返回None
    3. return可以返回多个值(实际上是返回一个元组,省略括号,直接赋值给多个变量)
    def move(x0, y0, s=0, angle=0):  
        x1=x0+s*math.cos(angle)  
        y1=y0+s*math.sin(angle)  
        return x1,y1  
      
    x,y = move(100,100, 50, math.pi*(1/3))  
    

    1.7. DocStrings

    1. 可以在函数中嵌入说明语句.
    2. 使用三引号可以跨行写说明,不需要续行符
    3. 使用__doc__方法,可以调用到说明语句。
    >>> def max(x,y):  
    ...     '''''Return the max of two number. 
    ...     The two number must be integer.'''  
    ...     if a>b:  
    ...         return a  
    ...     else:  
    ...         return b  
    ...   
    >>> print max(1,2)  
       
    >>> print max.__doc__  
    

    1.8. 函数起别名

    函数名其实是指向一个函数的引用,所以可以把函数名赋给一个变量,然后可以后这个变量调用函数:

    >>> a = abs # 变量a指向abs函数  
    >>> print(a(-1))  
    1  
    >>> print(a)  
    <built-in function abs>  
    

    1.9. 函数式编程

    Python对函数式编程提供部分支持;
    由于Python允许使用变量,所以Python不是纯函数式编程语言;

    2. 高阶函数

    2.1. 变量可以指向函数

    >>> x = abs(-10) # 调用函数,并获得函数调用结果;  
    >>> f = abs      # 把函数赋值给变量  
    >>> print(f)     # <built-in function abs>  
    >>> f(-10)       # 变量f指向abs,f(-10)与abs(-10)完全相同  
    

    2.2. 函数名也是变量

    函数名其实就是指向函数的变量。
    比如abs()这个函数,abs就是一个变量,指向计算绝对值的函数;

    >>> import builtins  
    >>> builtins.abs  
    <built-in function abs>  
    >>> builtins.abs = 10    # 可以把abs函数的指向改变  
    

    2.3. 传入函数

    一个函数A可以接收另一个函数B作为参数,这种函数A就称之为高阶函数;

    >>> def func_add(f, x, y):  
    ...     return f(x)+f(y)  
    ...   
    >>> abs_add(abs, 5, -6)  
    11  
    

    map()、reduce()、都是高阶函数

    2.4. 返回函数

    高阶函数除了可以接受函数做为参数外,还可以把函数做为结果值返回;

    3. 匿名函数lambda

    1. lambda 定义了一个匿名函数
    2. lambda 并不会带来程序运行效率的提高,只会使代码更简洁。
    3. lambda 只能有一个表达式,不用写return,返回值就是该表达式的值;
    4. 如果可以使用for...in...if来完成的,坚决不用lambda。
    5. 如果使用lambda,lambda内不要包含循环,如果有,宁愿定义函数来完成,使代码获得可重用性和更好的可读性。
    >>> foo = [2,18,9,22]  
    >>> list(filter(lambda x:x%3==0, foo)) #列表中可以被3整除的数;  
    [18, 9]  
    >>> list(map(lambda x:x*2+10, foo)) #将原列表元素做*2+10;  
    [14, 46, 28, 54]  
    >>> list(map(lambda x:[x,x*2], foo)) #将列表元素映射为list;  
    [[2, 4], [18, 36], [9, 18], [22, 44]]  
    >>> functools.reduce(lambda x,y:x+y, foo) # 返回数值,将原列表相加;  
    51  
    

    上述filter可以改写为:[x for x in foo if x%x==0]
    上述map可以改写为:[x*2+10 for x in foo]

    4. 装饰器

    作用:增强函数的功能,但又不修改函数定义本身;

    例1:

    定义一个装饰器,它可以打印当前调用函数的名称,它接受一个函数作为参数,并返回一个函数

     
    >>> def log(func):  
    ...     def wrapper(*args, **kwargs):  
    ...         print(‘call func: {name}: ’.format(name=func.__name__))  
    ...         return func(*args, **kwargs)  
    ...     return wrapper  
    

    把装饰器(log)作用到其它函数(now)上,相当于now=log(now):
    调用now(),在执行now本身之前,会先打印一行log

    >>> @log  
    ... def now():  
    ...     print(datetime.datetime.now())  
    ...   
    >>> import datetime #可以在调用now之前才import它需要用到的模块  
    >>> now()  
    call func: now  
    2018-07-27 11:29:18.239895  
    

    例2,定义一个装饰器,打印函数调用的次数和函数执行时间

    import time  
    def timer(func):  
        lst = [0, 0] # 使用一个list保存要记录的内容,在每次调用时,lst指向同一地址;  
      
        def wrapper(*args, **kwargs):  
            start = time.time()  
            r = func(*args, **kwargs) #调用函数,并保留返回值  
            end = time.time()  
            delta = end –start  
      
            lst[0] += 1 # lst[0]记录函数执行的次数  
            lst[1] += delta # lst[1]记录函数执行的时间  
      
            print(f‘cnt: {lst[0]}, time: {lst[1]}, {delta}’)  
      
            return r # 返回函数的结果,这里有个局限性,只能处理返回单个值的函数;  
      
        return wrapper # 返回包了壳的函数  
    

    测试:

    >>> @timer  
    >>> def my_sum(n): #定义一个函数  
    ...     r = 0  
    ...     for i in range(n):  
    ...         r += i  
    ...     return r  
    ...  
    >>> for i in range(10):  
    ...     r = my_sum(10000000)  
    ...  
    cnt 1, time 0.937***, 0.937***  
    cnt 2, time 1.852***, 0.914***  
    cnt 3, time 2.747***, 0.895***  
    cnt 4, time 3.681***, 0.934***  
    cnt 5, time 4.601***, 0.919***  
    cnt 6, time 5.563***, 0.962***  
    cnt 7, time 6.494***, 0.930***  
    cnt 8, time 7.403***, 0.909***  
    cnt 9, time 8.374***, 0.970***  
    cnt 10, time 9.280***, 0.906***  
    

    5. 偏函数

    1. 语法:functools.partial(函数对象, *args, **kwargs)
    2. 参数:函数对象、位置参数、命名参数

    Python的functools模块提供了很多功能,其中一个是偏函数(Partial Function);
    functools.partial功能:帮忙我们创建一个偏函数,但不需要我们自己定义偏函数;

    例如:将2进制字符串转为10进制整数,可以定义一个函数int2

    def int2(x, base=2):
        return int(x, base=base)
    

    这样默认处理2进制字符串.

    但我们不需要自己定义函数int2,可以使用偏函数达到目的:

    >>> import functools
    >>> int2 = functools.partial(int, base=2)
    >>> int2(‘00010000’)
    16
    

    functools.partial的作用:把函数的某些参数给固定住(即设置默认值),返回一个新的函数;
    但是调用int2时,仍然可以给它指定base参数为其它值:

    >>> int2(‘00010000’,base=10)
    10000
    

    例1:

    >>> int2 = functools.partial(int, base=2)  
    >>> int2(‘00010000’)  
    >>>  
    >>> #相当于:  
    >>> kwargs = {‘base’:2} # 将’base’:2作为kwargs的一部分  
    >>> int(‘00010000’, **kwargs)  
    

    例2:

    >>> max_with_10 = functools.partial(max, 10)  
    >>> max_with_10(5, 6, 7)  
    >>>  
    >>> #相当于:  
    >>> args=(10, 5, 6, 7) # 将10作为*args的一部分自动加到左边  
    >>> max(*args)  
    
  • 相关阅读:
    《大型网站技术架构》读后感
    质量属性
    课堂作业02
    课堂作业01
    《软件构架实践》读后感06
    《软件构架实践》读后感05
    Storm系列三: Storm消息可靠性保障
    Storm系列二: Storm拓扑设计
    Storm系列一: Storm初步
    网络层协议
  • 原文地址:https://www.cnblogs.com/gaiqingfeng/p/13254894.html
Copyright © 2020-2023  润新知