Tips:

  • 函数是Python内建支持的一种封装,函数类似于数学中的公式,可以把大段代码封装定义一个函数;函数内部逻辑就好比定义数学公式内部的逻辑,使用公式只用调研函数就可以了,避免重复书写函数内部逻辑。
  • Python不但能非常灵活地定义函数,而且本身内置了很多有用的函数,可以直接调用

调用函数

要调用一个函数,需要知道函数的名称和参数

  • 函数名其实就是指向一个函数对象的引用,完全可以把函数名赋给一个变量,相当于给这个函数起了一个“别名”:
  1. >> a = abs # 变量a指向abs函数
  2. >> a(-1) # 所以也可以通过a调用abs函数
  3. 1
  • 函数参数不能传错
  1. >>> abs(-20) # 接收一个参数,参数类型为数字。 如果参数数量或类型不对会报错。
  2. 20
  3. >>> max(20,30,4) #接收任意个数参数并返回最大那个
  4. 30

python内置常用函数

  • 数据类型转换
    1. >>> int('123') # 把其他数据类型转换为整形
    2. 123
    3. >>> int(12.34)
    4. 12
    5. >>> float('12.34')
    6. 12.34
    7. >>> str(1.23)
    8. '1.23'
    9. >>> str(100)
    10. '100'
    11. >>> bool(1)
    12. True
    13. >>> bool('')
    14. False

定义函数

定义一个函数要使用def语句,依次写出函数名、括号、括号中的参数和冒号:,然后,在缩进块中编写函数体,函数的返回值用return语句返回

  • 函数体内部的语句在执行时,一旦执行到return时,函数就执行完毕,并将结果返回。因此,函数内部通过条件判断和循环可以实现非常复杂的逻辑。如果没有return语句,函数执行完毕后也会返回结果,只是结果为Nonereturn None可以简写为return

  • 在Python交互环境中定义函数时,注意Python会出现...的提示。函数定义结束后需要按两次回车重新回到>>>提示符下

  • 函数可以同时返回多个值,但其实就是一个tuple。

  • lambda函数

    1. lambda x, y: x * 10 + y #lambda开始,后紧跟传入参数,冒号后编写返回数据逻辑

示例:

  • 定义函数
  1. # 自己定义一个函数
  2. def my_abs(x):
  3. if x >= 0:
  4. return x
  5. else:
  6. return -x
  7. # 调用函数
  8. my_abs(100)
  • 定义空函数:用pass代替内部逻辑,使得不会报错,用于设计
    1. def nop():
    2. pass

函数的参数

除了正常定义的必选参数外,还可以使用默认参数、可变参数和关键字参数,使得函数定义出来的接口,不但能处理复杂的参数,还可以简化调用者的代码

必选参数
  • 必须传入的参数,调用函数时,传入的值按照位置顺序依次赋给参数

默认参数

  • 函数定义时设置默认值,函数调用时可以传入参数值,也可以不传入使用默认值。

  • 必选参数在前,默认参数在后。

  • 默认参数降低了函数调用的难度,而一旦需要更复杂的调用时,又可以传递更多的参数来实现。无论是简单调用还是复杂调用,函数只需要定义一个。

  • 有多个默认参数时,调用的时候,既可以按顺序提供默认参数, 也可以不按顺序提供部分默认参数。当不按顺序提供部分默认参数时,需要把参数名写上

  • 默认参数必须指向不变对象

可变参数

  • 可变参数就是传入的参数个数是可变的。 允许你传入0个或任意个参数,这些可变参数在函数调用时自动组装为一个tuple

  • 定义时在参数前面加了一个号,调用时传入多个参数,在函数调用时被组装成一个tuple。 也可以把list或tuple前加作为参数值传入。

关键字参数

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

  • 调用函数时可以先组装出一个dict,然后,把该dict前加**转换为关键字参数传进去

  1. def person(name, age, **kw):
  2. print('name:', name, 'age:', age, 'other:', kw)
  3. >>> person('Bob', 35, city='Beijing')
  4. name: Bob age: 35 other: {'city': 'Beijing'}
  5. >>> person('Adam', 45, gender='M', job='Engineer')
  6. name: Adam age: 45 other: {'gender': 'M', 'job': 'Engineer'}

