Python入门教程[6]-高级特性

  • A+
所属分类:python快速入门
摘要这一篇文章会讲python的一些高级特性,这些特性会对编程的简洁性有所提高。我个人是很喜欢使用列表解析的。

切片

切片用于获取一个序列(列表或元组)或者字符串的一部份,返回一个新的序列或者字符串,使用方法是中括号中指定一个列表的开始下标与结束下标,用冒号隔开,切片在之前讲解字符串的时候有介绍过,不只是字符串,列表或元组使用切片也非常常见。

  1. list = ['0','1','2','3','4','5']
  2. list[1:3]
  3. >> ['1','2']
  4. list[2:] #省略end截取到最后
  5. >> ['2','3','4','5']
  6. list[:2] #省略start默认为0
  7. >> ['0','1']

同时, 有的时候我们会遇到双冒号的用法, 具体可以看这个链接, Python中(双冒号)的用法

这个时候写完整其实就是[开始: 结束: 一步],因为开始和结束可以省略, 所以最后的结果就是双冒号。a[::3]步长为3取值, 我们看下面一个简单的例子.

  1. ['0','1','2','3','4','5','6'][::2]
  2. >> ['0', '2', '4', '6']

 

列表解析

这个方法我个人觉得是很好的,给生成list提供了一种很便利的方法。我们可以直接看下面的例子。

list = ['0','1','2','3','4','5']
['0'+i for i in list] #这个是给每个字符串前面加上0
>> ['00', '01', '02', '03', '04', '05']

列表解析可以写的很复杂,可以有双重循环同时加上判断语句。

  1. # 打印出query中的词汇出现在title中的位置
  2. test_query = [1,2,3]
  3. test_title = [4,2,3,2]
  4. [i for y in test_query for i,x in enumerate(test_title) if x==y]
  5. """
  6. [1, 3, 2]
  7. """

除了上面的循环的嵌套之外, 我们还可以使用if else. 我们看一下下面这个简单的例子.

  1. test = [1,2,3,3,4,5]
  2. [i if i in [1,2] else 0 for i in test]

最终的显示结果如下所示:

Python入门教程[6]-高级特性

我们还可以在列表解析中使用双重的循环,这里需要注意循环的顺序

  1. [[i, j] for i in range(4) for j in range(i)]

最终的效果如下所示:

Python入门教程[6]-高级特性

lambda匿名函数与Map的使用

