函数

函数定义语法:
  def 函数名([参数列表]):
    '''注释'''
    函数体

函数形参不需要声明其类型,也不需要指定函数返回值类型
即使该函数不需要接收任何参数,也必须保留一对空的圆括号
括号后面的冒号必不可少
函数体相对于def关键字必须保持一定的空格缩进
Python允许嵌套定义函数
在定义函数时,开头部分的注释并不是必需的,但是如果为函数的定义加上这段注释的话,可以为用户提供友好的提示和使用帮助。
Python是一种高级动态编程语言,变量类型是随时可以改变的。Python中的函数和自定义对象的成员也是可以随时发生改变的,可以为函数和自定义对象动态增加新成员。
lambda表达式可以用来声明匿名函数,也就是没有函数名字的临时使用的小函数,尤其适合需要一个函数作为另一个函数参数的场合。

f = lambda x, z, y: x+y+z       # 三个参数
print(f(3, 4, 5))
# 12

g = lambda x, y=2, z=3: x+y+z       # 一个参数,另两个有缺省参数
print(g(1))
# 6
print(g(1, z=4, y=5))
# 10

L = [(lambda x: x**2), (lambda x: x**3), (lambda x: x**4)]      # lambda表达式作为列表元素
print(L[0](2), L[1](2), L[2](2))
# 4 8 16

D = {'f1': (lambda: 2+3), 'f2': (lambda: 2*3), 'f3': (lambda: 2**3)}        # lambda表达式作为字典的值
print(D['f1'](), D['f2'](), D['f3']())
# 5 6 8

L = [1, 2, 3, 4, 5]
print(list(map(lambda x: x+10, L)))        # 模拟向量运算
# [11, 12, 13, 14, 15]

   内置函数map()可以将一个函数作用到一个序列或迭代器对象上。

print(list(map(str, range(5))))
# ['0', '1', '2', '3', '4']


def add5(n): return n+5
print(list(map(add5, range(10))))
# [5, 6, 7, 8, 9, 10, 11, 12, 13, 14]


def add(x, y): return x+y
print(list(map(add, range(5), range(5))))
# [0, 2, 4, 6, 8]

   标准库functools中的reduce()函数可以将一个接受2个参数的函数以累积的方式从左到右依次作用到一个序列或迭代器对象的所有元素上。

from functools import reduce

seq = [1, 2, 3, 4, 5, 6, 7, 8, 9]
print(reduce(lambda x, y: x+y, seq))
# 45


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

print(reduce(add, range(10)))
# 45
print(reduce(add, map(str, range(10))))
# 0123456789

  内置函数filter将一个函数作用到一个序列上,返回该序列中使得该函数返回值为True的那些元素组成的filter对象。

seq = ['foo', 'x41', '?!', '***']

def func(x):
    return x.isalnum()

print(list(filter(func, seq)))
# ['foo', 'x41']

生成器
  包含yield语句的函数可以用来创建生成器对象,这样的函数也称生成器函数。yield语句与return语句的作用相似,都是用来从函数中返回值。与return语句不同的是,return语句一旦执行会立刻结束函数的运行,而每次执行到yield语句并返回一个值之后会暂停或挂起后面代码的执行,下次通过生成器对象的__next__()方法、内置函数next()、for循环遍历生成器对象元素或其他方式显式“索要”数据时恢复执行。生成器具有惰性求值的特点,适合大数据处理。
  Python使用生成器对延迟操作提供了支持。所谓延迟操作,是指在需要的时候才产生结果,而不是立即产生结果。这也是生成器的主要好处。

  python有两种不同的方式提供生成器:
    生成器函数:
      常规函数定义,但是,使用yield语句而不是return语句返回结果。yield语句一次返回一个结果,在每个结果中间,挂起函数的状态,以便下次重它离开的地方继续执行
    生成器表达式:
      类似于列表推导,但是,生成器返回按需产生结果的一个对象,而不是一次构建一个结果列表。
  注意,生成器只能遍历一次,再次遍历生成器将不会有任何记录。

def fib(n):
    '''
    普通函数
    '''
    a, b = 0, 1
    while a < n:
        print(a, end=' ')
        a, b = b, a+b

fib(100)
# 0 1 1 2 3 5 8 13 21 34 55 89


def f():
    '''
    生成器函数
    '''
    a, b = 0, 1
    while True:
        yield a
        a, b = b, a+b