命名关键字参数
  • 如果要限制关键字参数的名字,就可以用命名关键字参数

  • 函数定义方式:命名关键字参数需要一个特殊分隔符**后面的参数被视为命名关键字参数。 如果函数定义中已经有了一个可变参数,后面跟着的命名关键字参数就不再需要一个特殊分隔符*

  • 命名关键字参数可以有缺省值,从而简化调用: ```python def person(name, age, *, city, job): print(name, age, city, job)

def person(name, age, args, city, job): # 如果函数定义中已经有了一个可变参数,后面跟着的命名关键字参数就不再需要一个特殊分隔符了 print(name, age, args, city, job)

  1. - 函数调用方式:命名关键字参数必须传入参数名,这和位置参数不同。如果没有传入参数名,调用将报错
  2. ```python
  3. >>> person('Jack', 24, city='Beijing', job='Engineer')
  4. Jack 24 Beijing Engineer
  5. >>> person('Jack', 24, 'Beijing', 'Engineer') #由于调用时缺少参数名city和job,Python解释器把这4个参数均视为位置参数,但person()函数仅接受2个位置参数
  6. Traceback (most recent call last):
  7. File "<stdin>", line 1, in <module>
  8. TypeError: person() takes 2 positional arguments but 4 were given
  • 使用命名关键字参数时,要特别注意,如果没有可变参数,就必须加一个作为特殊分隔符。如果缺少,Python解释器将无法识别位置参数和命名关键字参数

参数组合使用

在Python中定义函数,可以用必选参数、默认参数、可变参数、关键字参数和命名关键字参数,这5种参数都可以组合使用。参数定义的顺序必须是:必选参数、默认参数、可变参数、命名关键字参数和关键字参数。

  • ```python def f1(a, b, c=0, args, *kw): print(‘a =’, a, ‘b =’, b, ‘c =’, c, ‘args =’, args, ‘kw =’, kw)

def f2(a, b, c=0, , d, *kw): print(‘a =’, a, ‘b =’, b, ‘c =’, c, ‘d =’, d, ‘kw =’, kw)

f1(1, 2) a = 1 b = 2 c = 0 args = () kw = {} f1(1, 2, c=3) a = 1 b = 2 c = 3 args = () kw = {} f1(1, 2, 3, ‘a’, ‘b’) a = 1 b = 2 c = 3 args = (‘a’, ‘b’) kw = {} f1(1, 2, 3, ‘a’, ‘b’, x=99) a = 1 b = 2 c = 3 args = (‘a’, ‘b’) kw = {‘x’: 99} f2(1, 2, d=99, ext=None) a = 1 b = 2 c = 0 d = 99 kw = {‘ext’: None}

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

args = (1, 2, 3, 4) kw = {‘d’: 99, ‘x’: ‘#’} f1(args, **kw) a = 1 b = 2 c = 3 args = (4,) kw = {‘d’: 99, ‘x’: ‘#’} args = (1, 2, 3) kw = {‘d’: 88, ‘x’: ‘#’} f2(args, **kw) a = 1 b = 2 c = 3 d = 88 kw = {‘x’: ‘#’} ```

  • Python的函数具有非常灵活的参数形态,既可以实现简单的调用,又可以传入非常复杂的参数。

  • 默认参数一定要用不可变对象,如果是可变对象,程序运行时会有逻辑错误!

  • 要注意定义可变参数和关键字参数的语法:

    • *args是可变参数,args接收的是一个tuple;

    • **kw是关键字参数,kw接收的是一个dict。

  • 以及调用函数时如何传入可变参数和关键字参数的语法:

  • 可变参数既可以直接传入:func(1, 2, 3),又可以先组装list或tuple,再通过*args传入:func(*(1, 2, 3))

  • 关键字参数既可以直接传入:func(a=1, b=2),又可以先组装dict,再通过**kw传入:func(**{'a': 1, 'b': 2})

  • 使用*args**kw是Python的习惯写法,当然也可以用其他参数名,但最好使用习惯用法。

  • 命名的关键字参数是为了限制调用者可以传入的参数名,同时可以提供默认值。

  • 定义命名的关键字参数在没有可变参数的情况下不要忘了写分隔符*,否则定义的将是位置参数。

