继续上次的进度:https://www.cnblogs.com/flashBoxer/p/9847521.html

正文:

装饰类

在类中有两种不通的方式使用装饰器,第一个和我们之前做过的函数非常相似:在类的方法上应用。这也是当时引入装饰器的原因之一

一些常用的装饰器已经内置到python中,像@classmethod @staticmethod @property。这三个装饰器我们之前都介绍过,这段就不翻译了(打字手酸,偷懒下)

下面的Circle 类使用了@classmethod @staticmethod和@property三个装饰器

  1. class Circle:
  2. def __init__(self, radius):
  3. self._radius = radius
  4.  
  5. @property
  6. def radius(self):
  7. """Get value of radius"""
  8. return self._radius
  9.  
  10. @radius.setter
  11. def radius(self, value):
  12. """Set radius, raise error if negative"""
  13. if value >= 0:
  14. self._radius = value
  15. else:
  16. raise ValueError("Radius must be positive")
  17.  
  18. @property
  19. def area(self):
  20. """Calculate area inside circle"""
  21. return self.pi() * self.radius**2
  22.  
  23. def cylinder_volume(self, height):
  24. """Calculate volume of cylinder with circle as base"""
  25. return self.area * height
  26.  
  27. @classmethod
  28. def unit_circle(cls):
  29. """Factory method creating a circle with radius 1"""
  30. return cls(1)
  31.  
  32. @staticmethod
  33. def pi():
  34. """Value of π, could use math.pi instead though"""
  35. return 3.1415926535

在这个类中
    .cylinder_volume()是一个常规函数
    .radius是一个可变属性:它可以被设置不同的值.然而通过定义setter方法,我们可以做一些判断来确保它不会被设置成一个没有意义的负数,.radius作为属性访问,不使用括号
    .area 是一个不可变的属性:没有.setter()方法的属性是无法更改的,即使它被定义为一个方法,它也被作为不需要括号的属性来使用。
    .unit_circle() 是一个类方法。它不被绑定到Circle的实例上.类方法通常用在工厂模式,用来创建类的特殊实例
    .pi() 是一个静态方法.除了命名空间外它不依赖Circle类。静态方法可以在实例或类上调用。

Circle类的使用例子:

  1. >>> c = Circle(5)
  2. >>> c.radius
  3. 5
  4.  
  5. >>> c.area
  6. 78.5398163375
  7.  
  8. >>> c.radius = 2
  9. >>> c.area
  10. 12.566370614
  11.  
  12. >>> c.area = 100
  13. AttributeError: can't set attribute
  14.  
  15. >>> c.cylinder_volume(height=4)
  16. 50.265482456
  17.  
  18. >>> c.radius = -1
  19. ValueError: Radius must be positive
  20.  
  21. >>> c = Circle.unit_circle()
  22. >>> c.radius
  23. 1
  24.  
  25. >>> c.pi()
  26. 3.1415926535
  27.  
  28. >>> Circle.pi()
  29. 3.1415926535

让我们定义一个类,在这个类中,我们会用到前面的@debug和@timer装饰器:

  1. from decorators import debug, timer
  2.  
  3. class TimeWaster:
  4. @debug
  5. def __init__(self, max_num):
  6. self.max_num = max_num
  7.  
  8. @timer
  9. def waste_time(self, num_times):
  10. for _ in range(num_times):
  11. sum([i**2 for i in range(self.max_num)])

看一下结果:

  1. >>> tw = TimeWaster(1000)
  2. Calling __init__(<time_waster.TimeWaster object at 0x7efccce03908>, 1000)
  3. '__init__' returned None
  4.  
  5. >>> tw.waste_time(999)
  6. Finished 'waste_time' in 0.3376 secs

另外一种方式是在整个类上使用装饰器.这里有个Python3.7中的dataclasses方法用例:

  1. from dataclasses import dataclass
  2.  
  3. @dataclass
  4. class PlayingCard:
  5. rank: str
  6. suit: str

语法的类似于函数装饰器。在上面的例子中,也可以通过PlayingCard = dataclass(PlayingCard)来实现。

