python函数解析

写在篇前

  函数能提高应用的模块性,和代码的重复利用率,是编程必须具备的基本抽象能力。python函数更是奇妙灵活,与很多特性值得探讨,本篇文章就来详细看看python 函数那些巧妙之处。首先,在篇前简单说说python函数的形式。比如说我们要实现一个函数:计算一个数m的n次方,那么函数可以定义如下:

def exponentiation(m, n):
    return m^n

# def 是函数定义关键字
# exponentiation 是函数名
# (m,n)是函数参数,后面冒号之后是函数体
# return 表示返回值,一个函数也可以没有函数体,此时返回值为None

函数参数

  • 必选参数

     必选参数,又称为位置参数,我们上面定义的函数exponentiationm, n 就是必选参数。当调用该函数时,每一个必选参数都必须传入合适的值。

  • 默认参数

     修改函数exponentiation为:

    def exponentiation(m, n=2):
        return m^n
    

     这样,n便是一个默认参数,当计算一个数的二次方时,只需要传入参数m即可:

    >>> exponentiation(3)
    9
    

     需要注意的是,必选参数必须在前,默认参数在后,否则Python的解释器会报错;为了避免不必要的坑,默认参数必须指向不变对象。 另外,在函数定义的时候就会确定默认参数的值,当我们改变x的值的时候对默认参数值并没有影响,即:

    >>> x = 42
    >>> def spam(a, b=x):
    ...     print(a, b)
    ...
    >>> spam(1)
    1 42
    >>> x = 23
    >>> spam(1)
    1 42
    >>>
    
  • 可变参数

      可变参数是python函数灵活性的表现之一,如果有这样一个需求,编写一个函数计算若干个数的和,这时可变参数便是一种好的选择:

    def calc_sum(*args):
        sum = 0
        for arg in args:
            sum += arg
        return sum
    
    
    print(calc_sum(1, 2, 3)) # 可变参数可传入0个或任意个参数,在函数调用时自动组装为一个tuple
    nums = [1, 2, 3]
    print(calc_sum(*nums))  # *nums意思是将nums中的所有元素以可变参数的形式传入函数
    
  • 关键字参数

     关键字参数允许你传入0个或任意个含参数名的参数,在函数内部会自动组装为一个dict。

    def print_info(pc_id, pc_name, **kwargs):
        print(pc_id, pc_name, kwargs, sep='\n')
    
    
    other_info = {'city': 'NanJing', 'company': 'APPLE'}
    print_info('0001', 'IMac-2018', city='NanJing', company='APPLE')
    print_info('0001', 'IMac-2018', **other_info)
    
    0001
    IMac-2018
    {'city': 'NanJing', 'company': 'APPLE'}
    0001
    IMac-2018
    {'city': 'NanJing', 'company': 'APPLE'}
    

 关键字参数可以传入任意参数名,那我们可不可以限制他传入指定的参数名呢?答案是当然可以,采用命名关键字可以实现该需求。但是这里需要搞清楚一个逻辑上的问题。这里的指定的参数名要和前面的必选参数区分开来。命名关键字参数如果有默认值可以不传入,但是如果传入就只能是已经限定的关键字参数。

def print_info(pc_id, pc_name, *, city, company):
    # 用一个 *,区分必选参数和关键字参数
    print("pc_id: %s\tpc_name: %s" % (pc_id, pc_name), end='\t')
    if city:
        print('city:%s' % city, end='\t')
    if company:
        print('company:%s' % company)


other_info = {'city': 'NanJing', 'company': 'APPLE'}

print_info('0001', 'IMac-2018', city='NanJing', company='APPLE')
print_info('0001', 'IMac-2018', **other_info)

# 但是如果函数定义中已经有了一个可变参数,命名关键字参数就不再需要分隔符*了
# def print_info(pc_id, pc_name, *args, city, company):
  • 小结

    以上四种参数可以灵活的进行组合应用,需要注意的是,参数定义的顺序必须是:必选参数、默认参数、可变参数、(命名关键字参数)关键字参数。

    def f(a, b, c=0, *args, **kw):
        print('a =', a, 'b =', b, 'c =', c, 'args =', args, 'kw =', kw)
    

特殊函数

匿名函数

  在python中可以通过lambda来定义匿名函数,其返回一个函数表达式,类似于def,但是比def更轻巧,可以没有名字。

# 关键字`lambda`表示匿名函数,冒号前面的`x`,`y`表示函数参数
# 匿名函数有个限制:只能有一个表达式,不用写return,返回值就是该表达式的结果。
>>>add = lambda x,y: x+y
>>>add(2,3)
5

 需要注意的是,lambda表达式中的参数x、y等是自由变量, 在运行时绑定值,而不是定义时就绑定,这跟函数的默认值参数定义是不同的。 因此在下面例子中,调用这个lambda表达式的时候,x的值是执行时的值。

