文章目录(Table of Contents)
切片
切片用于获取一个序列(列表或元组)或者字符串的一部份,返回一个新的序列或者字符串,使用方法是中括号中指定一个列表的开始下标与结束下标,用冒号隔开,切片在之前讲解字符串的时候有介绍过,不只是字符串,列表或元组使用切片也非常常见。
- list = ['0','1','2','3','4','5']
- list[1:3]
- >> ['1','2']
- list[2:] #省略end截取到最后
- >> ['2','3','4','5']
- list[:2] #省略start默认为0
- >> ['0','1']
同时, 有的时候我们会遇到双冒号的用法, 具体可以看这个链接, Python中(双冒号)的用法
这个时候写完整其实就是[开始: 结束: 一步],因为开始和结束可以省略, 所以最后的结果就是双冒号。a[::3]
步长为3取值, 我们看下面一个简单的例子.
- ['0','1','2','3','4','5','6'][::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']
列表解析可以写的很复杂,可以有双重循环同时加上判断语句。
- # 打印出query中的词汇出现在title中的位置
- test_query = [1,2,3]
- test_title = [4,2,3,2]
- [i for y in test_query for i,x in enumerate(test_title) if x==y]
- """
- [1, 3, 2]
- """
除了上面的循环的嵌套之外, 我们还可以使用if else. 我们看一下下面这个简单的例子.
- test = [1,2,3,3,4,5]
- [i if i in [1,2] else 0 for i in test]
最终的显示结果如下所示:
我们还可以在列表解析中使用双重的循环,这里需要注意循环的顺序:
- [[i, j] for i in range(4) for j in range(i)]
最终的效果如下所示:
lambda匿名函数与Map的使用
匿名函数就是有的时候我们不想要给这个函数一个明确的函数名,就可以这么使用。我们看一下下面的例子,匿名函数和map与filter合在一起的使用方法。
- list = [1,2,3,4,5]
- f = filter(lambda x: x%2==0,list) #返回符合条件的元素,类似于mathematica中的select
- m = map(lambda x:x*x,list) #对列表list每个元素进行平方操作
- for i in f:
- print(i)
- >> 2,4
- for i in m:
- print(i)
- >> 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的例子, 还是使用上面求和的例子来进行说明:
- import functools
- test = [1,2,3,4,5]
- functools.reduce(lambda x,y:x+y, test)
- >> 15
那么这个用法在实际使用的时候, 会有什么时候用到呢. 比如说在pandas中, 有一个操作是merge, 就是可以将两个dataframe按照相同的列进行合并. 但是如果我们有多个需要合并, 这个时候就可以使用reduce来进行操作了.
我们现在有如下的三种表:
- left = pd.DataFrame({'key': ['K0', 'K1', 'K2', 'K3'],
- 'A': ['A0', 'A1', 'A2', 'A3'],
- 'B': ['B0', 'B1', 'B2', 'B3']})
- middle = pd.DataFrame({'key': ['K0', 'K1', 'K2', 'K3'],
- 'C': ['C0', 'C1', 'C2', 'C3'],
- 'D': ['D0', 'D1', 'D2', 'D3']})
- right = pd.DataFrame({'key': ['K0', 'K1', 'K2', 'K3'],
- 'E': ['E0', 'E1', 'E2', 'E3'],
- 'F': ['F0', 'F1', 'F2', 'F3']})
显示结果如下所示:
他们有一个共同的列是key, 所以我们可以按照key来进行合并. 但是pd.merge只能两个两个合并, 这个时候我们就可以使用merge来进行合并操作了.
- functools.reduce(lambda x,y: pd.merge(x,y,on='key'),[left,middle,right])
最终的结果如下所示, 完成了3个表的合并:
参考资料: 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装饰器?
我们从一个简单的例子开始看起,现在我们有一个函数,里面只有打印的功能:
- def foo():
- print('i am foo')
现在我们有一个新的需求,希望可以添加日志记录,于是我们可以在代码中添加日志代码
- import logging
- logging.basicConfig(level=logging.DEBUG)
- def foo():
- print('i am foo')
- logging.info('foo is running')
但是如果有多个函数都希望添加日志的功能,这个时候如果每个函数里面都写一行 logging 的代码,就会造成大量雷同的代码。所以为了减少代码的重复,我们可以重新定义一个函数,专门处理日志,日志处理完毕之后再执行真正的业务代码。于是我们的代码就可以写成下面的样子:
- def use_logging(func):
- logging.info("{} is running".format(func.__name__))
- return func()
- def foo():
- print('i am foo')
- if __name__ == "__main__":
- use_logging(foo)
我们将要执行的函数作为参数传给 use_logging 函数,先完成 use_logging 的功能,再执行本来函数的功能。
但是这样的作法会破坏原有代码的逻辑结构。之前执行业务逻辑的时,可以直接调用 foo 函数,但是现在使用的时候需要改成 use_logging(foo) 的方式。那么有什么更好的方式呢,答案就是使用装饰器。
预备知识-函数嵌套
在介绍装饰器之前,我们先看两个预备的知识。首先是函数的嵌套,及一个函数里面嵌套另外一个函数。如下面例子所示:
- def foo2():
- def foo22():
- print('foo22')
- return foo22()
- foo2()
- """
- foo22
- """
在调用 foo2 的时候,因为会 return foo22,这个时候就会调用 foo22 这个函数。
预备知识-函数带括号和不带括号的区别
- 不带括号时,调用的是这个函数本身 ,是整个函数体,是一个函数对象,不需等该函数执行完成;
- 带括号(此时必须传入需要的参数),调用的是函数的return结果,需要等待函数执行完成的结果;
下面看一个例子,可以更加理解这两者的区别:
- def foo2():
- def foo22():
- print('foo22')
- return foo22()
- foo = foo2 # 当函数不带括号的时候,直接赋值
- print(foo.__name__)
- """
- foo2
- """
- foo()
- """
- foo22
- """
简单的装饰器
我们可以使用下面的方式定义 use_logging。他会把 foo 函数包裹在里面。看起来就像 foo 被 use_logging 装饰了。@ 符号是装饰器的语法糖,在定义函数的时候使用,避免再一次的赋值操作,就像下面,foo=use_logging(foo),就是再一次的赋值操作。
- def use_logging(func):
- def wrapper(*args, **kwargs):
- logging.info("{} is running".format(func.__name__))
- return func(*args, **kwargs)
- return wrapper
- def foo():
- print('i am foo')
- if __name__ == "__main__":
- foo = use_logging(foo)
- foo()
所以,我们把上面的代码,使用 @ 来重新写一下。这个时候就可以省去 foo=use_logging(foo) 这一句了。可以直接调用 foo 函数得到我们想要的结果。上面的 use_logging 。如果有其他函数也需要类似的功能,可以直接使用装饰器来修饰函数,不需要重复进行封装。
- def use_logging(func):
- def wrapper(*args, **kwargs):
- logging.info("{} is running".format(func.__name__))
- return func(*args, **kwargs)
- return wrapper
- @use_logging
- def foo():
- print('i am foo')
- if __name__ == "__main__":
- foo()
在上面的例子中,*arg
,**kwargs
是 python 中函数的可变参数。
*args
表示任何多个无名参数,返回一个元组;**kwargs
表示关键字参数,返回一个字典。
这两个组合表示了函数的所有参数。如果同时使用时 *args
参数列要在 **kwargs
前。总之就是记住,这两个组合表示了函数的所有参数。下面可以看一下*arg
,**kwargs
的例子:
- def foo(*args, **kwargs):
- print('args = ', args)
- print('kwargs = ', kwargs)
- print('---------------------------------------')
- if __name__ == '__main__':
- foo(1, 2, 3, 4)
- foo(a = 1, b = 2, c = 3)
- foo(1, 2, 3, 4, a = 1, b = 2, c = 3)
- foo('a', 1, None, a = 1, b = '2', c = 3)
他的输出结果如下所示:
带参数的装饰器
装饰器还可以有更大的灵活性,例如可以使用带参数的装饰器。装饰器的语法允许我们在调用的时候,提供其他的参数,例如 @decorator(a)。下面看一个使用带参数的装饰器,控制 logging 的等级,是info 还是 warning。
- def use_logging(level):
- def decorator(func):
- def wrapper(*args, **kwargs):
- if level == 'info':
- logging.info("{} is running".format(func.__name__))
- elif level == 'warn':
- logging.warning("{} is running".format(func.__name__))
- return func(*args, **kwargs)
- return wrapper
- return decorator # 返回的是一个装饰器
- @use_logging(level='info')
- def foo1():
- print('i am foo1')
- @use_logging(level='warn')
- def foo2():
- print('i am foo2')
- foo1()
- """
- INFO:root:foo1 is running
- i am foo1
- """
- foo2()
- """
- WARNING:root:foo2 is running
- i am foo2
- """
上面的 use_logging 是允许带参数的装饰器。他实际上是对原有装饰器的一个函数封装(可以和上面简单的装饰器做比较),并返回一个装饰器。当我们调用 use_logging(level='warning')
的时候,实际上 Python 会把这个参数传递到装饰器环境中,返回的装饰器就会根据参数不同实现不同的功能。
类装饰器
相比于函数装饰器,类装饰器具有更大的灵活度,高内聚等的优点。使用类装饰器,可以依靠类内的call方法(参考资料,Python 的 call 方法说明,关于这个需要记住,call方法可以把类实例当做函数调用),当使用 @ 形式将装饰器附加到函数上的时候,就会调用此方法。
- class Foo(object):
- def __init__(self, func):
- self._func = func
- def __call__(self):
- print('类内装饰器')
- self._func()
- @Foo
- def foo():
- print('I am foo')
- foo()
下面看一下带参数的类装饰器,可以对照着输出看一下运行的过程:
- # 带参数的类装饰器
- class Bar(object):
- def __init__(self, level):
- self.level = level
- print('实例化 Bar 类.')
- def __call__(self, func):
- print('调用 __call__ 方法')
- def wrapper():
- print("Starting", func.__name__)
- print("p1=", self.level)
- func()
- print("Ending", func.__name__)
- return wrapper
- @Bar(level='level 1')
- def foo():
- print('This is foo')
- foo()
- """
- 实例化 Bar 类.
- 调用 __call__ 方法
- Starting foo
- p1= level 1
- This is foo
- Ending foo
- """
装饰器的顺序
当有多个装饰器的时候:
- @a
- @b
- @c
- def f ():
这个时候装饰器的顺序如下所示:
- f = a(b(c(f)))
我们可以看一个例子:
- import logging
- logging.basicConfig(level=logging.DEBUG)
- def use_logging_01(func):
- def wrapper(*args, **kwargs):
- logging.info('This is 01')
- return func(*args, **kwargs)
- return wrapper
- def use_logging_02(func):
- def wrapper(*args, **kwargs):
- logging.info('This is 02')
- return func(*args, **kwargs)
- return wrapper
- def use_logging_03(func):
- def wrapper(*args, **kwargs):
- logging.info('This is 03')
- return func(*args, **kwargs)
- return wrapper
- @use_logging_01
- @use_logging_02
- @use_logging_03
- def foo():
- print('foo')
- foo()
他的输出结果为,可以理解为先运行最外面的一层,逐渐向里面,最后运行里面的核心功能函数(这个好比穿衣服,装饰器就是穿在外面的衣服,会先运行外面的内容):
functools.wraps
使用装饰器可以极大的减少重复的代码,但是他有一个缺点就是原函数的元信息会不见。比如函数的 name,doc 或消失,先看下面的例子:
- def use_logging(func):
- def wrapper(*args, **kwargs):
- print('use logging')
- return func(*args, **kwargs)
- return wrapper
- @use_logging
- def foo():
- """do something
- """
- print('This is foo')
- print(foo.__name__)
- """
- wrapper
- """
- print(foo.__doc__)
- """
- None
- """
可以看到 foo 函数被 wrapper 替代了。为了能保持原函数的元信息,我们使用 functools.wraps
。它本身也是一个装饰器,可以将元信息拷贝到装饰器函数中,这样使得装饰器函数与原函数有一样的元信息。
- from functools import wraps
- def use_logging(func):
- @wraps(func)
- def wrapper(*args, **kwargs):
- print('use logging')
- return func(*args, **kwargs)
- return wrapper
- @use_logging
- def foo():
- """do something
- """
- print('This is foo')
- print(foo.__name__, foo.__doc__)
- """
- foo do something
- """
装饰器使用场景-计算函数运行时间
有的时候我们希望统计每一个函数的运行时间长度,这个可以使用装饰器来完成。
- import time
- from functools import wraps
- def time_calc(func):
- @wraps(func)
- def wrapper(*args, **kwargs):
- start_time = time.time()
- func_output = func(*args, **kwargs) # 在这里运行函数
- exec_time = time.time() - start_time # 计算执行时间
- print("func run time {}".format(exec_time))
- return func_output
- return wrapper
- @time_calc
- def time_test():
- time.sleep(2)
- return 'pass'
- print(time_test())
- """
- func run time 2.000715494155884
- pass
- """
这样相当于我们在运行函数的时候,会在外面包一层记录时间的过程。
装饰器使用场景-给函数打日志
同样,我们可以自动在函数运行的时候进行日志的记录。
- import logging
- logging.basicConfig(level=logging.DEBUG)
- from functools import wraps
- def use_logging(level):
- def decorator(func):
- @wraps(func)
- def wrapper(*args, **kwargs):
- if level == 'info':
- logging.info('{} is running'.format(func.__name__))
- elif level == 'warning':
- logging.warning('{} is running'.format(func.__name__))
- return func(*args, **kwargs)
- return wrapper
- return decorator
- @use_logging('info')
- def time_test():
- return 'pass'
更多入门教程链接
关于更多入门教程, 可以通过下面的链接查看.
- 微信公众号
- 关注微信公众号
- QQ群
- 我们的QQ群号
评论