递归函数

  • 如果一个函数在内部调用自身本身,这个函数就是递归函数

  • 递归函数的优点是定义简单,逻辑清晰。理论上,所有的递归函数都可以写成循环的方式,但循环的逻辑不如递归清晰。

  • 使用递归函数需要注意防止栈溢出。在计算机中,函数调用是通过栈(stack)这种数据结构实现的,每当进入一个函数调用,栈就会加一层栈帧,每当函数返回,栈就会减一层栈帧。由于栈的大小不是无限的,所以,递归调用的次数过多,会导致栈溢出。解决递归调用栈溢出的方法是通过尾递归优化

  • 尾递归是指,在函数返回的时候,调用自身本身,并且,return语句不能包含表达式。这样,编译器或者解释器就可以把尾递归做优化,使递归本身无论调用多少次,都只占用一个栈帧,不会出现栈溢出的情况。

  • 遗憾的是,大多数编程语言没有针对尾递归做优化,Python解释器也没有做优化,所以,即使把上面的fact(n)函数改成尾递归方式,也会导致栈溢出。

函数高级特性

函数内置的一些方法,可以简化代码操作

切片

针对列表类型的数据如字符串、list、tuple,通过切片可快速截取其中部分数据。

  • L[0:3]表示,从索引0开始取,直到索引3为止,但不包括索引3。即索引012,正好是3个元素。

  • 如果第一个索引是0,还可以省略: L[:3]

  • 步长:

    • 所有数,每5个取一个:
      1. >>> L[::5]
      2. [0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 65, 70, 75, 80, 85, 90, 95]
  • 字符串'xxx'也可以看成是一种list,每个元素就是一个字符。因此,字符串也可以用切片操作,操作结果仍是字符串

迭代

迭代:针对可迭代对象,通过for循环来遍历

  • ```python

    因为dict的存储不是按照list的方式顺序排列,所以,迭代出的结果顺序很可能不一样。

    默认情况下,dict迭代的是key。如果要迭代value,可以用for value in d.values(),如果要同时迭代key和value,可以用for k, v in d.items()。

    d = {‘a’: 1, ‘b’: 2, ‘c’: 3} for key in d: … print(key) … a c b

由于字符串也是可迭代对象,因此,也可以作用于for循环:

for ch in ‘ABC’: … print(ch) … A B C

for x, y in [(1, 1), (2, 4), (3, 9)]: … print(x, y) … 1 1 2 4 3 9 ```

列表生成式

列表生成式即List Comprehensions,是Python内置的非常简单却强大的可以用来创建list的生成式.运用列表生成式,可以快速生成list,可以通过一个list推导出另一个list,而代码却十分简洁。

  • ```python

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

[x * x for x in range(1, 11)] [1, 4, 9, 16, 25, 36, 49, 64, 81, 100]

for循环后面还可以加上if判断

[x * x for x in range(1, 11) if x % 2 == 0] [4, 16, 36, 64, 100]

使用两层循环,可以生成全排列

[m + n for m in ‘ABC’ for n in ‘XYZ’] [‘AX’, ‘AY’, ‘AZ’, ‘BX’, ‘BY’, ‘BZ’, ‘CX’, ‘CY’, ‘CZ’] ```

生成器

  • 生成器(generator):按照某种算法推算出来,不必创建完整的list,一边循环一边计算的机制。

    • 不必创建完整的list,从而节省大量的空间

创建generator方法:

  • 1 把一个列表生成式的[]改成(),就创建了一个generator ```python

    L = [x x for x in range(10)] L [0, 1, 4, 9, 16, 25, 36, 49, 64, 81] g = (x x for x in range(10)) g at 0x1022ef630>

通过next()函数获得generator的下一个返回值。generator保存的是算法,每次调用next(g),就计算出g的下一个元素的值,直到计算到最后一个元素,没有更多的元素时,抛出StopIteration的错误

next(g) 0 next(g) 1

上面这种不断调用next(g)实在是太变态了,正确的方法是使用for循环,因为generator也是可迭代对象

g = (x * x for x in range(10)) for n in g: … print(n) … 0 1 4 ```

  • yield函数

    • 如果推算的算法比较复杂,用类似列表生成式的for循环无法实现的时候使用函数创建

    • 把函数内的print(b)改为yield b就可以了。 如果一个函数定义中包含yield关键字,那么这个函数就不再是一个普通函数,而是一个generator

    • 调用该generator时,首先要生成一个generator对象,然后用next()函数不断获得下一个返回值,在每次调用next()的时候执行,遇到yield语句返回,再次执行时从上次返回的yield语句处继续执行。

      1. def fib(max):
      2. n, a, b = 0, 0, 1
      3. while n < max:
      4. yield b
      5. a, b = b, a + b
      6. n = n + 1
      7. return 'done'