类装饰器的一种简单用法是作为元类方式的替代.在两种情况下,你都在动态的改变一个类的定义

类的装饰器和函数的装饰器语法接近,不同的是装饰器需要接收一个类而不是一个函数作为参数.事实上,上面的装饰器都可以作用于类,但当你这么用的时候,你可能得不到预期的结果。下面将@timer装饰器应用到一个类

  1. from decorators import timer
  2.  
  3. @timer
  4. class TimeWaster:
  5. def __init__(self, max_num):
  6. self.max_num = max_num
  7.  
  8. def waste_time(self, num_times):
  9. for _ in range(num_times):
  10. sum([i**2 for i in range(self.max_num)])

@timer只是TimeWaster = timer(TimeWaster)的缩写

在这里@timer只能显示类实例化需要的时间

  1. >>> tw = TimeWaster(1000)
  2. Finished 'TimeWaster' in 0.0000 secs
  3.  
  4. >>> tw.waste_time(999)
  5. >>>

在后面会有一个正确的类装饰器的示例@singleton。它保证一个类只有一个实例

嵌套的装饰器

可以将多个装饰器叠加到一个函数上

  1. from decorators import debug, do_twice
  2.  
  3. @debug
  4. @do_twice
  5. def greet(name):
  6. print(f"Hello {name}")

运行的顺序会按照叠加的顺序, @debug 调用 @do_twice @do_twice 调用greet(),或者debug(do_twice(greet()))

  1. >>> greet("Eva")
  2. Calling greet('Eva')
  3. Hello Eva
  4. Hello Eva
  5. 'greet' returned None

更改@debug和@do_twice的顺序:

  1. from decorators import debug, do_twice
  2.  
  3. @do_twice
  4. @debug
  5. def greet(name):
  6. print(f"Hello {name}")

在这种情况下,@do_twice也会被应用到@debug中:

  1. >>> greet("Eva")
  2. Calling greet('Eva')
  3. Hello Eva
  4. 'greet' returned None
  5. Calling greet('Eva')
  6. Hello Eva
  7. 'greet' returned None

带参数的装饰器

在需要传参给你的装饰器是这个例子会非常有用。例如,@do_twice可以扩展到@repeat(num_times)装饰器.然后,可以将执行的被装饰函数的次数作为参数给出。      
可以这么做:

  1. @repeat(num_times=4)
  2. def greet(name):
  3. print(f"Hello {name}")
  4.  
  5. >>> greet("World")
  6. Hello World
  7. Hello World
  8. Hello World
  9. Hello World

考虑下如何实现这个功能

到目前为止,写在@后面写的名字引用一个可以被另外一个函数调用的函数对象,需要repeat(num_times=4)来返回一个函数对象,这个对象可以被作为装饰器,幸运的是,我们已经知道如何返回函数!一般来说,需要以下内容:

  1. def repeat(num_times):
  2. def decorator_repeat(func):
  3. ... # Create and return a wrapper function
  4. return decorator_repeat

通常,装饰器创建并返回一个内部包装函数,所以完整地写出这个例子会给你一个内部函数

  1. def repeat(num_times):
  2. def decorator_repeat(func):
  3. @functools.wraps(func)
  4. def wrapper_repeat(*args, **kwargs):
  5. for _ in range(num_times):
  6. value = func(*args, **kwargs)
  7. return value
  8. return wrapper_repeat
  9. return decorator_repeat

例子看起来有点乱,但我们只是添加了一个def来接收参数,这个装饰器语法我们之前处理过多次.让我们从最里面的函数开始:

  1. def wrapper_repeat(*args, **kwargs):
  2. for _ in range(num_times):
  3. value = func(*args, **kwargs)
  4. return value

wrapper_repeat()函数接收任意参数,并放回被装饰函数的值,func(). 这个包装函数还包括了被装饰函数num_times的循环 ,除了必须要使用外部参数num_times外,和之前看到的装饰器函数没有什么不同,

