Python语法糖自己总结!(try,with,装饰器)


title: Python深入探究(三)一些小语法糖
sticky: 3
top_img:
keywords: “Python,高级语言”
cover: https://qinzheng7575-1.oss-cn-beijing.aliyuncs.com/pystudy/3.png
description: 参考《Python cookbook》
abbrlink: 46be9372
copyright_author: 秦政
copyright_author_href: https://qinzheng7575.github.io/
copyright_url: https://qinzheng7575.github.io/
copyright_info: 此文章版权归秦政所有,如有转载,请注明來自原作者


原文点击更好看!!!!


title: Python深入探究(三)一些小语法糖
sticky: 3
top_img:
keywords: “Python,高级语言”
cover: https://qinzheng7575-1.oss-cn-beijing.aliyuncs.com/pystudy/3.png
description: 参考《Python cookbook》
abbrlink: 46be9372
copyright_author: 秦政
copyright_author_href: https://qinzheng7575.github.io/
copyright_url: https://qinzheng7575.github.io/
copyright_info: 此文章版权归秦政所有,如有转载,请注明來自原作者


try用法

最简单的:

try:
    正常的操作
    ......................
except:
    发生异常,执行这块代码
    ......................
else:
    如果没有异常执行这块代码

except后面还可以跟错误信息,用来匹配到具体的错误:

try:
    fh = open("testfile", "w")
    fh.write("这是一个测试文件,用于测试异常!!")
except IOError:
    print "Error: 没有找到文件或读取文件失败"
else:
    print "内容写入文件成功"
    fh.close()

rise则是主动引发一个错误信息:

inputValue=input("please input a int data :")
if type(inputValue)!=type(1):
    raise ValueError
else:
    print(inputValue)

如果输入的数据不是整数,则报ValueError

with用法

with其实就是try…except…的一种简单用法

with open('data', 'r', encoding='utf-8') as f:
    data = f.readlines()

相当于:

f = open('data', 'r', encoding='utf-8')
try:
    data = f.readlines()
except:
    pass
finally:
    f.close()

with的写法中,会自动的关闭文件。

首先介绍下with 工作原理
(1)紧跟with后面的语句被求值后,返回对象的“__enter__()”方法被调用,这个方法的返回值将被赋值给as后面的变量;
(2)当with后面的代码块全部被执行完之后,将调用前面返回对象的“__exit__()”方法。

with工作原理代码示例:

class Sample:
    def __enter__(self):
        print("__enter__函数执行啦")
        return "这是with里面表达式返回的东西"
    def __exit__(self, exc_type, exc_val, exc_tb):
        print("__exit__函数执行啦")
def get_sample():
    return Sample()
with get_sample() as sample:
    print("Sample: ", sample)

输出:

__enter__函数执行啦
Sample:  这是with里面表达式返回的东西
__exit__函数执行啦

【注:】exit()方法中有3个参数, exc_type, exc_val, exc_tb,这些参数在异常处理中相当有用。
exc_type: 错误的类型
exc_val: 错误类型对应的值
exc_tb: 代码中错误发生的位置

为什么用with打开文件更好呢

在用try/except打开文件的时候,这样的写法是比较规范的:

try:
    r=open('练习/123.csv','r')
    try:
        print(r.read())
    except:
        print('read error')
    finally:
        r.close()
except:
    print('open error')

可以看到,如果文件不存在,open会抛出一个IOerror,我们可以捕捉并print open error,防止文件读写时因为产生IOError,而导致close()未调用(比如写出错)。我们可以用try… finally。

而with open简单写法,等同于try…finally(同时避免了两种IOerror的产生导致退出)

try:
    with open("1.log", "r") as f:
        print(f.read())
except:
    print("打开文件异常")

写文件

当我们写文件时,系统往往不会立即写入,而是先放到内存里缓存起来,空闲时慢慢写入。只有调用close()方法时,操作系统才能立即写入。为了防止忘记调用close(),所以用with语句比较保险。

with open("1.log", "w") as f:
    f.write("hello world")

装饰器decorator

一个装饰器就是一个函数,它接受一个函数作为参数并返回一个新的函数。

好好看完这篇!!!

它放在一个函数开始定义的地方,它就像一顶帽子一样戴在这个函数的头上。和这个函数绑定在一起。在我们调用这个函数的时候,第一件事并不是执行这个函数,而是将这个函数做为参数传入它头顶上这顶帽子,这顶帽子我们称之为装饰函数装饰器

1.打印日志

# 这是装饰函数
def logger(func):
    def wrapper(*args, **kw):
        print('我准备开始计算:{} 函数了:'.format(func.__name__))
        # 真正执行的是这行。
        func(*args, **kw)
        print('啊哈,我计算完啦。给自己加个鸡腿!!')
    return wrapper

@logger
def add(x,y):
    print('{}+{}={}'.format(x,y,x+y))

