让Python更优雅更易读(第二集)
友情链接
1.装饰器
1.1装饰器特别适合用来实现以下功能
- 运行时校验:在执行阶段进行特定校验,当校验通不过时终止执行。 适合原因:装饰器可以方便地在函数执行前介入,并且可以读取所有参数辅助校验。
- 注入额外参数:在函数被调用时自动注入额外的调用参数。适合原因:装饰器的位置在函数头部,非常靠近参数被定义的位置,关联性强。
- 缓存执行结果:通过调用参数等输入信息,直接缓存函数执行结果。
- 注册函数:将被装饰函数注册为某个外部流程的一部分。适合原因:在定义函数时可以直接完成注册,关联性强。
- 替换为复杂对象:将原函数(方法)替换为更复杂的对象,比如类实例或特殊的描述符对象
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!
"""
让Python更优雅更易读(第二集)的更多相关文章
- Karmada v1.3:更优雅 更精准 更高效
摘要:最新发布的1.3版本中,Karmada重新设计了应用跨集群故障迁移功能,实现了基于污点的故障驱逐机制,并提供平滑的故障迁移过程,可以有效保障服务迁移过程的连续性(不断服). 本文分享自华为云社区 ...
- 让Python更优雅更易读(第一集)
变量和注释 1.变量 在编写变量尽量要让其清晰只给,让人清除搞清楚代码的意图 下方两段代码作用完全一样,但第二段代码是不是更容易让人理解 value = s.strip() username = in ...
- Selenium 2自动化测试实战36(更易读的测试报告)
一.更易读的测试报告 1.知识点:python的注释. 1.一种叫comment,为普通的注释2.另一种叫doc string,用于函数,类和方法的描述.在类或方法的下方,通过三引号("&q ...
- 如何让code变得更易读
从开始编码到现在,从没有意识去如何去写出更加规范,更加易读的代码,只是按照需求将某一功能进行实现.下面是最近在网上搜索查看的一些通用的知识点,做一记录. 单一抽象层次 单一抽象层次是指一个函数或者方法 ...
- git rebase VS git merge? 更优雅的 git 合并方式值得拥有
写在前面 如果你不能很好的应用 Git,那么这里为你提供一个非常棒的 Git 在线练习工具 Git Online ,你可以更直观的看到你所使用的命令会产生什么效果 另外,你在使用 Git 合并分支时只 ...
- 少年,是时候换种更优雅的方式部署你的php代码了
让我们来回忆下上次你是怎么发布你的代码的: 1. 先把线上的代码用ftp备份下来 2. 上传修改了的文件 3. 测试一下功能是否正常 4. 网站500了,赶紧用备份替换回去 5. 替换错了/替换漏了 ...
- 让 Java 中 if else 更优雅的几个小技巧
对于一个高级 crud 工程师而言,if else 是写代码时使用频率最高的关键词之一,然而有时过多的 if else 会让我们优雅的 crud 代码显得不那么优雅,并且感到脑壳疼
- Effective java 系列之更优雅的关闭资源-try-with-resources
背景: 在Java编程过程中,如果打开了外部资源(文件.数据库连接.网络连接等),我们必须在这些外部资源使用完毕后,手动关闭它们.因为外部资源不由JVM管理,无法享用JVM的垃圾回收机制,如果我们不在 ...
- MySQL root密码忘记,原来还有更优雅的解法!
一直以来,对于MySQL root密码的忘记,以为只有一种解法-skip-grant-tables. 问了下群里的大咖,第一反应也是skip-grant-tables.通过搜索引擎简单搜索了下,无论是 ...
随机推荐
- RPA应用场景-考勤审批
场景概述 考勤审批 所涉系统名称 考勤系统,微信 人工操作(时间/次) 5分钟 所涉人工数量 43 操作频率 不定时 场景流程 1.客户领导长期出差,又不想对考勤系统做深度开发: 2.员工请假后,领导 ...
- bat-命令行安装软件
批处理 执行的两种方式 1.直接右键以管理员身份运行 2.在管理员身份的cmd窗口中 .\xxx.bat 执行 区别 第一种方式 当前cmd默认路径为 C:\windows\system32 第二种方 ...
- Object类中wait带参方法和notifyAll方法和线程间通信
notifyAll方法: 进入到Timed_Waiting(计时等待)状态有两种方式: 1.sleep(long m)方法,在毫秒值结束之后,线程睡醒,进入到Runnable或BLocked状态 2. ...
- JavaWEB-03-JDBC
内容 JDBC `JDBC`简介 JDBC `JDBC`快速入门 JDBC API `JDBC API` 详解 数据库连接池 JDBC `JDBC`案例 1. JDBC入门 1.1 概述 概念 JDB ...
- 可落地的DDD(7)-战术设计上的一些误区
背景 几年前我总结过DDD战术设计的一些落地经验可落地的DDD(5)-战术设计,和一次关于聚合根的激烈讨论最近两年有些新的落地体验,回过头来发现,当初对这些概念的理解还是没有深入,这篇文章重新阐述下. ...
- 40+倍提升,详解 JuiceFS 元数据备份恢复性能优化之路
JuiceFS 支持多种元数据存储引擎,且各引擎内部的数据管理格式各有不同.为了便于管理,JuiceFS 自 0.15.2 版本提供了 dump 命令允许将所有元数据以统一格式写入到 JSON 文件进 ...
- Solution -「树上杂题?」专练
主要是记录思路,不要被刚开始错误方向带偏了 www 「CF1110F」Nearest Leaf 特殊性质:先序遍历即为 \(1 \to n\),可得出:叶子节点编号递增或可在不改变树形态的基础上调整为 ...
- Solution -「Luogu 3959」 宝藏
果真是宝藏题目. 0x01 前置芝士 这道题我是真没往状压dp上去想.题目来源. 大概看了一下结构.盲猜直接模拟退火!\xyx 所需知识点:模拟退火,贪心. 0x02 分析 题目大意:给你一个图,可能 ...
- 机器学习-Kmeans
一.什么是聚类算法? 1.用于发现共同的群体(cluster),比如:邮件聚类.用户聚类.图片边缘. 2.聚类唯一会使用到的信息是:样本与样本之间的相似度(跟距离负相关) 给定N个训练样本(未标记的) ...
- 【我的面试-01】Web前端开发实习岗-面试题总结
简单开头 首先技术面试官会根据简历里所写的项目和个人掌握技术栈提问(我不知道已经改过多少次简历了,因为前期投简历是真的是沉在茫茫大海,捞漂流瓶都捞不到的那种) 我的技术栈:(Vue还在苦苦的自学当中, ...