再走一步,你就会发现装饰器函数:

  1. def decorator_repeat(func):
  2. @functools.wraps(func)
  3. def wrapper_repeat(*args, **kwargs):
  4. ...
  5. return wrapper_repeat

decorator_repeat()和我们之前写的装饰器函数非常像,除了他的名字不同,因为我们为最外层的函数保留了基础名称repeat(),这个是用户要调用的函数。

最外层返回装饰器函数的引用

  1. def repeat(num_times):
  2. def decorator_repeat(func):
  3. ...
  4. return decorator_repeat

在repeat()中有一些细节:

将decorator_repeat()作为一个内部函数意味着repeat()将引用一个函数对象-decotator_repeat.之前,我们用没有括号的repeat来引用函数对象.定义带有参数的装饰器,就需要添加括号
        
        num_times参数看起来没有在repeat()本身中使用,但是通过传递num_times,会创建一个闭包,来存储num_times的值,直到wrapper_repeat()使用它为止。
    
一切就绪后,让我们看看结果:

  1. @repeat(num_times=4)
  2. def greet(name):
  3. print(f"Hello {name}")
  4.  
  5. >>> greet("World")
  6. Hello World
  7. Hello World
  8. Hello World
  9. Hello World

这是我们想要的结果

Both Please, But Never Mind the Bread

稍微注意下.你可以把装饰器同时定义为带参数或者不带参数.你可能不需要这样,但更有灵活性也不错

前面已经看到,当装饰器需要参数的时候,需要有一个额外的外部函数,困难在于,代码需要知道装饰器是否被调用了,是否有参数

因为只有在没有参数的情况下调用装饰器时才会直接传递装饰的函数,这个函数必须是可选参数.意味着装饰器参数必须要友关键字指定,可以使用特殊的*,也就是说,下面的参数都是关键字

  1. def name(_func=None, *, kw1=val1, kw2=val2, ...): #
  2. def decorator_name(func):
  3. ... # Create and return a wrapper function.
  4.  
  5. if _func is None:
  6. return decorator_name #
  7. else:
  8. return decorator_name(_func) #

_func参数是一个标记,提示装饰器被调用的时候是否有参数
    1.如果name调用的时候没有传参,被装饰函数会被作为_func传入.如果有参数传入,_func会被置为None,一些关键字参数可能已不再是默认值, 参数列表中的*表示其余参数不能作为位置参数调用。

2.装饰器可以传参调用,返回一个装饰器函数,它可以读取和返回一个函数
    
    3.装饰器不可以传参调用,会只将装饰器应用到函数上

改造下之前的@repeat装饰器

  1. def repeat(_func=None, *, num_times=2):
  2. def decorator_repeat(func):
  3. @functools.wraps(func)
  4. def wrapper_repeat(*args, **kwargs):
  5. for _ in range(num_times):
  6. value = func(*args, **kwargs)
  7. return value
  8. return wrapper_repeat
  9.  
  10. if _func is None:
  11. return decorator_repeat
  12. else:
  13. return decorator_repeat(_func)

和之前的对比,唯一的变化是在末尾添加了_func参数和if-else。
这些例子表明,@repeat现在可以在有或没有参数的情况下使用:

  1. @repeat
  2. def say_whee():
  3. print("Whee!")
  4.  
  5. @repeat(num_times=3)
  6. def greet(name):
  7. print(f"Hello {name}")

默认情况num_times的值是2

  1. >>> say_whee()
  2. Whee!
  3. Whee!
  4.  
  5. >>> greet("Penny")
  6. Hello Penny
  7. Hello Penny
  8. Hello Penny

有状态的装饰器

有时候,可以跟踪状态的装饰器也是很有用的.一个简单的例子,我们会创建一个统计函数调用次数的装饰器

注意:在教程的前面,我们讨论了基于给定参数返回值的纯函数.有状态的装饰器正好相反,返回值取决于当前状态以及给定的参数。

