了解几个Python高级特性

  1. 前言算法

  Python 很是灵活强大,跟它具备一些特性有关,如匿名函数、列表推导式、迭代器、装饰器等。本文主要简单介绍:app

  切片ide

  迭代、可迭代对象、迭代器函数

  推导式(列表推导式、集合推导式、字典推导式)工具

  生成器和生成器表达式测试

  匿名函数spa

  装饰器设计

  2. 切片日志

  切片(slice)在 Python 中很是强大,能够轻松对字符串、列表和元组进行切割,完成拷贝。注意切片是浅拷贝,关于浅拷贝和深拷贝留做之后讨论。对象

  切片的语法是:obj[start: end: step]

  obj 是支持切片的对象,如:列表、字符串、元组等。

  start 是开始切的索引位置,索引是从 0 开始标记的。start 能够省略,默认值是 0.

  end 是切片结束的位置,实际上切不到 obj[end]。end 也能够省略,默认值是对象的长度。

  step 是切片的步长,也能够省略,默认值是 1。

  对字符串、列表、元组进行切片。

  >>> word = "Python"

  >>> word[:]

  'Python'

  >>> word[1:3]

  'yt'

  >>> word[::2]

  'Pto'

  >>> ls = [1, 2, 3, 4, 5, 6]

  >>> ls[::2]

  [1, 3, 5]

  >>> t = (1, 2, 3, 4, 5, 6)

  >>> t[2:6]

  (3, 4, 5, 6)

  切片也支持负数,使用 obj[::-1] 能够轻松实现翻转,如把列表翻转:

  >>> ls = [1, 2, 3, 4, 5, 6, 7]

  >>> ls[::-1]

  [7, 6, 5, 4, 3, 2, 1]

  obj[end] 是取不到的。

  >>> ls = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

  >>> ls[1:9]

  [2, 3, 4, 5, 6, 7, 8, 9]

  能够看到 ls[1:9], ls[1],即 2 能够取到,ls[9],即 10 是取不到的。

  3. 迭代、可迭代对象、迭代器

  迭代(iteration):迭代是一种操做,能够理解为遍历,如用 for 循环遍历列表或者元组。

  可迭代对象(iterable object):能够用 for 循环迭代的对象。如列表、元组、字符串、字典、集合等。

  到目前为止,能够看到大多数容器对象均可以使用 for 语句:

  for element in [1, 2, 3]:

  print(element)

  for element in (1, 2, 3):

  print(element)

  for key in {'one':1, 'two':2}:

  print(key)

  for char in "123":

  print(char)

  for line in open("myfile.txt"):

  print(line, end='')

  咱们还能够用 collections 模块中的 Iterable 类型判断一个对象是不是可迭代对象。

  >>> from collections import Iterable

  >>> isinstance([1, 2], Iterable)

  True

  >>> isinstance((1, 2), Iterable)

  True

  >>> isinstance('abc', Iterable)

  True

  迭代器(iterator):是遵循迭代器协议的可迭代对象就称为迭代器。迭代器协议机制是:for 语句会在容器对象上调用 iter()。 该函数返回一个定义了 __next__() 方法的迭代器对象,此方法将逐一访问容器中的元素。 当元素用尽时,__next__() 将引起 StopIteration 异常来通知终止 for 循环。你可使用 next() 内置函数来调用 __next__() 方法。

  通俗理解就是:能被 next()函数调用并不断返回下一个值的对象成为迭代器。

  迭代器的使用很是广泛并使得 Python 成为一个统一的总体。下面这个例子显示了迭代器的运做方式:

  >>> s = 'abc'

  >>> it = iter(s)

  >>> it

  >>> next(it)

  'a'

  >>> next(it)

  'b'

  >>> next(it)

  'c'

  >>> next(it)

  Traceback (most recent call last):

  File "", line 1, in

  next(it)

  StopIteration

  咱们可使用 isinstance()方法判断一个对象是不是 Iterator 对象。

  >>> from collections import Iterator

  >>> ls = [1, 2, 3, 4]

  >>> isinstance(ls, Iterator)

  False

  >>> isinstance(iter(ls), Iterator)

  True

  >>> t = (1, 2, 3, 4, 5)

  >>> isinstance(t, Iterator)

  False

  >>> isinstance(iter(t), Iterator)

  True

  >>> s = 'abcde'

  >>> isinstance(s, Iterator)

  False

  >>> isinstance(iter(s), Iterator)

  True

  经过上面的例子咱们能够看到,列表、元组、字符串等是可迭代对象,可是不是迭代器。可使用 iter()函数,轻松把列表、元组、字符串等转为迭代器。

  为何列表、元组、字符串等不是迭代器呢?

  由于 Python 的 Iterator 对象表示的是一个数据流,迭代器能够被 next()函数不断调用并返回下一个数据,直到没有数据时抛出 StopIteration 错误。咱们能够把这个数据流当作一个有序序列,可是咱们却不能提早知道序列的长度,只能不断经过 next()函数实现按需计算下一个数据,所以 Iterator 的计算是惰性的,只有在须要返回下一个数据时它才会计算。

  因此 Iterator 能够表示一个无限大的数据流,如所有整数,可是列表等容器因为内存空间限制,用于不可能存储全体整数。

  小结:

  迭代(iteration):迭代是一种操做,用 for 循环遍历。

  可迭代对象(iterable object):能够用 for 循环迭代的对象。

  迭代器(iterator):能够做用于 next()函数的可迭代对象,它们是一个惰性计算序列。

  能够用 iter()函数把 list、str、tuple、dict 转为迭代器。

  4. 推导式

  推导式(comprehension)是 Python 很是重要的一个特性,提供了更加简单的建立列表、集合、字典的方式。其中列表推导式(list comprehensions)是用的最多的。

  4.1 列表推导式

  列表推导式是 Python 很是重要的一个特性之一。列表推导式提供了一个更简单的建立列表的方法。

  常见的用法是把某种操做应用于序列或可迭代对象的每一个元素上,而后使用其结果来建立列表,或者经过知足某些特定条件元素来建立子序列。

  好比:假设咱们想建立一个平方列表。

  一般咱们是这么作:

  >>> squares = []

  >>> for x in range(10):

  ... squares.append(x**2)

  ...

  >>> squares

  [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

  这样作尽管能够达到目的,可是这里建立(或被重写)的名为 x 的变量在 for 循环后仍然存在,可能存在反作用。咱们能够经过如下方法计算平方列表的值而不会产生任何反作用。

  方法一:squares = list(map(lambda x: x**2, range(10)))

  方法一等价于:

  方法二:squares = [x**2 for x in range(10)]

  这里的方法二就是列表推导式,咱们能够看到列表推导式更加简洁易读。

  列表推导式的结构是:

  [ 表达式 for子句(必须有一个) 0 或多个 for 或者 if 子句]

  说明:由一对方括号([])所包含如下内容:一个表达式,后面跟一个 for 子句,而后是零个或多个 for 或 if 子句。根据后面的 for 等子句计算表达式的值,而后把全部计算的值存为一个新列表。如:如下列表推导式会将两个列表中不相等的元素组合起来变为一个新的列表:

  [(x, y) for x in [1,2,3] for y in [3,1,4] if x != y]

  它等价于

  >>> combs = []

  >>> for x in [1,2,3]:

  ... for y in [3,1,4]:

  ... if x != y:

  ... combs.append((x, y))

  ...

  >>> combs

  [(1, 3), (1, 4), (2, 3), (2, 1), (2, 4), (3, 1), (3, 4)]

  可见,使用列表推导式很是简洁。注意在上面两个代码片断中, for 和 if 的顺序是相同的。若是表达式是一个元组(例如上面的 (x, y)),那么就必须加上括号。

  列表推导式可使用复杂的表达式和嵌套函数。如:

  >>> from math import pi

  >>> [str(round(pi, i)) for i in range(1, 6)]

  ['3.1', '3.14', '3.142', '3.1416', '3.14159']

  嵌套的列表推导式:列表推导式中的初始表达式能够是任何表达式,包括另外一个列表推导式。

  考虑下面这个 3x4 的矩阵,它由 3 个长度为 4 的列表组成

  >>> matrix = [

  ... [1, 2, 3, 4],

  ... [5, 6, 7, 8],

  ... [9, 10, 11, 12],

  ... ]

  下面的列表推导式将交换其行和列

  >>> [[row[i] for row in matrix] for i in range(4)]

  [[1, 5, 9], [2, 6, 10], [3, 7, 11], [4, 8, 12]]

  如上节所示,嵌套的列表推导式是基于跟随其后的 for 进行求值的,因此这个例子等价于:

  >>> transposed = []

  >>> for i in range(4):

  ... transposed.append([row[i] for row in matrix])

  ...

  >>> transposed

  [[1, 5, 9], [2, 6, 10], [3, 7, 11], [4, 8, 12]]

  反过来讲,也等价于

  >>> transposed = []

  >>> for i in range(4):

  ... # the following 3 lines implement the nested listcomp

  ... transposed_row = []

  ... for row in matrix:

  ... transposed_row.append(row[i])

  ... transposed.append(transposed_row)

  ...

  >>> transposed

  [[1, 5, 9], [2, 6, 10], [3, 7, 11], [4, 8, 12]]

  实际应用中,使用内置函数去组成复杂的流程语句是更好的选择。 zip() 函数将会很好地处理这种状况

  >>> list(zip(*matrix)) # *matrix 是列表解包,会去除最外层的[]

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

  列表推导式小结:

  列表推导式格式: [表达式(可嵌套列表推导式) for子句 0或多个 for 或 if 子句]

  列表推导式建立新列表更加简洁易读,没有反作用。

  注意,虽然元组和列表相似,可是列表是可变的,元组是不可变的,因此元组没有推导式。

  元组是不可变的,其序列一般包含不一样种类的元素,而且经过解包或者索引来访问。一个元素的元组的建立必须在一个元素后面添加一个逗号,如 t = (1, )

  列表是可变的,通常列表的元素都是同种类型的,而且经过迭代访问。

  4.2 集合推导式

  集合是无序、肯定、互异的。它一般用于成员测试和去重操做。此外还能够进行并集、交集、差集、补集等操做。

  集合也和列表同样支持推导式,集合推导式(set comprehensions)以下:

  >>> {x for x in 'abracadabra' if x not in 'abc'}

  {'r', 'd'}

  即:把[] 变为 {} 便可,其余和列表规则同样。

  4.3 字典推导式

  字典也支持字典推导式(dict comprehensions),如:

  >>> {x: x**2 for x in (2, 4, 6)}

  {2: 4, 4: 16, 6: 36}

  >>> {x: y for x, y in zip("abcd", [1, 2, 3, 4])}

  {'a': 1, 'b': 2, 'c': 3, 'd': 4}

  即:把[] 变为 {},同时表达式符合字典的键值对形式 key: value。其余规则同列表推导式。

  5. 生成器和生成器表达式

  5.1 生成器

  生成器(Generator):是一个用于建立迭代器的简单而强大的工具。 它们的写法相似标准的函数,但当它们要返回数据时会使用 yield 语句。 每次对生成器调用 next() 时,它会从上次离开位置恢复执行(它会记住上次执行语句时的全部数据值)。就是把函数中 return 关键字换成了 yield 关键字,这样定义出来的就不是函数了,而是一个生成器,一般咱们用 for 循环去迭代生成器,而不是用 next()函数一个一个调用,示例以下:

  def reverse(data):

  for index in range(len(data)-1, -1, -1):

  yield data[index]

  >>> for char in reverse('golf'):

  ... print(char)

  ...

  f

  l

  o

  g

  能够用生成器来完成的操做一样能够用基于类的迭代器来完成。 但生成器的写法更为紧凑,由于它会自动建立 __iter__() 和 __next__() 方法。

  另外一个关键特性在于局部变量和执行状态会在每次调用之间自动保存。

  除了会自动建立方法和保存程序状态,当生成器终结时,它们还会自动引起 StopIteration。 这些特性结合在一块儿,使得建立迭代器能与编写常规函数同样容易。

  生成器比较难理解的一点在于生成器的执行流程和函数流程不同,函数是顺序执行,遇到 return 语句,或者最后一行函数语句就返回。而变成生成器的函数时,在每次调用 next()函数的时候执行,遇到 yield 语句返回,再次执行时从上次返回的 yield 语句处继续执行。

  举个栗子:

  >>> def language():

  ... print("Step 1")

  ... yield "Python"

  ... print("Step 2")

  ... yield "Java"

  ... print("Step 3")

  ... yield "C"

  ...

  >>> lang = language()

  >>> next(lang)

  Step 1

  'Python'

  >>> next(lang)

  Step 2

  'Java'

  >>> next(lang)

  Step 3

  'C'

  >>> next(lang)

  Traceback (most recent call last):

  File "", line 1, in

  StopIteration

  调用 language 生成器时,首先要生成一个 generator 对象,而后用 next()函数不断得到下一个返回值。在执行的过程当中,遇到 yield 就中断,下次又继续执行,执行 3 次 yield 后,没有 yield 能够执行了,因此第 4 次调用 next(lang)就报错了。

  5.2 生成器表达式

  生成器除了用相似于函数的定义方法外,还能够用相似于列表推导式的方式生成,所用语法相似列表推导式,就是把外层的方括号换成圆括号便可。这种表达式被设计用于生成器将当即被外层函数所使用的状况。

  生成器表达式相比完整的生成器更紧凑但较不灵活,相比等效的列表推导式则更为节省内存。由于生成器表达式是生成迭代器,迭代器是惰性计算,它存储的是算法,须要多少就计算多少,而列表推导式是直接所有计算出来,放到内存。

  例如:

  >>> sum(i*i for i in range(10)) # sum of squares

  285

  >>> xvec = [10, 20, 30]

  >>> yvec = [7, 5, 3]

  >>> sum(x*y for x,y in zip(xvec, yvec)) # dot product

  260

  >>> unique_words = set(word for line in page for word in line.split())

  >>> valedictorian = max((student.gpa, student.name) for student in graduates)

  >>> data = 'golf'

  >>> list(data[i] for i in range(len(data)-1, -1, -1))

  ['f', 'l', 'o', 'g']

  6. 匿名函数

  当咱们传入函数时,有些时候不须要显示的定义函数,直接传入匿名函数( anonymous functions)更方便。

  匿名函数是经过 lambda 关键字来建立的。基本语法是:lambda 参数: 表达式

  其中参数能够是多个,用逗号分隔。如:lambda a, b: a+b,这个匿名函数返回两个参数的和。再举个栗子看看匿名函数的经常使用用法:

  >>> pairs = [(1, 'one'), (2, 'two'), (3, 'three'), (4, 'four')]

  >>> pairs.sort(key=lambda pair: pair[1])

  >>> pairs

  [(4, 'four'), (1, 'one'), (3, 'three'), (2, 'two')]

  Lambda 函数能够在须要函数对象的任何地方使用。它们在语法上限于单个表达式。从语义上来讲,它们只是正常函数定义的语法糖,不用写 return,返回值就是表达式的结果。

  匿名函数的优点在于没有名字,不用担忧命名冲突。此外匿名函数也是一个函数对象,也能够把匿名函数赋值给一个变量,再利用变量来调用该函数。如:

  >>> s = lambda x, y: x + y # 计算两数之和

  >>> s

  at 0x000002B457B3AF78>

  >>> s(2, 3)

  5

  >>> s(3, 7)

  此外,与嵌套函数定义同样,lambda 函数能够引用所包含域的变量,把匿名函数做为返回值返回,举个官网栗子

  >>> def make_incrementor(n):

  ... return lambda x: x + n

  ...

  >>> f = make_incrementor(42)

  >>> f(0)

  42

  >>> f(1)

  43

  7. 装饰器

  装饰器(decorator)是一个很是有用的设计。它能够在代码运行期间动态增长功能。本质上,装饰器是一个返回函数的高阶函数。

  好比:咱们如今要定义一个能打印日志的装饰器,能够定义以下:

  def log(func):郑州作人流多少钱 http://wap.zyfuke.com/

  def wrapper(*args, **kwargs):

  print(f"call {func.__name__}()")

  return func(*args, **kwargs)

  return wrapper

  这个 log 其实就是一个装饰器,它接收一个函数做为参数,并返回一个函数。咱们借助 Python 的 @ 语法,把装饰器置于函数的定义处:

  def log(func):

  def wrapper(*args, **kwargs):

  print(f"call {func.__name__}()") # func.__name__获取该函数的名字

  return func(*args, **kwargs)

  return wrapper

  @log

  def now():

  print("2020-05-05")

  if __name__ == '__main__':

  now()

  结果输出:

  call now()

  2020-05-05

  把 @log 放到 now() 函数的定义处,至关于执行了语句:`now = log(now)。

  因为 log()是一个装饰器,返回一个函数,因此,原来的 now()函数仍然存在,只是如今同名的 now 变量指向了新的函数,因而调用 now()将执行新函数,即在 log()函数中返回的 wrapper()函数。

  wrapper()函数的参数定义是(*args, **kwargs),所以 wrapper()能够接收任意参数的调用,在 wrapper()函数内,首先打印日志,再紧接着调用原始函数。

  若是 decorator 自己须要传入参数,那就须要编写一个返回 decorator 的高阶函数,写出来会更复杂,好比,自定义 log 的文本:

  def log(text):

  def decorator(func):

  def wrapper(*args, **kwargs):

  print(f"{text} call {func.__name__}")

  return func(*args, **kwargs)

  return wrapper

  return decorator

  @log("开始")

  def now():

  print("2020-05-05")

  if __name__ == '__main__':

  now()

  结果输出:

  开始 call now

  2020-05-05

  与 2 层嵌套相比,3 层嵌套效果如:now = log("开始")(now)

  说明:首先执行 log(“咱们”),而后返回 decorator 函数,在调用返回的函数,参数是 now 函数,返回值最终是 wrapper 函数。

  这两种 decorator 定义方式都没有问题,不过还差最后一步,由于函数也是对象,它有__name__等属性,通过 decorator 装饰以后的函数,它们的__name__已经从原来的 now 变成了,wrapper:

  def log(text):

  def decorator(func):

  def wrapper(*args, **kwargs):

  print(f"{text} call {func.__name__}")

  return func(*args, **kwargs)

  return wrapper

  return decorator

  @log("开始")

  def now():

  print("2020-05-05")

  if __name__ == '__main__':

  now()

  print(now.__name__)

  结果输出:

  开始 call now

  2020-05-05

  wrapper

  这是由于返回的 wrapper()函数名字就是 wrapper,因此须要把原始函数的__name__等属性复制到 wrapper()函数中,不然,有些依赖函数签名的代码执行会出错。Python 内置的 functools.wraps 就是干这个事的,因此一个完整的 decorator 的写法以下:

  import functools

  def log(func):

  @functools.wraps(func)

  def wrapper(*args, **kwargs):

  print(f"call {func.__name__}()") # func.__name__获取该函数的名字

  return func(*args, **kwargs)

  return wrapper

  @log

  def now():

  print("2020-05-05")

  if __name__ == '__main__':

  now()

  print(now.__name__)

  结果输出:

  call now()

  2020-05-05

  now

  带参数的装饰器:

  import functools

  def log(text):

  def decorator(func):

  @functools.wraps(func)

  def wrapper(*args, **kwargs):

  print(f"{text} call {func.__name__}")

  return func(*args, **kwargs)

  return wrapper

  return decorator

  @log("如今")

  def now():

  print("2020-05-05")

  if __name__ == '__main__':

  now()

  print(now.__name__)

  结果输出:

  如今 call now

  2020-05-05

  now

相关文章
相关标签/搜索