add(1,2)

输出:

我准备开始计算:add 函数了:
1+2=3
啊哈,我计算完啦。给自己加个鸡腿!!

2.计时器

同样,我们按照上述代码的思路,稍微改一下:

import time
def timer(func):
    def wrapper(*args, **kw):
        start=time.time()
        # 真正执行的是这行。
        func(*args, **kw)
        last=time.time()-start
        print('花的时间是{}'.format(last))
    return wrapper

@timer
def count(n):
    for i in range(n):
        n-=1

count(10000000)
输出:花的时间是1.0152852535247803

就这么简单!那还有别的用法吗

带参数的decorator

装饰器本身就是一个把函数作为输入的函数,那么装饰器这个函数不能传参进去,岂不是很不爽?

但是,判断并根据输入的东西来执行对应的操作,不就需要另一个函数来实现了吗,所以要用到两层嵌套了。

def say_gender(name):
    def wrapper(func):
        def deco(*args,**kwargs):
            if name=="qz":
                print("我是男生")
            elif name=="xjj":
                print("我是女生")
            else:
                return

            # 真正执行函数的地方
            func(*args, **kwargs)
        return deco
    return wrapper

@say_gender("qz")#给装饰器传入姓名,自动输出性别男
def boy():
    print('真正的函数执行,我是qz')

@say_gender("xjj")#给装饰器传入姓名,自动输出性别女
def girl():
    print('真正的函数执行,我是xjj')

girl()
print('----------')
boy()

输出:
我是女生
真正的函数执行,我是xjj
----------
我是男生
真正的函数执行,我是qz

这里函数嵌套有些难理解的地方,来说一下。上述代码可以这样简化:

def say_gender(name):
    def wrapper(func):
        def deco(*args,**kwargs):
		    #do something...
            func(*args,**kwargs)
        return deco
    return wrapper

而调用girl()其实相当于调用say_gender('xjj')(girl)

这种语法有点奇怪,下面来解释这种语法:
一般而言,调用一个函数是加一个括号。如果看见括号后还有一个括号,说明第一个函数返回了一个函数,如果后面还有括号,说明前面那个也返回了一个函数。以此类推。例子如下:

def fun(args_outer):
    print("now in the outer"+" "+args_outer);
    def inner_fun(args_inner):
        print("now in the inner"+" "+args_inner);
    return inner_fun;
输出:
now in the outer hello
now in the inner world

这叫做python method chaining

我们再看函数执行过程:

say_gender()了一个参数‘xjj’之后这个函数干了什么呢?它什么都没干,直接返回了一个函数wrapper!!!

因为def wrapper()虽然写在say_gender()里面了,但是它只是一个声明啊!!,所以它接受完参数后什么都没干,直接返回了一个函数wrapper但是,返回的这个函数wrapper,由于前面说的语法的关系,收到了第二个参数girlwrapper函数除了接受参数girl依然啥都没干,返回了函数deco(装饰器函数)

而这个deco就不一样了(它为内置函数传参,不需要在装饰器函数上添加*args, **kwargs),直接根据*args, **kwargs接受外面的参数,然后deco函数开始执行,可以使用外部的变量name(这里是内置函数应用外部函数的局部变量),继续按照顺序执行func(*args, **kwargs)(这是为被装饰的函数传参),最后return deco(返回内置函数,不需要加括号)

不如看这个例子:

def test2(func):
    count = 1
    def test2_1(*args, **kwargs):		# 为内置函数传参,不需要在装饰器函数上添加*args, **kwargs
        nonlocal count                  # 这里是内置函数应用外部函数的局部变量
        func(*args, **kwargs)			# 为被装饰的函数传参
        print('函数调用{}'.format(func.__name__))  # 打印func函数的名字,即被装饰函数的名字
        count += 1
    return test2_1                 # 返回内置函数,不需要加()

这时候在回头看看这句话!

**一个装饰器就是一个函数,它接受一个函数作为参数并返回一个新的函数。 **

是不是豁然开朗?!

参考1

参考2

参考3

高阶用法(一):不带参数的类装饰器

上述是通过函数实现的装饰器,因此会有很多层函数的嵌套,接下来我们基于类来实现装饰器。

class logger(object):
    def __init__(self,func):
        self.func=func
        self.ncalls=0
    def __call__(self,*args,**kwargs):
        print("[INFO],函数{func}() 正在运行".format(func=self.func.__name__))
        self.ncalls+=1
        return self.func(*args,**kwargs)

@logger
def test():
    print('测试')
if __name__ == "__main__":
    test()
    test()
    print(test.ncalls)#打印出这个函数被执行的次数

输出:
[INFO],函数test() 正在运行
测试
[INFO],函数test() 正在运行
测试
2

这里面__init__原本是这个类的初始化函数,这里用于接受被装饰的函数。__call__实现装饰逻辑,其中return部分就实现了原本函数的执行。