在下一节中,您将看到如何使用类来保持状态。但在简单的情况下,也可以使用函数属性:

  1. import functools
  2.  
  3. def count_calls(func):
  4. @functools.wraps(func)
  5. def wrapper_count_calls(*args, **kwargs):
  6. wrapper_count_calls.num_calls += 1
  7. print(f"Call {wrapper_count_calls.num_calls} of {func.__name__!r}")
  8. return func(*args, **kwargs)
  9. wrapper_count_calls.num_calls = 0
  10. return wrapper_count_calls
  11.  
  12. @count_calls
  13. def say_whee():
  14. print("Whee!")

状态——函数的调用次数——存储在包裹函数(wrapper_count_calls)的函数属性.num_calls中。下面是使用它的效果:

  1. >>> say_whee()
  2. Call 1 of 'say_whee'
  3. Whee!
  4.  
  5. >>> say_whee()
  6. Call 2 of 'say_whee'
  7. Whee!
  8.  
  9. >>> say_whee.num_calls
  10. 2

类装饰器

典型的维护状态的方式是使用类。在本节中,将看到如何重写@count_calls的例子来实现类装饰器

回想一下,装饰器语法@my_decorator只是func = my_decorator(func)一种方便快捷的用法.因此,如果my_decorator是一个类,需要在它的.__init__方法中接收func作为一个参数.而且,这个类需要是可以被调用的,这样它就可以替代装饰器函数了

