友情链接

让Python更优雅更易读(第一集)

1.装饰器

1.1装饰器特别适合用来实现以下功能

  1. 运行时校验:在执行阶段进行特定校验,当校验通不过时终止执行。 适合原因:装饰器可以方便地在函数执行前介入,并且可以读取所有参数辅助校验。
  2. 注入额外参数:在函数被调用时自动注入额外的调用参数。适合原因:装饰器的位置在函数头部,非常靠近参数被定义的位置,关联性强。
  3. 缓存执行结果:通过调用参数等输入信息,直接缓存函数执行结果。
  4. 注册函数:将被装饰函数注册为某个外部流程的一部分。适合原因:在定义函数时可以直接完成注册,关联性强。
  5. 替换为复杂对象:将原函数(方法)替换为更复杂的对象,比如类实例或特殊的描述符对象

1.2装饰器简单实现

import time
def cal_time(func):
def wrapper(*args,**kwargs):
t1=time.time()
result=func(*args,**kwargs)
t2=time.time()
print(f"{func.__name__} running time: {t2-t1} secs.")
return result
return wrapper

cal_time装饰器接收待装饰函数func作为唯一的位置参数,并在函数内定义了一个新函数:wrapper。

@cal_time
def second2():
time.sleep(2) second2()#second2 running time: 2.0001144409179688 secs.

一个无参数装饰器,实现起来较为简单。假如你想实现一个接收参数的装饰器,代码会更复杂一些。

import time
def cal_time(print_args=False):
def decorator(func):
def wrapper(*args,**kwargs):
t1=time.time()
result=func(*args,**kwargs)
t2=time.time()
if print_args:
print(f'args: {args},kwargs:{kwargs}')
print(f"{func.__name__} running time: {t2-t1} secs.")
return result
return wrapper
return decorator @cal_time(print_args=True)
def second2():
time.sleep(2) second2()
#args: (),kwargs:{}
#second2 running time: 2.0001144409179688 secs.
#先进行一次调用,传入装饰器参数,获得第一层内嵌函数
#进行第二次调用,获取第二层内嵌函数wrapper
_decorator = cal_time(print_args=True)
sleepTime = _decorator(second2)

1.3使用functools.wraps()修饰包装函数

def calls_counter(func):
"""装饰器:记录函数被调用多少次"""
counter = 0
def decorated(*args, **kwargs):
nonlocal counter
counter +=1
return func(*args,**kwargs)
def print_counter():
print(f'counter:{counter}')
#给函数增加额外函数,打印统计函数被调用的次数
decorated.print_counter = print_counter
return decorated @cal_time()
@calls_counter
def second2():
time.sleep(2)

这是一个记录函数被调用多少次的装饰器

我们发现当我们同时使用上述两个装饰器的时候报错了

Traceback (most recent call last):
File "F:/pythonProject1/AutomaticTesting/single.py", line 33, in <module>
second2.print_counter()
AttributeError: 'function' object has no attribute 'print_counter'

首先,由calls_counter对函数进行包装,此时的second2变成了新的包装函数,包含print_counter属性

使用cal_time包装后,second2变成了cal_time提供的包装函数,原包装函数额外的print_counter属性被自然地丢掉了

要解决上述问题只要引入装饰器wraps就可以了

import time
from functools import wraps def cal_time(print_args=False):
def decorator(func):
@wraps(func)
def wrapper(*args,**kwargs):
... def calls_counter(func):
"""装饰器:记录函数被调用多少次"""
counter = 0 @wraps(func)
def decorated(*args, **kwargs):
... @cal_time()
@calls_counter
def second2():
time.sleep(2)
#
second2()
second2.print_counter()
#second2 running time: 2.0001144409179688 secs.
#counter:1

1.4可选参数的装饰器

以上数的cal_time为例

有了参数以后我们不仅在装饰器使用时候@必须带上()

def cal_time(func=None,*,print_args=False):
def decorator(_func):
@wraps(_func)
def wrapper(*args,**kwargs):
t1=time.time()
result=func(*args,**kwargs)
t2=time.time()
if print_args:
print(f'args: {args},kwargs:{kwargs}')
print(f"{_func.__name__} running time: {t2-t1} secs.")
return result
return wrapper
if func is None:
return decorator
else:
return decorator(func)
@cal_time
@calls_counter
def second2():
time.sleep(2)

这时候调用就不需要()了