a = f()
for i in range(20):
    print(a.__next__(), end=' ')
# 0 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597 2584 4181


def gen():
    yield 1
    yield 2
    yield 3

x, y, z = gen()
print(x, y, z)
# 1 2 3

 函数嵌套定义
  在Python中,函数是可以嵌套定义的。

def myMap(iterable, op, value):          # 自定义函数
    if op not in '+-*/':
        return 'Error operator'

    def nested(item):                          # 嵌套定义函数
        return eval(str(item)+op+str(value))
    return map(nested, iterable)         # 使用在函数内部定义的函数


print(list(myMap(range(5), '+', 5)))    # 调用外部函数,不需要关心其内部实现
# [5, 6, 7, 8, 9]
print(list(myMap(range(5), '-', 5)))
# [-5, -4, -3, -2, -1]
print(list(myMap(range(5), '*', 5)))
# [0, 5, 10, 15, 20]
print(list(myMap(range(5), '/', 5)))
# [0.0, 0.2, 0.4, 0.6, 0.8]

  可以使用嵌套函数定义可调用对象。
  任何包含__call__()方法的类的对象都是可调用的。

def linear(a, b):
    def result(x):
        return a * x + b
    return result


class Linear:
    def __init__(self, a, b):
        self.a, self.b = a, b

    def __call__(self, x):
        return self.a * x + self.b


taxes = linear(0.3, 2)      # 函数嵌套方式
print(taxes(5))
# 3.5

taxes = Linear(0.3, 2)      # 类方式
print(taxes(5))
# 3.5

 装饰器
  装饰器(decorator)是函数嵌套定义的另一个重要应用。装饰器本质上也是一个函数,只不过这个函数接收其他函数作为参数并对其进行一定的改造之后返回新函数。类中的静态方法、类方法、属性等也都是通过修饰器实现的,Python中还有很多这样的用法。

def check_permission(func):
    def wrapper(*args, **kwargs):
        if kwargs.get('username') != 'admin':
            raise Exception('Sorry. You are not allowed.')
        return func(*args, **kwargs)
    return wrapper


class ReadWriteFile(object):
    # 把函数check_permission作为装饰器使用
    @check_permission
    def read(self, username, filename):
        return open(filename, 'r').read()

    def write(self, username, filename, content):
        open(filename, 'a+').write(content)
    # 把函数check_permission作为普通函数使用
    write = check_permission(write)


t = ReadWriteFile()
print('Originally.......')
print(t.read(username='admin', filename=r'e:/test/a123.txt'))
print('Now, try to write to a file........')
t.write(username='admin', filename=r'e:/test/a123.txt', content='\nhello world')
print('After calling to write...........')
print(t.read(username='admin', filename=r'e:/test/a123.txt'))

'''
Originally.......
原始文本
Now, try to write to a file........
After calling to write...........
原始文本
hello world
'''

 函数柯里化
  在计算机科学中,柯里化(Currying)是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数且返回结果的新函数的技术。

  偏函数
    偏函数(partial function)和函数柯里化(function currying)是函数式编程中常用的技术。有时候我们在复用已有函数时可能需要固定其中的部分参数,这除了可以通过默认值参数来实现之外,还可以使用偏函数。
    也可以使用标准库functools提供的partial()方法创建指定函数的偏函数。

from functools import partial


def add3(a, b, c):
    return a+b+c


def add2(a, c):
    return add3(a, 666, c)


print(add2(1, 2))
# 669

add2 = partial(add3, b=666)     # 使用标准库functools提供的partial()方法创建指定函数的偏函数。
print(add2(a=1, c=2))
# 669

  单参函数实现多参函数
    函数柯里化除了可以实现偏函数类似的功能之外,还可以利用单参数函数来实现多参数函数,这要归功于Python对函数嵌套定义和lambda表达式的支持。

def func(a):
    # lambda表达式实现
    return lambda b: a+b


print(func(3)(5))
# 8


def func(a):
    # 函数嵌套定义实现
    def funcNested(b):
        return a+b
    return funcNested


print(func(3)(5))
# 8


def func(a):
    # 多级函数嵌套实现多参数要求
    def funcNested(b):
        def funcNestedNested(c):
            return a+b+c
        return funcNestedNested
    return funcNested


print(func(3)(5)(8))
# 16