官方文档

高阶用法(二):带参数的类装饰器

换一个口味,wraps方法

在《python cookbook》中,并没有让我们从0开始编写一个装饰器,而是直接使用了wraps方法,我们先跟着他学一下,后面会讨论下warps方法的本质原理。

import time
from functools import wraps

def timethis(func):
    @wraps(func)#这个注解很重要!!!!
    def wrapper(*args,**kwargs):
        start=time.time()
        results=func(*args,**kwargs)
        end=time.time()
        print(func.__name__,end-start)
        return results
    return wrapper

@timethis
def count(n):
    while n>0:
        n-=1

count(100000)
输出:
count 0.015119791030883789

还记得上面说的,装饰器是接受一个函数,然后返回另一个函数

def countdown(n):
    pass

跟像下面这样写其实效果是一样的:

def countdown(n):
    pass
countdown = timethis(countdown)

我们把countdown这个函数传了进去,在里面wrapper函数通过*args,**kwargs接受了参数,然后这个新的函数包装器被作为结果返回,替代了原始函数(传入的func)。

函数签名信息

你写了一个装饰器作用在某个函数上,但是这个函数的重要的元信息比如名字、文档字符串、注解和参数签名都丢失了。

任何时候你定义装饰器的时候,都应该使用 functools 库中的 @wraps 装饰器来注解底层包装函数。例如:

import time
from functools import wraps
def timethis(func):
    '''
    Decorator that reports the execution time.
    '''
    @wraps(func)
    def wrapper(*args, **kwargs):
        start = time.time()
        result = func(*args, **kwargs)
        end = time.time()
        print(func.__name__, end-start)
        return result
    return wrapper

下面我们使用这个被包装后的函数并检查它的元信息:

>>> @timethis
... def countdown(n):
...     '''
...     Counts down
...     '''
...     while n > 0:
...         n -= 1
...
>>> countdown(100000)
countdown 0.008917808532714844
>>> countdown.__name__
'countdown'
>>> countdown.__doc__
'\n\tCounts down\n\t'
>>> countdown.__annotations__
{'n': <class 'int'>}
>>>

在编写装饰器的时候复制元信息是一个非常重要的部分。如果你忘记了使用 @wraps , 那么你会发现被装饰函数丢失了所有有用的信息。比如如果忽略 @wraps 后的效果是下面这样的:

>>> countdown.__name__
'wrapper'
>>> countdown.__doc__
>>> countdown.__annotations__
{}
>>>

@wraps 有一个重要特征是它能让你通过属性 __wrapped__ 直接访问被包装函数。例如:

>>> countdown.__wrapped__(100000)
>>>

__wrapped__ 属性还能让被装饰函数正确暴露底层的参数签名信息。例如:

>>> from inspect import signature
>>> print(signature(countdown))
(n:int)
>>>

一个很普遍的问题是怎样让装饰器去直接复制原始函数的参数签名信息, 如果想自己手动实现的话需要做大量的工作,最好就简单的使用 @wraps 装饰器。 通过底层的 __wrapped__ 属性访问到函数签名信息。

带有参数的装饰器

这一节和前面原理差不多,但是使用了wrap的注解,让函数的元信息能够得到保留。

from functools import wraps
import logging

def logged(level, name=None, message=None):
 """
 Add logging to a function. level is the logging
 level, name is the logger name, and message is the
 log message. If name and message aren't specified,
 they default to the function's module and name.
 """
 def decorate(func):
     logname = name if name else func.__module__
     log = logging.getLogger(logname)
     logmsg = message if message else func.__name__

     @wraps(func)
     def wrapper(*args, **kwargs):
         log.log(level, logmsg)
         return func(*args, **kwargs)
     return wrapper
 return decorate

# Example use
@logged(logging.DEBUG)
def add(x, y):
 return x + y

@logged(logging.CRITICAL, 'example')
def spam():
 print('Spam!')

初看起来,这种实现看上去很复杂,但是核心思想很简单。 最外层的函数 logged() 接受参数并将它们作用在内部的装饰器函数上面。 内层的函数 decorate() 接受一个函数作为参数,然后在函数上面放置一个包装器。 这里的关键点是包装器是可以使用传递给 logged() 的参数的。

定义一个接受参数的包装器看上去比较复杂主要是因为底层的调用序列。特别的,如果你有下面这个代码:

@decorator(x, y, z)
def func(a, b):
 pass

装饰器处理过程跟下面的调用是等效的;

def func(a, b):
 pass
func = decorator(x, y, z)(func)

decorator(x, y, z) 的返回结果必须是一个可调用对象,它接受一个函数作为参数并包装它, 可以参考9.7小节中另外一个可接受参数的包装器例子。

在后面的内容,需要我对python类与对象有更深的理解,再来探讨吧

你可能感兴趣的:(python,面向对象编程)