如果需要一个类可以被调用,要实现.__call__方法(看示例:https://www.cnblogs.com/flashBoxer/p/9774064.html)

  1. class Counter:
  2. def __init__(self, start=0):
  3. self.count = start
  4.  
  5. def __call__(self):
  6. self.count += 1
  7. print(f"Current count is {self.count}")

.__call__方法每次运行都会尝试调用一个类的实例:

  1. >>> counter = Counter()
  2. >>> counter()
  3. Current count is 1
  4.  
  5. >>> counter()
  6. Current count is 2
  7.  
  8. >>> counter.count
  9. 2

因此,实现类装饰器需要实现.__init__和.__call__

  1. import functools
  2.  
  3. class CountCalls:
  4. def __init__(self, func):
  5. functools.update_wrapper(self, func)
  6. self.func = func
  7. self.num_calls = 0
  8.  
  9. def __call__(self, *args, **kwargs):
  10. self.num_calls += 1
  11. print(f"Call {self.num_calls} of {self.func.__name__!r}")
  12. return self.func(*args, **kwargs)
  13.  
  14. @CountCalls
  15. def say_whee():
  16. print("Whee!")

.__init__方法必须可以存储一个函数的引用和能够做一些必要的初始化. 调用.__call__方法来替代装饰器函数.它做的和我们之前的 wrapper()函数基本一样,注意,这里使用functools.update_wrapper()函数,而不是@functools.wraps

这个@CountCalls装饰器的工作原理与前一节相同:

  1. >>> say_whee()
  2. Call 1 of 'say_whee'
  3. Whee!
  4.  
  5. >>> say_whee()
  6. Call 2 of 'say_whee'
  7. Whee!
  8.  
  9. >>> say_whee.num_calls
  10. 2

更多现实中的例子

我们已经学到了很多(看了下翻译的行数量,已经1K+了,确实很多),已经学会如何创建各种各样的装饰师,把我们的新知识应用到创建更多的示例中,这些示例在现实中可能非常有用。

代码降速,重新访问

我们之前实现的@slow_down一直是保持sleep 1秒.现在你知道了如何给装饰器添加参数,因此,让我们来重写@slow_down,使用一个可选的rate参数来控制它的sleep时间:

  1. import functools
  2. import time
  3.  
  4. def slow_down(_func=None, *, rate=1):
  5. """Sleep given amount of seconds before calling the function"""
  6. def decorator_slow_down(func):
  7. @functools.wraps(func)
  8. def wrapper_slow_down(*args, **kwargs):
  9. time.sleep(rate)
  10. return func(*args, **kwargs)
  11. return wrapper_slow_down
  12.  
  13. if _func is None:
  14. return decorator_slow_down
  15. else:
  16. return decorator_slow_down(_func)

我们使用  Both Please, But Never Mind the Bread  这里的样例来让@slow_down有参数和没有参数时都可调用,countdown()函数现在在每次计数之间休眠2秒:

  1. @slow_down(rate=2)
  2. def countdown(from_number):
  3. if from_number < 1:
  4. print("Liftoff!")
  5. else:
  6. print(from_number)
  7. countdown(from_number - 1

和前面一样,你最好自己写写,跑下看看结果

  1. >>> countdown(3)
  2. 3
  3. 2
  4. 1
  5. Liftoff!

创建单例模式

单例模式是一个只有一个实例的类.在Python经常使用的单例对象包括None,True和False.可以使用is来比较,像我们之前在Both Please的章节中:

  1. if _func is None:
  2. return decorator_name
  3. else:
  4. return decorator_name(_func)

is只对完全相同实例的对象返回True。下面的@singleton装饰器将类的第一个实例存储为属性,从而将类转换为单例对象。之后创建实例只是返回已经存储的实例:

  1. import functools
  2.  
  3. def singleton(cls):
  4. """Make a class a Singleton class (only one instance)"""
  5. @functools.wraps(cls)
  6. def wrapper_singleton(*args, **kwargs):
  7. if not wrapper_singleton.instance:
  8. wrapper_singleton.instance = cls(*args, **kwargs)
  9. return wrapper_singleton.instance
  10. wrapper_singleton.instance = None
  11. return wrapper_singleton
  12.  
  13. @singleton
  14. class TheOne:
  15. pass

这个类装饰器和我们的函数装饰器基本一样.唯一不同的地方在于使用cls代替了fun来表示这是一个类装饰器

看下运行结果:

  1. >>> first_one = TheOne()
  2. >>> another_one = TheOne()
  3.  
  4. >>> id(first_one)
  5. 140094218762280
  6.  
  7. >>> id(another_one)
  8. 140094218762280
  9.  
  10. >>> first_one is another_one
  11. True

很明显,first_one确实与另一个实例完全相同。

缓存返回值

装饰器可以提供很方便的缓存和记忆机制.作为一个例子,我们来看看斐波那契数列的递归定义:

  1. from decorators import count_calls
  2.  
  3. @count_calls
  4. def fibonacci(num):
  5. if num < 2:
  6. return num
  7. return fibonacci(num - 1) + fibonacci(num - 2)

实现很简单,性能很糟糕

  1. >>> fibonacci(10)
  2. <Lots of output from count_calls>
  3. 55
  4.  
  5. >>> fibonacci.num_calls
  6. 177

为了计算第10个斐波那契数,你实际上只需要计算前面的斐波那契数,但是这个实现需要177次计算。更糟糕的是:斐波纳契数列(20)需要21891次计算,第30次需要270万次计算。这是因为代码一直在重新计算已知的斐波那契数。

通常的解决方案是使用for循环和查找表来实现斐波那契数。但是,简单的计算缓存也可以做到这一点:

  1. import functools
  2. from decorators import count_calls
  3.  
  4. def cache(func):
  5. """Keep a cache of previous function calls"""
  6. @functools.wraps(func)
  7. def wrapper_cache(*args, **kwargs):
  8. cache_key = args + tuple(kwargs.items())
  9. if cache_key not in wrapper_cache.cache:
  10. wrapper_cache.cache[cache_key] = func(*args, **kwargs)
  11. return wrapper_cache.cache[cache_key]
  12. wrapper_cache.cache = dict()
  13. return wrapper_cache
  14.  
  15. @cache
  16. @count_calls
  17. def fibonacci(num):
  18. if num < 2:
  19. return num
  20. return fibonacci(num - 1) + fibonacci(num - 2)

缓存作为查找表工作,所以现在fibonacci()只执行一次计算:

  1. >>> fibonacci(10)
  2. Call 1 of 'fibonacci'
  3. ...
  4. Call 11 of 'fibonacci'
  5. 55
  6.  
  7. >>> fibonacci(8)
  8. 21

注意,在对fibonacci(8)的最后调用中,没有进行新的计算,因为fibonacci(10)已经计算了第8个fibonacci数。
在标准库中,提供了@functools.lru_cache。

这个装饰器比上面的例子要具备更多特性.我们应该使用@functools.lru_cache来代替我们自己写的缓存装饰器

  1. import functools
  2.  
  3. @functools.lru_cache(maxsize=4)
  4. def fibonacci(num):
  5. print(f"Calculating fibonacci({num})")
  6. if num < 2:
  7. return num
  8. return fibonacci(num - 1) + fibonacci(num - 2)

maxsize参数指定缓存了多少次调用。默认值是128,但是可以指定maxsize=None来缓存所有函数调用。但是,请注意,如果正在缓存许多很大的对象,这可能会导致内存问题。

可以使用.cache_info()方法查看缓存的执行情况,并在需要时进行调优。在我们的示例中,我们设定一个小maxsize来查看从缓存中删除元素的效果:

  1. >>> fibonacci(10)
  2. Calculating fibonacci(10)
  3. Calculating fibonacci(9)
  4. Calculating fibonacci(8)
  5. Calculating fibonacci(7)
  6. Calculating fibonacci(6)
  7. Calculating fibonacci(5)
  8. Calculating fibonacci(4)
  9. Calculating fibonacci(3)
  10. Calculating fibonacci(2)
  11. Calculating fibonacci(1)
  12. Calculating fibonacci(0)
  13. 55
  14.  
  15. >>> fibonacci(8)
  16. 21
  17.  
  18. >>> fibonacci(5)
  19. Calculating fibonacci(5)
  20. Calculating fibonacci(4)
  21. Calculating fibonacci(3)
  22. Calculating fibonacci(2)
  23. Calculating fibonacci(1)
  24. Calculating fibonacci(0)
  25. 5
  26.  
  27. >>> fibonacci(8)
  28. Calculating fibonacci(8)
  29. Calculating fibonacci(7)
  30. Calculating fibonacci(6)
  31. 21
  32.  
  33. >>> fibonacci(5)
  34. 5
  35.  
  36. >>> fibonacci.cache_info()
  37. CacheInfo(hits=17, misses=20, maxsize=4, currsize=4)

添加单元信息

下面的示例与前面的Registering Plugins示例有点类似,因为它不会真正改变被装饰函数的行为。相反,它只是将unit添加为函数属性:

  1. def set_unit(unit):
  2. """Register a unit on a function"""
  3. def decorator_set_unit(func):
  4. func.unit = unit
  5. return func
  6. return decorator_set_unit

下面的示例根据圆柱体的半径和高度(以厘米为单位)来计算体积:

  1. import math
  2.  
  3. @set_unit("cm^3")
  4. def volume(radius, height):
  5. return math.pi * radius**2 * height
  6.  
  7. 这个.unit函数属性是可以访问的:
  8. >>> volume(3, 5)
  9. 141.3716694115407
  10.  
  11. >>> volume.unit
  12. 'cm^3'

注意,可以使用函数注释实现类似的功能:

  1. import math
  2.  
  3. def volume(radius, height) -> "cm^3":
  4. return math.pi * radius**2 * height

但是,由于注释用于类型提示,因此很难将注释和静态类型检查相结合。   
    
在连接到一个能够在单位间转换的库,单位可以变得更加强大和有趣.pip install pint,  您可以将体积转换为立方英寸或加仑:

  1. >>> import pint
  2. >>> ureg = pint.UnitRegistry()
  3. >>> vol = volume(3, 5) * ureg(volume.unit)
  4.  
  5. >>> vol
  6. <Quantity(141.3716694115407, 'centimeter ** 3')>
  7.  
  8. >>> vol.to("cubic inches")
  9. <Quantity(8.627028576414954, 'inch ** 3')>
  10.  
  11. >>> vol.to("gallons").m # Magnitude
  12. 0.0373464440537444

你还可以修改装饰器来直接返回一个Pint数量.数量是通过与单位相乘得到的,在pint中,units必须只能在UnitRegistry中查询.这里注册用来存储函数属性来避免命名空间混乱

  1. def use_unit(unit):
  2. """Have a function return a Quantity with given unit"""
  3. use_unit.ureg = pint.UnitRegistry()
  4. def decorator_use_unit(func):
  5. @functools.wraps(func)
  6. def wrapper_use_unit(*args, **kwargs):
  7. value = func(*args, **kwargs)
  8. return value * use_unit.ureg(unit)
  9. return wrapper_use_unit
  10. return decorator_use_unit
  11.  
  12. @use_unit("meters per second")
  13. def average_speed(distance, duration):
  14. return distance / duration

使用@use_unit装饰器,转换单位实际上是很容易

  1. >>> bolt = average_speed(100, 9.58)
  2. >>> bolt
  3. <Quantity(10.438413361169102, 'meter / second')>
  4.  
  5. >>> bolt.to("km per hour")
  6. <Quantity(37.578288100208766, 'kilometer / hour')>
  7.  
  8. >>> bolt.to("mph").m # Magnitude
  9. 23.350065679064745

验证JSON

让我们看最后一个用例。快速看下Flask路由的管理程序:

  1. @app.route("/grade", methods=["POST"])
  2. def update_grade():
  3. json_data = request.get_json()
  4. if "student_id" not in json_data:
  5. abort(400)
  6. # Update database
  7. return "success!"

这里我们确保key student_id是请求的一部分.虽然验证有效,但它实际上并不属于函数本身.另外,可能还有其他使用相同验证的路由。因此,让我们Don't repeat yourself,来使用装饰器抽象出任何不必要的逻辑,下面的@validate_json装饰器会完成这个工作:

  1. from flask import Flask, request, abort
  2. import functools
  3. app = Flask(__name__)
  4.  
  5. def validate_json(*expected_args): #
  6. def decorator_validate_json(func):
  7. @functools.wraps(func)
  8. def wrapper_validate_json(*args, **kwargs):
  9. json_object = request.get_json()
  10. for expected_arg in expected_args: #
  11. if expected_arg not in json_object:
  12. abort(400)
  13. return func(*args, **kwargs)
  14. return wrapper_validate_json
  15. return decorator_validate_json

在上面的代码中,装饰器采用了一个可变长度列表作为参数,这样我们就可以传递尽可能多的字符串参数,每个参数都代表一个用于验证JSON数据的键:

1.json的keys列表作为参数传递给装饰器
    2.包裹函数验证JSON数据中出现的每个预期键

然后,路由管理程序可以关注其真正的业务级别——因为它可以安全地假设JSON数据是有效的:

  1. @app.route("/grade", methods=["POST"])
  2. @validate_json("student_id")
  3. def update_grade():
  4. json_data = request.get_json()
  5. # Update database.
  6. return "success!"

结束语:翻译就到这里吧,这篇文章的作者对装饰器的理解很是深入,文章很长,翻起来确实花了不少时间。文中如果有翻译不稳妥的地方,请留言给我。最后老铁们如果觉得对理解python的装饰器有帮助,右下角点个赞吧,结尾附上原文地址:https://realpython.com/primer-on-python-decorators/

Python 装饰器入门(下)的更多相关文章

  1. Python 装饰器入门(上)

    翻译前想说的话: 这是一篇介绍python装饰器的文章,对比之前看到的类似介绍装饰器的文章,个人认为无人可出其右,文章由浅到深,由函数介绍到装饰器的高级应用,每个介绍必有例子说明.文章太长,看完原文后 ...

  2. python -- 装饰器入门

    用例: 统计函数执行需要的时间 假设我们执行的一段代码的运行时间比我们预想的时间要久,而这段代码块有多个函数调用组成,我们有理由相信至少是其中的一个函数调用导致整个代码块产生了瓶颈.我们如何去发现导致 ...

  3. python装饰器入门

    按别人的教程弄的. 要清楚基于类和基于函数的实现的不同之处. #!/usr/bin/env python # -*- coding: utf-8 -*- ''' class entryExit(obj ...

  4. Python装饰器与面向切面编程

    今天来讨论一下装饰器.装饰器是一个很著名的设计模式,经常被用于有切面需求的场景,较为经典的有插入日志.性能测试.事务处理等.装饰器是解决这类问题的绝佳设计,有了装饰器,我们就可以抽离出大量函数中与函数 ...

  5. python装饰器总结

    一.装饰器是什么 python的装饰器本质上是一个Python函数,它可以让其他函数在不需要做任何代码变动的前提下增加额外功能,装饰器的返回值也是一个函数对象.简单的说装饰器就是一个用来返回函数的函数 ...

  6. 【转】Python装饰器与面向切面编程

    原文请参考: http://www.cnblogs.com/huxi/archive/2011/03/01/1967600.html 今天来讨论一下装饰器.装饰器是一个很著名的设计模式,经常被用于有切 ...

  7. Python装饰器探险

    关于python装饰器的理解和用法,推荐廖雪峰老师和这一篇博客以及知乎 以下代码均已手动敲过,看完本篇内容,包你装饰器小成! 装饰器实际上就是为了给某程序增添功能,但该程序已经上线或已经被使用,那么就 ...

  8. Python装饰器与面向切面编程(转)

    add by zhj: 装饰器的作用是将代码中可以独立的功能独立出来,实现代码复用,下面那个用于统计函数运行时间的装饰器就是很好的例子,我们不用修改原有的函数和调用原有函数的地方,这遵循了开闭原则.装 ...

  9. Python——装饰器与面向切面编程

    今天来讨论一下装饰器.装饰器是一个很著名的设计模式,经常被用于有切面需求的场景,较 为经典的有插入日志.性能测试.事务处理等.装饰器是解决这类问题的绝佳设计,有了装饰器,我们就可以抽离出大量函数中与函 ...

随机推荐

  1. kubeadm安装kubernetes 1.13.1集群完整部署记录

    k8s是什么 Kubernetes简称为k8s,它是 Google 开源的容器集群管理系统.在 Docker 技术的基础上,为容器化的应用提供部署运行.资源调度.服务发现和动态伸缩等一系列完整功能,提 ...

  2. 区块链--Bitcoin共识机制

    目录 中心化和去中心化 比特币共识机制 拜占庭将军共识机制 比特币成功解决了拜占庭问题 中心化和去中心化 中心化模式: 优点:效率高 缺点:中间层次太多(组织层次连接) 去中心化模式: 缺点:效率低 ...

  3. error: Build input file cannot be found: '*******/node_modules/react-native/Libraries/WebSocket/libfishhook.a' 问题解决记录

    解决了刚才的'config.h' file not found问题,本以为就可以顺畅的跑起来,谁知道又被恶心到了,Build input file cannot be found!!! 问题: err ...

  4. PAT甲题题解-1104. Sum of Number Segments (20)-(水题)

    #include <iostream> #include <cstdio> #include <algorithm> #include <string.h&g ...

  5. "Gun N' Rose" Team学习心得

    如果我比别人看得更远,只因为我站在巨人的肩膀上.                                                        ——牛顿 高级软件工程课程终于开课了!第 ...

  6. Docker 下 mysql 简单的 主从复制实现

    1. 拉取镜像 docker pull mysql: 2. 运行这个镜像 docker run -d --name maser mysql: 3. 安装一些必要的软件 docker exec -it ...

  7. TP3.2加载外部PHPexcel类,实现导入和导出

    TP3.2加载外部PHPexcel类,实现导入和导出 导入: 1.将下载好的PHPexcel文件放到libray/Org/Uti/文件夹下,将PHPEXCEL.PHP改为PHPEXCEL.class. ...

  8. XShell中文乱码问题解决

    现象:XShell终端中输入中文显示乱码 原因:XShell终端的编码格式与服务器不同 解决:修改XShell终端的编码格式:菜单中点击,文件->属性->终端->编码,选择“UTF- ...

  9. Java之流的分类

    Java I/O流分类:

  10. SP4487 GSS6 - Can you answer these queries VI

    题目大意 给出一个由N个整数组成的序列A,你需要应用M个操作: I p x 在 p  处插入插入一个元素 x D p 删除 p 处的一个元素 R p x 修改 p 处元素的值为 x Q l r 查询一 ...