>>> x = 10
>>> a = lambda y: x + y
>>> x = 20
>>> b = lambda y: x + y
>>> a(10)
30
>>> b(10)
30

高阶函数

  一个函数接收另一个函数作为参数,这种函数称之为高阶函数(Higher-order Functions)。python中有几个常用的内置高阶函数:

  • filter

    filter() 函数接收两个参数,一个函数和一个序列,其中传入的函数会依次作用于序列的每一个元素,根据返回值是True或则False决定是否保留该元素。

    filter(lambda x: x>10, [1, 56, 3, 36, 9])
    
    
    >>> g = filter(lambda x: x>10, [1, 56, 3, 36, 9])
    >>> g
    <filter object at 0x10207b470>
    >>> type(g)
    <class 'filter'>
    >>> list(g)
    [56, 36]
    
  • map/reduce

      map()函数接收两个参数,一个是函数,一个是Iterablemap将传入的函数依次作用到序列的每个元素,并把结果作为新的Iterator返回;reduce() 把一个函数作用在一个序列[x1, x2, x3, …]上,这个函数必须接收两个参数,reduce把结果继续和序列的下一个元素做累积计算。

    >>> r = map(lambda x:x*x, [1, 2, 3, 4, 5, 6, 7, 8, 9])
    >>> type(r)
    <class 'map'>
    >>> list(r)
    [1, 4, 9, 16, 25, 36, 49, 64, 81]
    
    from functools import reduce
    >>>reduce(lambda x,y: x+y, [1,2,3,4])
    10
    
  • sorted

      用于可迭代对象的排序,如:

    # 实例1
    >>> sorted([36, 5, -12, 9, -21], key=abs)
    [5, 9, -12, -21, 36]
    
  • 返回函数

      高阶函数除了可以接受函数作为参数,也可以将函数作为返回值,实现“延迟计算”。

    def calc_sum(lst):
        def lazy_sum():
            return sum(lst)
        return lazy_sum
    
    >>> f = calc_sum([1, 2, 3, 4])
    >>>f
    <function calc_sum.<locals>.lazy_sum at 0x10e9bd158>
    >>>f()
    10
    

偏函数

  偏函数的作用是为函数某些参数设置默认值,使调用更加方便,以下会是一个好的例子:

points = [ (1, 2), (3, 4), (5, 6), (7, 8) ]
import math
def distance(p1, p2):
    x1, y1 = p1
    x2, y2 = p2
    return math.hypot(x2 - x1, y2 - y1)
>>> pt = (4, 3)
>>> points.sort(key=partial(distance,p2=pt))  # 注意这里必须是p2=pt
>>> points
[(3, 4), (1, 2), (5, 6), (7, 8)]

回调函数

  在计算机程序设计中,回调函数,是指通过函数参数传递到其它代码的,某一块可执行代码的引用。这一设计允许了底层代码调用在高层定义的子程序。关于理解可以参考回调函数是什么。这里我们也给出一个例子辅助理解:

def greeting(name):
    print('hello %s!' % name)
    
def someone_coming(callback):
    name = input()
    callback(name)
    
def main():
    someone_coming(greeting)
    
if __name__ == '__main__':
    main()

内置函数

  这里确切的应该说是其他内置函数,因为上面也涉及很多内置函数,如sortedfiltermap等。另外,python内置函数非常之多,如果想了解更全面的内部函数,可以参考内置函数1内置函数2

  • zip()

     用于将可迭代的对象作为参数,将对象中对应的元素打包成一个个元组,然后返回由这些元组组成的列表。需要注意的是,当多个可迭代数据length不同时,取最小长度,其他忽略。

    >>>a = [1,2,3]
    >>> b = [4,5,6]
    >>> c = [4,5,6,7,8]
    >>> zipped = zip(a,b)     # 打包为元组的列表
    [(1, 4), (2, 5), (3, 6)]
    >>> zip(a,c)              # 元素个数与最短的列表一致
    [(1, 4), (2, 5), (3, 6)]
    >>> zip(*zipped)          # 与 zip 相反,*zipped 可理解为解压,返回二维矩阵式
    [(1, 2, 3), (4, 5, 6)]
    
  • reversed()

     用于反转序列,生成新的可迭代对象

    >>> a = reversed(range(10)) # 传入range对象
    >>> a # 类型变成迭代器
    <range_iterator object at 0x035634E8>
    >>> list(a)
    [9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
    
  • enumerate()

     enumerate() 函数用于将一个可遍历的数据对象(如列表、元组或字符串)组合为一个索引序列,同时列出数据和数据下标,一般用在 for 循环当中

    >>>seq = ['one', 'two', 'three']
    >>> for i, element in enumerate(seq):
    ...     print i, element
    
    0 one
    1 two
    2 three
    

猜你喜欢

转载自blog.csdn.net/jeffery0207/article/details/87885220