1.5用类来实现装饰器(函数替换)

能否用装饰器形式使用只有一个判断标准,就是是否是可调用的对象

如果一个类实现了__call__魔法方法,那么他的实例就是可调用对象

现在我们把计时装饰器改写

import time
from functools import wraps
class cal_time:
"""装饰器:记录函数用时"""
def __init__(self,print_arg=False):
self.print_arg = print_arg def __call__(self, func):
@wraps(func)
def wrapper(*args,**kwargs):
t1=time.time()
result=func(*args,**kwargs)
t2=time.time()
if self.print_arg:
print(f'args: {args},kwargs:{kwargs}')
print(f"{func.__name__} running time: {t2-t1} secs.")
return result
return wrapper

2数据模型与描述符

数据模型有关的方法,基本都以双下划线开头和结尾,它们通常被称为魔法方法

例如:我们打印对象的时候输出的是<类名+内存地址>

class Person:

    def __init__(self, name):
self.name = name print(Person("yetangjian"))#<__main__.Person object at 0x000001BA41805FD0>

__str__就是Python数据模型里最基础的一部分。当对象需要当作字符串使用时,我们可以用__str__方法来定义对象的字符串化结果

注:除了print()以外,str()与.format()函数同样也会触发__str__方法

class Person:

    ...

    def __str__(self):
return self.name print(Person("yetangjian")) #yetangjian
print(f'l am {Person("yetangjian")}') #l am yetangjian

常见魔法方法

01. __repr__

在如下的例子中,使用了一个{name!r}这样的语法

变量名后的!r表示优先使用repr方法,再使用str方法。针对字符串类型会自动给变量加上引号,省去了手动添加的麻烦。

name='yetangjian'
age = 18
print(f"{name!r},{age!r}")#'yetangjian',18

同样我们实现的方法与str方法类似,我们依旧使用上述的例子

class Person:

    ...

    def __repr__(self):
return f"{self.name!r},{self.age!r}" p=Person("yetangjian",80)
print(repr(p))#'yetangjian',80
02.__format__

定义对象在字符串格式化时的行为

class Person:

    ...

    def __format__(self, format_spec):
if format_spec == "all":
return f"{self.name!r},{self.age!r}"
else:
return f"{self.name!r}" p=Person("yetangjian",80)
print(f"all:{p:all}") #all:'yetangjian',80
print("only name:{p:simple}".format(p=p)) #only name:'yetangjian'

模板语法不仅适用于format,同样适用于f-string

03比较运算符重载
class Num:

    def __init__(self,number):
self.n = number
#等于
def __eq__(self, other):
if isinstance(other,self.__class__):
return other.n == self.n
return False
#不等于
def __ne__(self, other):
return not (self == other) def __lt__(self, other):
if isinstance(other,self.__class__):
return self.n < other.n
#不支持某种运算,可以返回NotImplemented
return NotImplemented
#小于等于
def __le__(self, other):
return self.__lt__(other) or self.__eq__(other) num1 = Num(5)
num2 = Num(10)
print(num1 <= num2) #True

但是我们会发现重载这些运算符号代码量实在太大,而且较为重复。下面推荐一个工具,简化这个工作量

@total_ordering

使用functools下的这个装饰器,我们只需要实现__eq__方法,__lt__、__le__、__gt__、__ge__四个方法里随意挑一个实现即可,@total_ordering会帮你自动补全剩下的所有方法

from functools import total_ordering

@total_ordering
class Num: def __init__(self,number):
self.n = number
#等于
def __eq__(self, other):
if isinstance(other,self.__class__):
return other.n == self.n
return False def __lt__(self, other):
if isinstance(other,self.__class__):
return self.n < other.n
#不支持某种运算,可以返回NotImplemented
return NotImplemented num1 = Num(5)
num2 = Num(10)
print(num1 <= num2) #True

描述符

使用property做校验
class Count:

    def __init__(self,c):
self.__math = c
@property
def math(self):
return self.__math
@math.setter
def math(self,v):
if v > 50:
raise ValueError("数字大于100")
self.__math = v c = Count(5)
c.math = 40
print(c.math) #40

描述符(descriptor)是Python对象模型里的一种特殊协议,它主要和4个魔法方法有关: __get__、__set__、__delete__和__set_name__

任何一个实现了__get__、__set__或__delete__的类,都可以称为描述符类,它的实例则叫作描述符对象

