学习笔记

0207-高级特性-高级函数

Posted on 2022-02-07,12 min read
封面图

函数的参数

位置参数
按照对应的位置引用参数;
默认参数

def power(x, n=2):
    s = 1
    while n > 0:
        n = n - 1
        s = s * x
    return s
#调用
>>> power(5)
25
>>> power(5, 2)
25

例子2:

def enroll(name, gender, age=6, city='Beijing'):
    print('name:', name)
    print('gender:', gender)
    print('age:', age)
    print('city:', city)

不按顺序提供部分默认参数时,需要把参数名写上。比如调用enroll('Adam', 'M', city='Tianjin'),意思是,city参数用传进去的值,其他默认参数继续使用默认值。
默认参数的:
定义一个函数,传入一个list,添加一个END再返回:

def add_end(L=[]):
    L.append('END')
    return L
#调用
>>> add_end()
['END']
#但是,再次调用add_end()时,结果就不对了:
>>> add_end()
['END', 'END']
>>> add_end()
['END', 'END', 'END']

应该用None这个不变对象来实现:

def add_end(L=None):
    if L is None:
        L = []
    L.append('END')
    return L

可变参数
给定一组数字a,b,c……,请计算a2 + b2 + c2 + ……
方法一: 对于提前组装好的list或tuple:

def calc(numbers):
    sum = 0
    for n in numbers:
        sum = sum + n * n
    return sum
#调用
>>> calc([1, 2, 3])
14
>>> calc((1, 3, 5, 7))
84

方法二:利用可变参数:

def calc(*numbers):
    sum = 0
    for n in numbers:
        sum = sum + n * n
    return sum
#直接数字
>>> calc(1, 2)
5
>>> calc()
0
#引用list或tuple:
>>> nums = [1, 2, 3]
>>> calc(*nums)
14

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

def person(name, age, **kw):
    print('name:', name, 'age:', age, 'other:', kw)
#调用
>>> person('Michael', 30)
name: Michael age: 30 other: {}
>>> person('Bob', 35, city='Beijing')
name: Bob age: 35 other: {'city': 'Beijing'}
>>> person('Adam', 45, gender='M', job='Engineer')
name: Adam age: 45 other: {'gender': 'M', 'job': 'Engineer'}
#调用dict
>>> extra = {'city': 'Beijing', 'job': 'Engineer'}
>>> person('Jack', 24, **extra)
name: Jack age: 24 other: {'city': 'Beijing', 'job': 'Engineer'}

命名关键字参数
只接收cityjob作为关键字参数, 这种方式定义的函数如下:
用到特殊分隔符**后面的参数被视为命名关键字参数;

def person(name, age, *, city, job):
    print(name, age, city, job)
#调用
>>> person('Jack', 24, city='Beijing', job='Engineer')
Jack 24 Beijing Engineer
#已经有了一个可变参数
def person(name, age, *args, city, job):
    print(name, age, args, city, job)

参数组合
参数定义的顺序必须是:必选参数、默认参数、可变参数、命名关键字参数和关键字参数。

递归函数

计算阶乘n! = 1 x 2 x 3 x ... x n

def fact(n):
    if n==1:
        return 1
    return n * fact(n - 1)

递归调用栈溢出的方法是通过尾递归优化:

def fact(n):
    return fact_iter(n, 1)
def fact_iter(num, product):
    if num == 1:
        return product
    return fact_iter(num - 1, num * product)
#调用:
fact_iter(5, 1)
===> fact_iter(5, 1)
===> fact_iter(4, 5)
===> fact_iter(3, 20)
===> fact_iter(2, 60)
===> fact_iter(1, 120)
===> 120

汉诺塔的移动可以用递归函数非常简单地实现。

# -*- coding: utf-8 -*-
def move(n, a, b, c):
    if n == 1:
        print(a, '-->', c)
    else:
        move(n - 1, a, c, b)
        print(a, '-->', c)
        move(n - 1, b, a, c)
#调用
move(3, 'A', 'B', 'C')

高级特性

切片

>>> L = ['Michael', 'Sarah', 'Tracy', 'Bob', 'Jack']
>>> L[0:3]
['Michael', 'Sarah', 'Tracy']

L[0:3]表示,从索引0开始取,直到索引3为止,但不包括索引3.
第一个索引是0,可以省略:

>>> L[:3]
['Michael', 'Sarah', 'Tracy']

倒数切片:

>>> L[-2:]
['Bob', 'Jack']
>>> L[-2:-1]
['Bob']
>>> L[:10:2]
[0, 2, 4, 6, 8]

迭代

dict迭代的是key。如果要迭代value,可以用for value in d.values(),如果要同时迭代keyvalue,可以用for k, v in d.items().
判断是否可以迭代:

>>> from collections.abc import Iterable
>>> isinstance('abc', Iterable) # str是否可迭代
True
>>> isinstance([1,2,3], Iterable) # list是否可迭代
True
>>> isinstance(123, Iterable) # 整数是否可迭代
False

列表生成式

把要生成的元素x * x放到前面,后面跟for循环,就可以把list创建出来;

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

后面还可以接过滤条件:

>>> [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']

列出当前目录下的所有文件和目录名:

>>> import os # 导入os模块,模块的概念后面讲到
>>> [d for d in os.listdir('.')] # os.listdir可以列出文件和目录
['.emacs.d', '.ssh', '.Trash', 'Adlm', 'Applications', 'Desktop', 'Documents', 'Downloads', 'Library', 'Movies', 'Music', 'Pictures', 'Public', 'VirtualBox VMs', 'Workspace', 'XCode']

使用两个变量来生成list:

>>> d = {'x': 'A', 'y': 'B', 'z': 'C' }
>>> [k + '=' + v for k, v in d.items()]
['y=B', 'x=A', 'z=C']

if...else...
for后面的if是一个筛选条件,不能带else

>>> [x for x in range(1, 11) if x % 2 == 0 else 0]
  File "<stdin>", line 1
    [x for x in range(1, 11) if x % 2 == 0 else 0]
                                              ^
SyntaxError: invalid syntax

for前面的部分是一个表达式,它必须根据x计算出一个结果,必须加上else:

>>> [x if x % 2 == 0 else -x for x in range(1, 11)]
[-1, 2, -3, 4, -5, 6, -7, 8, -9, 10]

生成器

1. 将列表生成式的[]变为()即是生成器:

>>> 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
<generator object <genexpr> at 0x1022ef630>

可以next()不断生成下一个对象,一般用for...in...迭代产生:

>>> next(g)
0
>>> next(g)
1
>>> next(g)
4
>>> next(g)
9
>>> g = (x * x for x in range(10))
>>> for n in g:
...     print(n)
... 
0
1
4
9
.
.
81

2. 函数中加yield即为generator函数:
斐波拉契数列生成器:

def fib(max):
    n, a, b = 0, 0, 1
    while n < max:
        yield b
        a, b = b, a + b
        n = n + 1
    return 'done'
>>> f = fib(6)
>>> f
<generator object fib at 0x104feaaa0>

调用generator函数会创建一个generator对象,多次调用generator函数会创建多个相互独立的generator。
杨辉三角:

def triangles():
    last = [1]
    while True:
        yield last
        next = [last[n]+last[n-1] for n in range(1,len(last))] #中间元素
        next = [1]+next+[1] #首尾加1
        last = next

迭代器

可以直接作用于for循环的数据类型有以下几种:

一类是集合数据类型,如listtupledictsetstr等;

一类是generator,包括生成器和带yieldgenerator function

这些可以直接作用于for循环的对象统称为可迭代对象:Iterable
使用isinstance()判断一个对象是否是Iterable对象:

>>> from collections.abc import Iterable
>>> isinstance([], Iterable)
True
>>> isinstance({}, Iterable)
True
>>> isinstance('abc', Iterable)
True
>>> isinstance((x for x in range(10)), Iterable)
True
>>> isinstance(100, Iterable)
False

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

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

>>> from collections.abc import Iterator
>>> isinstance((x for x in range(10)), Iterator)
True
>>> isinstance([], Iterator)
False
>>> isinstance({}, Iterator)
False
>>> isinstance('abc', Iterator)
False

不能提前知道序列的长度,只能通过next()函数实现按需计算下一个数据,所以Iterator的计算是惰性的,只有在需要返回下一个数据时它才会计算。
Iterator甚至可以表示一个无限大的数据流,例如全体自然数。

函数式编程

高阶函数

把函数作为参数传入,这样的函数称为高阶函数,函数式编程就是指这种高度抽象的编程范式。
简单的示例:

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

map/reduce

>>> def f(x):
...     return x * x
...
>>> r = map(f, [1, 2, 3, 4, 5, 6, 7, 8, 9])
>>> list(r)
[1, 4, 9, 16, 25, 36, 49, 64, 81]
#reduce
reduce(f, [x1, x2, x3, x4]) = f(f(f(x1, x2), x3), x4)

利用map和reduce编写一个str2float函数:

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from functools import reduce

def str2float(s):
    DIGITS = {'0': 0, '1': 1, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9}
    index = s.find('.')
    def char2num(s):
        return DIGITS[s]
    to_int = reduce(lambda x, y: x * 10 + y, map(char2num, s.replace('.','')))
    return to_int * (10 ** (index - len(s) + 1))                # s含小数点,所以index-len(s)会多减去一位数,需要加1补足

print(str2float('111.11'))

filter
str.strip( '0' ); # 去除首尾字符 0
str2.strip(); #去除首尾空格
filter()把传入的函数依次作用于每个元素,然后根据返回值是True:保留;还是False:丢弃该元素。
保留奇数:

def is_odd(n):
    return n % 2 == 1

list(filter(is_odd, [1, 2, 4, 5, 6, 9, 10, 15]))
# 结果: [1, 5, 9, 15]

求素数:利用埃氏筛法

构造一个从3开始的奇数序列:
def _odd_iter():
    n = 1
    while True:
        n = n + 2
        yield n
定义一个筛选函数:
def _not_divisible(n):
    return lambda x: x % n > 0
定义一个生成器,不断返回下一个素数:
def primes():
    yield 2
    it = _odd_iter() # 初始序列
    while True:
        n = next(it) # 返回序列的第一个数
        yield n
        it = filter(_not_divisible(n), it) # 构造新序列
设置一个退出循环的条件:
# 打印1000以内的素数:
for n in primes():
    if n < 1000:
        print(n)
    else:
        break

利用filter() 筛选出回数

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
def is_palindrome(n):
    a=str(n)
    b=a[::-1]
    if a==b:
        return n
# 测试:
output = filter(is_palindrome, range(1, 1000))
print('1~1000:', list(output))

sorted

>>> sorted([36, 5, -12, 9, -21], key=abs)
[5, 9, -12, -21, 36]

key选择的函数是对list每项作用, 而不是作用在整个list
要进行反向排序,不必改动key函数,可以传入第三个参数reverse=True

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

下一篇: 0206-underscore-Python→

loading...