可迭代对象Iterable

  • 可直接作用于for循环的对象统称为可迭代对象

    • 可以直接作用于for循环的数据类型有以下几种:一类是集合数据类型,如listtupledictsetstr等;一类是generator,包括生成器和带yield的generator function。

    • 可以使用isinstance()判断一个对象是否是Iterable对象

      1. >>> from collections import Iterable
      2. >>> isinstance([], Iterable)
      3. True
      4. >>> isinstance({}, Iterable)
      5. True
      6. >>> isinstance('abc', Iterable)
      7. True
      8. >>> isinstance((x for x in range(10)), Iterable)
      9. True
      10. >>> isinstance(100, Iterable)
      11. False

迭代器Iterator

  • 可以被next()函数调用并不断返回下一个值的对象称为迭代器。

  • 可以使用isinstance()判断一个对象是否是Iterator对象

    1. >>> from collections import Iterator
    2. >>> isinstance((x for x in range(10)), Iterator)
    3. True
    4. >>> isinstance([], Iterator)
    5. False
    6. >>> isinstance({}, Iterator)
    7. False
    8. >>> isinstance('abc', Iterator)
    9. False
  • listdictstrIterable变成Iterator可以使用iter()函数:

    1. >>> isinstance(iter([]), Iterator)
    2. True
    3. >>> isinstance(iter('abc'), Iterator)
    4. True
  • Python的Iterator对象表示的是一个数据流,Iterator对象可以被next()函数调用并不断返回下一个数据,直到没有数据时抛出StopIteration错误。可以把这个数据流看做是一个有序序列,但我们却不能提前知道序列的长度,只能不断通过next()函数实现按需计算下一个数据,所以Iterator的计算是惰性的,只有在需要返回下一个数据时它才会计算。

  • Iterator甚至可以表示一个无限大的数据流,例如全体自然数。而使用list是永远不可能存储全体自然数的

  • 生成器都是Iterator对象,但listdictstr虽然是Iterable,却不是Iterator

函数式编程

  • 函数式编程就是一种抽象程度很高的编程范式。类似计算。

  • 纯粹的函数式编程语言编写的函数没有变量,因此,任意一个函数,只要输入是确定的,输出就是确定的,这种纯函数我们称之为没有副作用。而允许使用变量的程序设计语言,由于函数内部的变量状态不确定,同样的输入,可能得到不同的输出,因此,这种函数是有副作用的。(python支持变量,不是纯函数式编程)

  • 函数式编程的一个特点就是,允许把函数本身作为参数传入另一个函数,还允许返回一个函数!

下面介绍一些python的函数式编程

高阶函数

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

  • 通过高阶函数,把运算规则抽象

  • 编写高阶函数,就是让函数的参数能够接收别的函数。 ```python def f(a): return x*x

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

  1. > python内置高阶函数介绍:mapreduce
  2. <a name="okenxn"></a>
  3. #### Map()
  4. - `map()`函数接收两个参数,一个是函数,一个是`Iterable``map`将传入的函数依次作用到序列的每个元素,并把结果作为新的`Iterator`返回。
  5. ```python
  6. >>> def f(x):
  7. ... return x * x
  8. ...
  9. >>> r = map(f, [1, 2, 3, 4, 5, 6, 7, 8, 9])
  10. >>> list(r) #r是一个Iterator,Iterator是惰性序列,通过list()函数让它把整个序列都计算出来并返回一个list
  11. [1, 4, 9, 16, 25, 36, 49, 64, 81]