__get__
class Info:
def __get__(self, instance, owner=None):
"""
__get__方法存在两个参数
instance:当通过实例来访问描述符属性,该参数为实例对象;
如果通过类访问,则为None
owner:描述符对象所绑定的类
"""
print(f'__get__,{instance},{owner}')
if not instance:
return self class Foo:
#要使用一个描述符,最常见的方式是把它的实例对象设置为其他类(常被称为owner类)的属性
bar = Info() print(Foo.bar)
print(Foo().bar)
"""
通过类来访问,所以instance为None,返回描述符本身
__get__,None,<class '__main__.Foo'>
<__main__.Info object at 0x0000000001D644F0>
通过实例来访问
__get__,<__main__.Foo object at 0x00000000026149D0>,<class '__main__.Foo'>
None
"""
__set__
class Info:
...... def __set__(self, instance, value):
"""
__set__方法存在两个参数
instance:属性当前绑定的实例对象
value:待设置的属性值
"""
print(f'__set__,{instance},{value}') Foo().bar = 10#__set__,<__main__.Foo object at 0x0000000001DE49D0>,10

描述符的__set__仅对实例起作用,对类不起作用。这和__get__方法不一样

使用描述符实现校验
class IntegerField:
"""整型字段,只允许一定范围内的整型值
:param min_value: 允许的最小值
:param max_value: 允许的最大值
""" def __init__(self, min_value, max_value):
self.min_value = min_value
self.max_value = max_value def __get__(self, instance,owner=None):
# 当不是通过实例访问时,直接返回描述符对象
if not instance:
return self
# 返回保存在实例字典里的值
return instance.__dict__['_integer_field'] def __set__(self, instance, value):
# 校验后将值保存在实例字典里
value = self._validate_value(value)
instance.__dict__['_integer_field'] = value def _validate_value(self, value):
"""校验值是否为符合要求的整数"""
try:
value = int(value)
except (TypeError, ValueError):
raise ValueError('value is not a valid integer!')
if not (self.min_value <= value <= self.max_value):
raise ValueError(f'value must between {self.min_value} and {self.max_value}!')
return value

因为每个描述符对象都是owner类的属性,而不是类实例的属性,所以我们用的都是instance.dict而不是用self.dict。如果把值都存入self中就会存在互相覆盖,值冲突的情况

class Person:
age = IntegerField(min_value=10,max_value=100) def __init__(self,age):
self.age = age p = Person(110)
"""
raise ValueError(f'value must between {self.min_value} and {self.max_value}!')
ValueError: value must between 10 and 100!
"""

最新文章

  1. React学习笔记。
  2. EF里如何定制实体的验证规则和实现IObjectWithState接口进行验证以及多个实体的同时验证
  3. 23.备忘录模式(Memento Pattern)
  4. Unity开发游戏 flapybird 无广告老马版分享
  5. ArcMap中的名称冲突问题
  6. ASP.NET MVC 中的ViewData与ViewBag
  7. 【python之路8】python基本数据类型(二)
  8. Robot Framework自动化测试环境的搭建
  9. hdu3306 Another kind of Fibonacci【矩阵快速幂】
  10. react学习笔记1--基础知识
  11. JQuery中 json 和字符串直接相互转换
  12. SQLsever 复制一行内容到本表
  13. [.NET] 《Effective C#》快速笔记(二)- .NET 资源托管
  14. vue-cli3.0 项目如何使用sass
  15. Vue-比较方法、计算属性和侦听器
  16. 苹果与Windows双系统时间不同步的解决办法
  17. java php c# 三种语言的AES加密互转
  18. linux小白
  19. Django入门指南-第7章:模板引擎设置(完结)
  20. typeof运算符

热门文章

  1. static关键字续、继承、重写、多态
  2. Redis docker 主从模式与哨兵sentinel
  3. 30m精度土壤类型、土壤质地、土壤有机质、土壤PH、土壤氮磷钾
  4. 写了个 Markdown 命令行小工具,希望能提高园友们发文的效率!
  5. JDBC:处理事务
  6. e.printStackTrace() 原理的分析
  7. JAVA定时任务原理入门
  8. 1个小时!从零制作一个! AI图片识别WEB应用!
  9. 金玉良缘易配而木石前盟难得|M1 Mac os(Apple Silicon)天生一对Python3开发环境搭建(集成深度学习框架Tensorflow/Pytorch)
  10. eclipse mave无法下载jar包