Python02

Python高级特性

切片

切片用于简化list、tuple、str的索引操作。
如:取list的前三个元素:L[0:3]
切片tips:

  • 若第一个索引是0,可以简化:L[:3]
  • 支持倒数切片:L[-2:-1],表示取倒数第二个元素;L[-2:],表示取倒数两个元素。
  • 间隔取样:L[:10:2],表示前10个数,每两个取一个;L[::5]`,表示所有数,每5个取一个。
  • 什么都不写,只写[:]就可以原样复制一个list

利用切片实现trim()函数,去除字符串首位的空格:

def trim(s):
    while s[:1] == ' ':
        s=s[1:]

    while s[-1:] == ' ':
        s=s[:-1]

    return s

迭代

可以通过for...in来遍历的对象称为可迭代对象。
可以通过isinstance()来判断对象是否为collections.abc模块的Iterable类型:

isinstance('abc', Iterable) # True
isinstance([1,2,3], Iterable) # True
isinstance(123, Iterable) # False

常见的可迭代对象有:list、tuple、str、dict、set

  • 默认情况下,dict迭代的是key
    • 要迭代value,可以用for value in d.values()
    • 要同时迭代key和value,可以用for k, v in d.items(),for循环可以同时引用两个变量

想要实现类似Java下标循环,可以使用enumerate()函数,将对象变为索引-元素对,然后同时引用索引和值两个变量:

for i, value in enumerate(['A', 'B', 'C']):
    print(i, value)

# 0 A
# 1 B
# 2 C

列表生成器

Python内置了简单好用的list生成器。
list生成器中可以使用for、if来生成复合要求的list

使用for...in

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

L2 = [m + n for m in 'ABC' for n in 'XYZ']
# L2 = ['AX', 'AY', 'AZ', 'BX', 'BY', 'BZ', 'CX', 'CY', 'CZ']

还可以使用两个变量的for循环,如利用dict的items()函数可以同时迭代key和value:

L3 = [k+'='+v for k, v in {'x':'X', 'y':'Y', 'z':'Z'}.items()]
# L3 = ['x=X', 'y=Y', 'z=Z']

使用if

在列表生成器中,if一般放在for...in之后使用,用于筛选遍历的元素:

L1 = [x for x in range(1, 11) if x % 2 == 0]
# L1 = [2, 4, 6, 8, 10]

up = ['HELLO', 'WORLD', 18, 'APPLE', None]
low = [s.lower() for s in L1 if isinstance(s, str)]
# low = ['hello', 'world', 'apple']

if使用情况:

  • 跟在for后面的if是一个筛选条件,不能带else
  • 放在for前面的if ... else是表达式,必须带else

生成器

列表生成器的长度是受内存限制的,且使用大list存储少量元素会导致空间浪费。
所以Python提供了generator生成器,用于一边循环一遍计算元素内容。
创建generator:

  • 将列表生成器的[]换为(),即可创建生成器:g = (x * x for x in range(10))
  • 通过generator函数来定义生成器,在generator函数中,使用yeild返回元素,在生成器边界使用return

遍历generator:

  • 可以通过next()函数获取生成器的下一个元素,没有更多元素时抛出StopIteration异常
  • 通过for...in来遍历生成器:for v in g: print(v)

generator函数

函数定义中包含yield关键字,则这个函数是一个generator函数,调用一个generator函数将返回一个generator

# 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为生成器对象
# f = <generator object fib at 0x104feaaa0>

generator函数会在每次调用next()时执行,遇到yield语句返回,再次执行时从上次返回的yield语句处继续执行

def odd():
    print('step 1')
    yield 1
    print('step 2')
    yield 3

>>> o = odd()
>>> next(o)
step 1
1
>>> next(o)
step 2
3
>>> next(o)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration

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

>>> next(odd())
step 1
1
>>> next(odd())
step 1
1

杨辉三角生成器:

def triangles():
    L = [1]    
    while True:        
        yield  L        
        X = [0] + L + [0]        
        L = [X[i] + X[i+1] for i in range(len(L)+1)]

迭代器

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

  • 集合数据类型:list、tuple、set、dict、str等
  • generator,生成器和带yield的generate function

其中生成器可以用for循环,还可以用next()函数返回元素。
可以被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

可见生成器都是Iterator对象;list、dict、str虽然是Iterable,却不是Iterator,这是因为Iterator的计算是惰性的,只有在需要返回下一个数据时它才会计算。

可以使用iter()函数将其变为Iterator

函数式编程

函数式编程就是一种抽象程度很高的编程范式,允许把函数本身作为参数传入另一个函数,还允许返回一个函数。

高阶函数

Python中,变量可以指向函数,可以通过变量调用其指向的函数。
函数的参数可以接收变量,变量可以指向函数,所以函数的参数就可以传入函数,这样的函数称为高阶函数:

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

map/reduce

Python提供了map()和reduce()函数:

map

map()函数接收两个参数:

  1. 函数
  2. Iterable对象
  • map将传入的函数依次作用到序列的每个元素,并把结果作为新的Iterator返回
def f(x):
    return x * x

rst = map(x, [1, 2, 3, 4, 5])
list(rst)                     # [1, 4, 9, 16, 25]
list(map(str, [1, 2, 3, 4]))  # ['1', '2', '3', '4']

reduce

reduce()函数接收两个参数:

  1. 接收两个参数的函数
  2. Iterable对象
  • reduce把函数作用在序列[x1, x2, x3, ...]上
  • reduce把前两个元素输入函数,然后继续将结果和序列的下一个元素作为函数的输入,直到序列的全部元素输入函数
  • 可以等效为:reduce(f, [x1, x2, x3, x4]) = f(f(f(x1, x2), x3), x4)
from functools import reduce
# reduce()函数需要显式引入
def add(x, y):
    return x + y

reduce(add, [1, 3, 5, 7, 9])  # 25

map和reduce的应用

  1. 利用map()函数,把用户输入的不规范的英文名字,变为首字母大写,其他小写的规范名字。
def normalize(name):
    return name[0].upper() + name[1:].lower()
  1. 编写一个prod()函数,可以接受一个list并利用reduce()求积
from functools import reduce

def prod(L):
    def mul(x, y): return x * y
    return reduce(mul, L)
  1. 利用map和reduce编写一个str2float函数,把字符串'123.456'转换成浮点数123.456
def str2float(s):
    def cat(x ,y):
        return x + y * 10 ** -len(str(y))
    return reduce(cat, map(int, s.split('.')))

filter

Python的filter()函数用于过滤序列。
filter()也接收两个参数:

  1. 函数
  2. Iterable序列
  • filter()把传入的函数依次作用于每个元素,根据返回值为True/False决定保留/丢弃该元素
def not_empty(s):
    return s and s.strip()

list(filter(not_empty, ['A', '', 'B', None, 'C', '  ']))  # ['A', 'B', 'C'] 

利用filter()函数筛选回文数:

def is_palindrome(n):
    return str(n) == str(n)[::-1]
output = filter(is_palindrome, range(1, 1000))

sorted

Python中的sorted()函数是一个高阶函数,它的key参数可以接收函数,来实现需要的排序效果。

key指定的函数将作用于list的每一个元素上,并根据key函数返回的结果,对初始元素进行排序。

sorted()函数还有第三个参数,reverse=True,可以实现反向排序。

L = [('Bob', 75), ('Adam', 92), ('Bart', 66), ('Lisa', 88)]

# 按名字排序
def by_name(t):
    return t[0]
L2 = sorted(L, key=by_name)  # [('Adam', 92), ('Bart', 66), ('Bob', 75), ('Lisa', 88)]

# 按成绩从高到低排序
def by_score(t):
    return -t[1]
L3 = sorted(L, key=by_score)  # [('Adam', 92), ('Lisa', 88), ('Bob', 75), ('Bart', 66)]

返回函数

高阶函数还可以把函数作为结果值返回。

def lazy_sum(*args):
    def sum():
        ax = 0
        for n in args:
            ax = ax + n
        return ax
    return sum

调用lazy_sum()时,返回的并不是求和结果,而是求和函数。

f = lazy_sum(1, 3, 5, 7, 9)  # <function lazy_sum.<locals>.sum at 0x101c6ed90>
# 调用函数f时,才真正计算求和的结果
f()  # 25

内部函数sum可以引用外部函数lazy_sum的参数和局部变量,当返回函数sum时,相关参数和变量都保存在返回的函数中,称为闭包(Closure)

tips:

  • 每次调用lazy_sum(),都会返回一个新的函数:
    	f1 = lazy_sum(1, 3, 5, 7, 9)
    	f2 = lazy_sum(1, 3, 5, 7, 9)
    	f1==f2  # False
    
  • 返回函数不要引用任何循环变量,或者后续会发生变化的变量。
  • 当闭包的内层函数想要修改外层变量时,需要使用nonlocal x声明变量x为外层函数的局部变量

应用:利用闭包返回一个计数器函数,每次调用它返回递增整数:

def createCounter():
    x = 0
    def counter():
        nonlocal x
        x += 1
        return x
    return counter

匿名函数

Python使用lambda表示匿名函数:lambda x: x * x
tips:

  • 冒号前面的x表示函数参数
  • 匿名函数只能有一个表达式,该表达式的结果为返回值,不用写return
  • 匿名函数可以赋值给变量,也可以作为结果返回

装饰器

“装饰器”(Decorator),用于在代码运行期间动态增加功能。

两层嵌套decorator

装饰器本质是一个返回函数的高阶函数:接受一个函数作为参数,并返回一个函数

def log(func):
    def wrapper(*args, **kw):
        print('call %s():' % func.__name__)
        return func(*args, **kw)
    return wrapper

使用decorator:使用Python的@语法,把decorator置于函数的定义处

@log
def now():
    print('2015-3-25')

把@log放到now()函数的定义处,相当于执行了语句:now = log(now)

三层嵌套decorator

如果decorator本身需要传入参数,需要编写一个返回decorator的高阶函数。
比如,要自定义log的文本:

def log(text):
    def decorator(func):
        def wrapper(*args, **kw):
            print('%s %s():' % (text, func.__name__))
            return func(*args, **kw)
        return wrapper
    return decorator

使用嵌套decorator的方法:

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

这样的使用方法,相当于执行语句:now = log('execute')(now)

__name__属性

函数也是对象,函数包含__name__属性。

  • 将函数赋值给变量,不会改变函数的__name__属性
  • 用装饰器修饰函数,函数的__name__属性会改变为装饰器返回函数的__name__属性

函数的__name__属性被修改可能会导致依赖函数签名的代码出现错误,所以,需要把原始函数的__name__等属性复制到返回的函数中。
针对复制原始函数的__name__属性,Python提供了functools.wraps,使用方法:

import functools

def log(func):
    @functools.wraps(func)
    def wrapper(*args, **kw):
        print('call %s():' % func.__name__)
        return func(*args, **kw)
    return wrapper

同时支持有参和无参的装饰器

写出一个@log的decorator,使它既支持@log又支持@log('execute')

def log(text):
    if type(text) != str:
        @functools.wraps(text)
        def wrapper(*args, **kw):
            print('call %s:'% text.__name__)

            return text(*args, **kw)
        return wrapper

    else:
        def decorator(func):
            @functools.wraps(func)
            def wrapper(*args, **kw):
                print('%s %s():'% (text, func.__name__))

                return func(*args, **kw)
            return wrapper
        return decorator

偏函数

在函数的参数部分了解到,通过设定参数的默认值,可以降低函数调用的难度。
但是要频繁使用参数为另一个值时,需要每次穿入参数,所以Python在functools中提供了partial()函数,用于把一个函数的某些参数给固定住,返回一个新的函数,方便用于调用。
使用方法:

int2 = functools.partial(int, base=2)
int2('110')  # 6
tag(s): Python 
show comments · back · home