Reduce

  • reduce可以把一个函数作用在一个序列[x1, x2, x3, ...]上,这个函数必须接收两个参数,reduce把结果继续和序列的下一个元素做累积计算。 ```python reduce(f, [x1, x2, x3, x4]) = f(f(f(x1, x2), x3), x4)

示例

from functools import reduce def add(x, y): … return x + y … reduce(add, [1, 3, 5, 7, 9]) 25 ```

filter

  • python内置的用于过滤序列, 接收一个函数和一个序列。filter()把传入的函数依次作用于每个元素,然后根据返回值是True还是False决定保留还是丢弃该元素。

  • filter()函数返回的是一个Iterator,也就是一个惰性序列,所以要强迫filter()完成计算结果,需要用list()函数获得所有结果并返回list。

sorted

  • 对list进行排序

  • 它还可以接收一个key函数来实现自定义的排序。key指定的函数将作用于list的每一个元素上,并根据key函数返回的结果进行排序

    1. >>> sorted([36, 5, -12, 9, -21], key=abs)
    2. [5, 9, -12, -21, 36]
  • 要进行反向排序,可以传入第三个参数reverse=True

    1. >>> sorted(['bob', 'about', 'Zoo', 'Credit'], key=str.lower, reverse=True)
    2. ['Zoo', 'Credit', 'bob', 'about']

返回函数

  • 高阶函数除了可以接受函数作为参数外,还可以把函数作为结果值返回。 ```python def lazy_sum(*args): def sum():
    1. ax = 0
    2. for n in args:
    3. ax = ax + n
    4. return ax
    return sum

f = lazy_sum(1, 3, 5, 7, 9) f

.sum at 0x101c6ed90>

f() 25 ```

  • 内部函数sum可以引用外部函数lazy_sum的参数和局部变量,当lazy_sum返回函数sum时,相关参数和变量都保存在返回的函数中,这种称为“闭包(Closure)”的程序结构拥有极大的威力。返回闭包时牢记一点:返回函数不要引用任何循环变量,或者后续会发生变化的变量。

  • 当我们调用lazy_sum()时,每次调用都会返回一个新的函数,即使传入相同的参数。返回一个函数时,该函数并未执行,返回函数中不要引用任何可能会变化的变量。

lambda匿名函数

  • 在传入函数时,有些时候,不需要显式地定义函数,直接传入匿名函数更方便。

  • 匿名函数lambda x: x * x,关键字lambda表示匿名函数,冒号前面的x表示函数参数。只能有一个表达式,不用写return,返回值就是该表达式的结果。

  • 匿名函数有个好处,因为函数没有名字,不必担心函数名冲突。此外,匿名函数也是一个函数对象,也可以把匿名函数赋值给一个变量,再利用变量来调用该函数。也可以把匿名函数作为返回值返回

Decorator装饰器

  • 增强函数的功能,比如,在函数调用前后自动打印日志,但又不希望修改now()函数的定义,这种在代码运行期间动态增加功能的方式,称之为“装饰器”(Decorator)。

  • 本质上,decorator就是一个返回函数的高阶函数

  1. def log(func):
  2. def wrapper(*args, **kw): # wrapper()函数的参数定义是(*args, **kw),因此,wrapper()函数可以接受任意参数的调用。
  3. print('call %s():' % func.__name__)
  4. return func(*args, **kw)
  5. return wrapper
  6. @log
  7. def now():
  8. print('2015-3-25')
  • 如果decorator本身需要传入参数,那就需要编写一个返回decorator的高阶函数 ```python def log(text): def decorator(func):
    1. def wrapper(*args, **kw):
    2. print('%s %s():' % (text, func.__name__))
    3. return func(*args, **kw)
    4. return wrapper
    return decorator

@log(‘execute’) def now(): print(‘2015-3-25’)

  1. <a name="gmg7nk"></a>
  2. ### 偏函数
  3. - 当函数的参数个数太多,需要简化时,使用`functools.partial`可以创建一个新的函数,这个新函数可以固定住原函数的部分参数,从而在调用时更简
  4. ```python
  5. >>> import functools
  6. >>> int2 = functools.partial(int, base=2)
  7. >>> int2('1000000')
  8. 64
  9. >>> int2('1010101')
  10. 85