匿名函数就是有的时候我们不想要给这个函数一个明确的函数名,就可以这么使用。我们看一下下面的例子,匿名函数和map与filter合在一起的使用方法。

  1. list = [1,2,3,4,5]
  2. f = filter(lambda x: x%2==0,list#返回符合条件的元素,类似于mathematica中的select
  3. m = map(lambda x:x*x,list#对列表list每个元素进行平方操作
  4. for i in f:
  5.     print(i)
  6. >> 2,4
  7. for i in m:
  8.     print(i)
  9. >> 1,4,9,16,25

 

Python中Reduce的使用

关于reduce的功能, 有点类似于迭代的感觉. 他的大致工作流程如下所示:

Working : 

  • At first step, first two elements of sequence are picked and the result is obtained.
  • Next step is to apply the same function to the previously attained result and the number just succeeding the second element and the result is again stored.
  • This process continues till no more elements are left in the container.
  • The final returned result is returned and printed on console.

我们举一个简单的小例子来说明他是如何工作的. 比如说我们要对[1,2,3,4]进行求和, 他的步骤就是:

  • 首先从[1,2,3,4]中拿出1和2, 进行求和1+2=3
  • 接着将上面得到的结果3与后面的3接着求和, 3+3=6
  • 最后将6与4求和, 得到4+6=10

下面我们看一个python的例子, 还是使用上面求和的例子来进行说明:

  1. import functools
  2. test = [1,2,3,4,5]
  3. functools.reduce(lambda x,y:x+y, test)
  4. >> 15

那么这个用法在实际使用的时候, 会有什么时候用到呢. 比如说在pandas中, 有一个操作是merge, 就是可以将两个dataframe按照相同的列进行合并. 但是如果我们有多个需要合并, 这个时候就可以使用reduce来进行操作了.

我们现在有如下的三种表:

  1. left = pd.DataFrame({'key': ['K0', 'K1', 'K2', 'K3'],
  2.                      'A': ['A0', 'A1', 'A2', 'A3'],
  3.                      'B': ['B0', 'B1', 'B2', 'B3']})
  4. middle = pd.DataFrame({'key': ['K0', 'K1', 'K2', 'K3'],
  5.                       'C': ['C0', 'C1', 'C2', 'C3'],
  6.                       'D': ['D0', 'D1', 'D2', 'D3']})
  7. right = pd.DataFrame({'key': ['K0', 'K1', 'K2', 'K3'],
  8.                       'E': ['E0', 'E1', 'E2', 'E3'],
  9.                       'F': ['F0', 'F1', 'F2', 'F3']})

显示结果如下所示:

Python入门教程[6]-高级特性

他们有一个共同的列是key, 所以我们可以按照key来进行合并. 但是pd.merge只能两个两个合并, 这个时候我们就可以使用merge来进行合并操作了.

  1. functools.reduce(lambda x,y: pd.merge(x,y,on='key'),[left,middle,right])

最终的结果如下所示, 完成了3个表的合并:

Python入门教程[6]-高级特性

参考资料: reduce() in Python

 

生成器

生成器是一个迭代器,不同的是,生成器只能被迭代一次,因为每次迭代的元素不是像列表元素一样,已经在内存中,而是你每迭代一次,它生成一个元素。

g = (x**x for x in range(1,3))
for x in g:
    print(x)
>> 1,4
for x in g:
    print(x)
>> #没有输出,再次运行生成器是不会有输出的

使用生成器有什么好处呢?因为生成器不是把所有元素存在内存,而是动态生成的,所以当你要迭代的对象有非常多的元素时,使用生成器能为你节约很多内存,这是一个内存友好的特性。

 

yield

yield 的使用方法和return类似。不同的是return可以返回有效的Python对象,而yield返回的是一个生成器,函数碰到return就直接返回了,使用了yield的函数,到yield返回一个元素,当再次迭代生成器时,会从yield后面继续执行,直到遇到下一个yield或者函数结束退出。

下面我们看一斐波那契数列的例子:

def fib(n):
    count = 0
    a,b = 1,1
    while count<n:
        yield a
        a,b = b,a+b
        count = count+1

f = fib(10)
for i in f:
    print(i)
>> 1,1,2,3,5,8,13,21,34,55

 

装饰器

装饰器可以为函数添加额外的功能而不影响函数的主体功能。在 Python 中,函数是第一等公民,也就是说,函数可以做为参数传递给另外一个函数,一个函数可以将另一函数作为返回值,这就是装饰器实现的基础。装饰器本质上是一个函数,它接受一个函数作为参数,返回值也是一个函数。这部分内容参考自,如何理解Python装饰器?

我们从一个简单的例子开始看起,现在我们有一个函数,里面只有打印的功能:

  1. def foo():
  2.     print('i am foo')

现在我们有一个新的需求,希望可以添加日志记录,于是我们可以在代码中添加日志代码

  1. import logging
  2. logging.basicConfig(level=logging.DEBUG)
  3. def foo():
  4.     print('i am foo')
  5.     logging.info('foo is running')

但是如果有多个函数都希望添加日志的功能,这个时候如果每个函数里面都写一行 logging 的代码,就会造成大量雷同的代码。所以为了减少代码的重复,我们可以重新定义一个函数,专门处理日志,日志处理完毕之后再执行真正的业务代码。于是我们的代码就可以写成下面的样子:

  1. def use_logging(func):
  2.     logging.info("{} is running".format(func.__name__))
  3.     return func()
  4. def foo():
  5.     print('i am foo')
  6. if __name__ == "__main__":
  7.     use_logging(foo)

我们将要执行的函数作为参数传给 use_logging 函数,先完成 use_logging 的功能,再执行本来函数的功能。

但是这样的作法会破坏原有代码的逻辑结构。之前执行业务逻辑的时,可以直接调用 foo 函数,但是现在使用的时候需要改成 use_logging(foo) 的方式。那么有什么更好的方式呢,答案就是使用装饰器。

预备知识-函数嵌套

在介绍装饰器之前,我们先看两个预备的知识。首先是函数的嵌套,及一个函数里面嵌套另外一个函数。如下面例子所示:

  1. def foo2():
  2.     def foo22():
  3.         print('foo22')
  4.     return foo22()
  5. foo2()
  6. """
  7. foo22
  8. """

在调用 foo2 的时候,因为会 return foo22,这个时候就会调用 foo22 这个函数。

 

预备知识-函数带括号和不带括号的区别

  • 不带括号时,调用的是这个函数本身 ,是整个函数体,是一个函数对象,不需等该函数执行完成;
  • 带括号(此时必须传入需要的参数),调用的是函数的return结果,需要等待函数执行完成的结果;

下面看一个例子,可以更加理解这两者的区别:

  1. def foo2():
  2.     def foo22():
  3.         print('foo22')
  4.     return foo22()
  5. foo = foo2 # 当函数不带括号的时候,直接赋值
  6. print(foo.__name__)
  7. """
  8. foo2
  9. """
  10. foo()
  11. """
  12. foo22
  13. """

 

简单的装饰器

我们可以使用下面的方式定义 use_logging。他会把 foo 函数包裹在里面。看起来就像 foo 被 use_logging 装饰了。@ 符号是装饰器的语法糖,在定义函数的时候使用,避免再一次的赋值操作,就像下面,foo=use_logging(foo),就是再一次的赋值操作

  1. def use_logging(func):
  2.     def wrapper(*args, **kwargs):
  3.         logging.info("{} is running".format(func.__name__))
  4.         return func(*args, **kwargs)
  5.     return wrapper
  6. def foo():
  7.     print('i am foo')
  8. if __name__ == "__main__":
  9.     foo = use_logging(foo)
  10.     foo()

所以,我们把上面的代码,使用 @ 来重新写一下。这个时候就可以省去 foo=use_logging(foo) 这一句了。可以直接调用 foo 函数得到我们想要的结果。上面的 use_logging 。如果有其他函数也需要类似的功能,可以直接使用装饰器来修饰函数,不需要重复进行封装。

  1. def use_logging(func):
  2.     def wrapper(*args, **kwargs):
  3.         logging.info("{} is running".format(func.__name__))
  4.         return func(*args, **kwargs)
  5.     return wrapper
  6. @use_logging
  7. def foo():
  8.     print('i am foo')
  9. if __name__ == "__main__":
  10.     foo()

在上面的例子中,*arg**kwargs是 python 中函数的可变参数。

  • *args 表示任何多个无名参数,返回一个元组;
  • **kwargs表示关键字参数,返回一个字典。

这两个组合表示了函数的所有参数。如果同时使用时 *args 参数列要在 **kwargs 前。总之就是记住,这两个组合表示了函数的所有参数。下面可以看一下*arg**kwargs的例子:

  1. def foo(*args, **kwargs):
  2.     print('args = ', args)
  3.     print('kwargs = ', kwargs)
  4.     print('---------------------------------------')
  5. if __name__ == '__main__':
  6.     foo(1, 2, 3, 4)
  7.     foo(a = 1, b = 2, c = 3)
  8.     foo(1, 2, 3, 4, a = 1, b = 2, c = 3)
  9.     foo('a', 1, None, a = 1, b = '2', c = 3)

他的输出结果如下所示:

Python入门教程[6]-高级特性

 

带参数的装饰器

装饰器还可以有更大的灵活性,例如可以使用带参数的装饰器。装饰器的语法允许我们在调用的时候,提供其他的参数,例如 @decorator(a)。下面看一个使用带参数的装饰器,控制 logging 的等级,是info 还是 warning。

  1. def use_logging(level):
  2.     def decorator(func):
  3.         def wrapper(*args, **kwargs):
  4.             if level == 'info':
  5.                 logging.info("{} is running".format(func.__name__))
  6.             elif level == 'warn':
  7.                 logging.warning("{} is running".format(func.__name__))
  8.             return func(*args, **kwargs)
  9.         return wrapper
  10.     return decorator # 返回的是一个装饰器
  11. @use_logging(level='info')
  12. def foo1():
  13.     print('i am foo1')
  14. @use_logging(level='warn')
  15. def foo2():
  16.     print('i am foo2')
  17. foo1()
  18. """
  19. INFO:root:foo1 is running
  20. i am foo1
  21. """
  22. foo2()
  23. """
  24. WARNING:root:foo2 is running
  25. i am foo2
  26. """

上面的 use_logging 是允许带参数的装饰器。他实际上是对原有装饰器的一个函数封装(可以和上面简单的装饰器做比较),并返回一个装饰器。当我们调用 use_logging(level='warning') 的时候,实际上 Python 会把这个参数传递到装饰器环境中,返回的装饰器就会根据参数不同实现不同的功能。

 

类装饰器

相比于函数装饰器,类装饰器具有更大的灵活度,高内聚等的优点。使用类装饰器,可以依靠类内的call方法(参考资料,Python 的 call 方法说明,关于这个需要记住,call方法可以把类实例当做函数调用),当使用 @ 形式将装饰器附加到函数上的时候,就会调用此方法。

  1. class Foo(object):
  2.     def __init__(self, func):
  3.         self._func = func
  4.     def __call__(self):
  5.         print('类内装饰器')
  6.         self._func()
  7. @Foo
  8. def foo():
  9.     print('I am foo')
  10. foo()

下面看一下带参数的类装饰器,可以对照着输出看一下运行的过程:

  1. # 带参数的类装饰器
  2. class Bar(object):
  3.     def __init__(self, level):
  4.         self.level = level
  5.         print('实例化 Bar 类.')
  6.     def __call__(self, func):
  7.         print('调用 __call__ 方法')
  8.         def wrapper():
  9.             print("Starting", func.__name__)
  10.             print("p1=", self.level)
  11.             func()
  12.             print("Ending", func.__name__)
  13.         return wrapper
  14. @Bar(level='level 1')
  15. def foo():
  16.     print('This is foo')
  17. foo()
  18. """
  19. 实例化 Bar 类.
  20. 调用 __call__ 方法
  21. Starting foo
  22. p1= level 1
  23. This is foo
  24. Ending foo
  25. """

 

装饰器的顺序

当有多个装饰器的时候:

  1. @a
  2. @b
  3. @c
  4. def f ():

这个时候装饰器的顺序如下所示:

  1. f = a(b(c(f)))

我们可以看一个例子:

  1. import logging
  2. logging.basicConfig(level=logging.DEBUG)
  3. def use_logging_01(func):
  4.     def wrapper(*args, **kwargs):
  5.         logging.info('This is 01')
  6.         return func(*args, **kwargs)
  7.     return wrapper
  8. def use_logging_02(func):
  9.     def wrapper(*args, **kwargs):
  10.         logging.info('This is 02')
  11.         return func(*args, **kwargs)
  12.     return wrapper
  13. def use_logging_03(func):
  14.     def wrapper(*args, **kwargs):
  15.         logging.info('This is 03')
  16.         return func(*args, **kwargs)
  17.     return wrapper
  18. @use_logging_01
  19. @use_logging_02
  20. @use_logging_03
  21. def foo():
  22.     print('foo')
  23. foo()

他的输出结果为,可以理解为先运行最外面的一层,逐渐向里面,最后运行里面的核心功能函数(这个好比穿衣服,装饰器就是穿在外面的衣服,会先运行外面的内容):

Python入门教程[6]-高级特性

 

functools.wraps

使用装饰器可以极大的减少重复的代码,但是他有一个缺点就是原函数的元信息会不见。比如函数的 namedoc 或消失,先看下面的例子:

  1. def use_logging(func):
  2.     def wrapper(*args, **kwargs):
  3.         print('use logging')
  4.         return func(*args, **kwargs)
  5.     return wrapper
  6. @use_logging
  7. def foo():
  8.     """do something
  9.     """
  10.     print('This is foo')
  11. print(foo.__name__)
  12. """
  13. wrapper
  14. """
  15. print(foo.__doc__)
  16. """
  17. None
  18. """

可以看到 foo 函数被 wrapper 替代了。为了能保持原函数的元信息,我们使用 functools.wraps。它本身也是一个装饰器,可以将元信息拷贝到装饰器函数中,这样使得装饰器函数与原函数有一样的元信息。

  1. from functools import wraps
  2. def use_logging(func):
  3.     @wraps(func)
  4.     def wrapper(*args, **kwargs):
  5.         print('use logging')
  6.         return func(*args, **kwargs)
  7.     return wrapper
  8. @use_logging
  9. def foo():
  10.     """do something
  11.     """
  12.     print('This is foo')
  13. print(foo.__name__, foo.__doc__)
  14. """
  15. foo do something
  16. """

 

装饰器使用场景-计算函数运行时间

有的时候我们希望统计每一个函数的运行时间长度,这个可以使用装饰器来完成。

  1. import time
  2. from functools import wraps
  3. def time_calc(func):
  4.     @wraps(func)
  5.     def wrapper(*args, **kwargs):
  6.         start_time = time.time()
  7.         func_output = func(*args, **kwargs) # 在这里运行函数
  8.         exec_time = time.time() - start_time # 计算执行时间
  9.         print("func run time {}".format(exec_time))
  10.         return func_output
  11.     return wrapper
  12. @time_calc
  13. def time_test():
  14.     time.sleep(2)
  15.     return 'pass'
  16. print(time_test())
  17. """
  18. func run time 2.000715494155884
  19. pass
  20. """

这样相当于我们在运行函数的时候,会在外面包一层记录时间的过程。

 

装饰器使用场景-给函数打日志

同样,我们可以自动在函数运行的时候进行日志的记录。

  1. import logging
  2. logging.basicConfig(level=logging.DEBUG)
  3. from functools import wraps
  4. def use_logging(level):
  5.     def decorator(func):
  6.         @wraps(func)
  7.         def wrapper(*args, **kwargs):
  8.             if level == 'info':
  9.                 logging.info('{} is running'.format(func.__name__))
  10.             elif level == 'warning':
  11.                 logging.warning('{} is running'.format(func.__name__))
  12.             return func(*args, **kwargs)
  13.         return wrapper
  14.     return decorator
  15. @use_logging('info')
  16. def time_test():
  17.     return 'pass'

 

 

更多入门教程链接

关于更多入门教程, 可以通过下面的链接查看.

  • 微信公众号
  • 关注微信公众号
  • weinxin
  • QQ群
  • 我们的QQ群号
  • weinxin
王 茂南

发表评论

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: