条件分支控制流高级用法
条件分支控制流高级用法
1.概述
这篇文章总结条件判断的一些高级用法,总结下条件分支控制流使用策略。
- 在编程中能不用条件就不用
- 能不嵌套条件表达式就不要嵌套
- 能简化条件表达式就不要复杂化条件表达式。
2.条件控制流高级用法
2.1.省略零值判断
当你编写if分支时,如果需要判断某个类型的对象是否是零值,可能会把代码写成下面。
if containers_count == 0
if fruits_list != []
- 1
- 2
- 3
这种判断语句其实可以变得更简单,因为某个对象作为主角出现在if分支里,解释器会主动对它进行“真值测试”,也就是调用bool()函数获取他的布尔值。
每类对象都有着各自的规则。
布尔值为假:None、0、False、[]、()、{}、set()、frozenset()、等
布尔值为真:非0的数值、True、非空的列表、元组、字典、用户定义的类和实例等等
if not containers_count:
if not fruits_list
- 1
- 2
- 3
2.2.把否定逻辑移入表达式内
在构造布尔逻辑表达式时,你可以用not关键字来表达否定含义。
i > 8
not i>8
- 1
- 2
有时候过于使用not关键字,反倒忘记了运算符本身就可以表达否定逻辑。这样的代码,就好比你在看到一个人沿着楼梯往上走时,不说“他在上楼”,非要说“他在做和下楼相反的事情”
if not number < 10
if not index == 1
- 1
- 2
如果把否定逻辑移入表达式内,他们可以改成下面这样。
if number >= 10
if current_user is not None
- 1
- 2
2.3.尽可能让三元表达式保持简单
除了标准的分支,python提供了浓缩的表达式,三元表达式
# 三元表达式语法
true_value if <expression> else false_value
- 1
- 2
2.4.修改对象的布尔值
class UserCollection:
def __init__(self, users):
self.items = users
users = UserCollection(['xiaoming', 'xiaozhang'])
if len(users.items) > 0:
print(f'这个属性有值:{users.items}')
- 1
- 2
- 3
- 4
- 5
- 6
- 7
上面的示例判断user对象是否真的有内容,因此在分支判断语句用到了len(users.items) > 0 这样的表达式,其实这个可以变得更简单。
只要给UserCollection类实现__len__魔法方法,users对象就可以直接用于“真值测试”
class userCollection_v2:
def __init__(self, users):
self.items = users
# 为类定义len魔法方法,python在计算这类对象的布尔值时,会受len(users)的结果影响,假如长度为0,布尔值为False,反之为True
def __len__(self):
return len(self.items)
users = userCollection_v2(['xiaoming', 'xiaozhang'])
# 不在需要手动判断对象内部items的长度
if users:
print(f'这个属性有值:{users.items}')
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
不过定义len并非影响布尔值结果的唯一办法,还有一个魔法方法__bool__和对象的布尔值息息相关。
为对象定义__bool__方法后,对它进行布尔值运算会直接返回该方法的调用结果。
class ScoreJudger:
def __init__(self, score):
self.score = score
# 仅当分数大于60时为真
def __bool__(self):
return self.score >= 60
if ScoreJudger(60):
print('true')
else:
print('false')
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
如果一个类中同时定义了__len__和__bool__两个方法,解释器会优先使用__bool__方法的执行结果。
2.5.判断对象None时用is运算符
当我们判断两个对象是否相等时,可以使用双等号和is来判断,但是他们是有区别的。因此我们需要了解它的区别在根据场景选择使用哪个方式。
- 双等号对比两个对象的值是否相等,行为可被__eq__方法重载
- is判断两个对象指向的内存地址是否是同一个,无法被重载。当在执行x is y时,其实就是在判断id(x)和id(y)的结果是否相等。
1.双等号计算两个对象是否相等
# is与==
x,y,z = 1, 1, 2
print('x == y的结果:', x == y)
print('x == z的结果:', x == z)
# 重写eq方法
class EqualWithAnything:
def __eq__(self, other):
'''
判断 x == y
:param other: y的值
:return:
'''
return True
foo = EqualWithAnything()
print('重写eq魔法方法后,上面定义的EqualWithAnything对象,在和任何对象做==计算时都会返回True。', foo == None)
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
2.is计算两个对象是否相等
当你想判断对象是否为None时,应该使用is运算符,它的行为不会被重写。
print('is判断对象是否为None', foo is None)
# 输出结果
False
x = None
print('有且仅有真正的None 才能通过is判断', x is None)
# 输出结果
True
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
既然is在进行比较时更严格,为什么不把所有判断都用is来替代那?
这是因为,除了None,True,False这三个内置对象以外,其他类型对象在python中并不是严格以单例模式存在的,即便值一致他们在内存中仍然可以是两个对象。
因此当你需要判断某个对象是否是None,True,False时,使用is,其他情况下使用 ==
2.6.消失的分支
从一份电影数据中提取电影信息,按照评分rating的值把电影划分为S、A、B、C、等级;按照指定顺序输出电影信息。
1.电影分析工具第一个版本
import random
movies = [{'name': '侠客行', 'year': 2008, 'rating': '9'},
{'name': '西游记', 'year': 1983, 'rating': '9'},
{'name': '厨神', 'year': 2010, 'rating': '7'},
{'name': '喜来乐', 'year': 2006, 'rating': '6.9'},
{'name': '康熙来了', 'year': 2021, 'rating': '8'}
]
class Movies:
def __init__(self, name, year, rating):
self.name = name
self.year = year
self.rating = rating
@property
def rank(self):
'''
根据评分对电影分级
-S:8.5分以上
-A: 8~8.5
-B: 7~8
-C: 6~7
-D: 6分以下
'''
rating_num = float(self.rating)
if rating_num >= 8.5:
return 'S'
elif rating_num > 8:
return 'A'
elif rating_num > 7:
return 'B'
elif rating_num > 6:
return 'C'
else:
return 'D'
def get_scorted_movies(movies, sorting_type):
'''
电影列表排序并返回结果
:param movies: 对象列表
:param sorting_type: 排序选项
:return:
'''
if sorting_type == "name":
sorted_movies = sorted(movies, key=lambda movie: movie.name.lower())
elif sorting_type == "rating":
sorted_movies = sorted(movies, key=lambda movie: float(movie.rating), reverse=True)
elif sorting_type == "year":
sorted_movies = sorted(movies, key=lambda movie: movie.year, reverse=True)
elif sorting_type == "random":
sorted_movies = sorted(movies, key=lambda movie: random.random())
else:
raise RuntimeError(f'Unknown sorting type: {sorting_type}')
return sorted_movies
def main():
'''
为了把上面代码串起来,在main函数里实现了接收排序选项,解析电影数据,排序并打印电影列表功能。
:return:
'''
all_sorting_type = ('name', 'rating', 'year', 'random')
sorting_type = input('Please input sorting type:')
if sorting_type not in all_sorting_type:
print('Sorry,"{}" is not a valid sorting type,please choose from'
'"{}",exit now'.format(sorting_type, '/'.join(all_sorting_type))
)
return
# 初始化电影数据对象
movies_items = []
for movie_json in movies:
movie = Movies(**movie_json)
movies_items.append(movie)
# 排序输出电影列表
sorted_movies = get_scorted_movies(movies_items,sorting_type)
for movie in sorted_movies:
print(
f'-[{movie.rank} {movie.name} ({movie.year}) | rating: {movie.rating}]'
)
if __name__ == '__main__':
main()
# 输出结果
Please input sorting type:name
-[S 侠客行 (2008) | rating: 9]
-[C 厨神 (2010) | rating: 7]
-[C 喜来乐 (2006) | rating: 6.9]
-[B 康熙来了 (2021) | rating: 8]
-[S 西游记 (1983) | rating: 9]
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
上面的代码完成了一个小工具,虽然这个工具实现了功能,在他的代码里隐藏着两大段可以简化的条件分支代码,如果使用前档的方式可以使分支消失。
2.使用bisect优化范围类分支判断
第一个需要优化的分支判断是rank方法属性中的分支。
@property
def rank(self):
'''
根据评分对电影分级
-S:8.5分以上
-A: 8~8.5
-B: 7~8
-C: 6~7
-D: 6分以下
'''
rating_num = float(self.rating)
if rating_num >= 8.5:
return 'S'
elif rating_num > 8:
return 'A'
elif rating_num > 7:
return 'B'
elif rating_num > 6:
return 'C'
else:
return 'D'
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
仔细观察这段代码,你会发现它有一个明细的规律,每个if/elif 语句后,都跟着一个评分的分界点,这个分界点把评分分成不同的分段。要优化这段代码,先把所有分界点收集起来,放在一个元组里。
breakpoints = [6, 7, 8, 8.5]
- 1
breakpoints已经是一个排好序的元组,所以我们可以直接使用bisect模块来实现查找功能,bisect是python内置的二分算法模块,他有一个同名函数bisect,可以用来在有序列表里做二分查找。
import bisect
# 用二分查找的容器必须是排好序的
breakpoints = [6, 7, 8, 8.5]
print('使用二分查找算法,根据值返回索引位置:', bisect.bisect(breakpoints, 7))
# 将分界点定义成元组,并引入bisect,之前的分支代码可以简化成下面的样子。
class Movies2:
def __init__(self, name, year, rating):
self.name = name
self.year = year
self.rating = rating
@property
def rank(self):
breakpoints = (6, 7, 8, 8.5)
grades = ('D', 'C', 'B', 'A', 'S')
index = bisect.bisect(breakpoints,float(self.rating))
return grades[index]
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
3.使用字典优化k-v相等类型的分支
在get_sorted_movies()函数里,同样有一段分支代码,这段代码有两个明显的特点。
- 他用到的条件表达式都非常类似,都是对sorting_type做等值判断也就是key==value。
- 每个分支内部逻辑也大同小异,都是调用sorted()函数,只是key和reverse略有不同。
如果有一段条件分支代码同时满足这两个条件,我们就可以使用字典来简化它。
def get_scorted_movies(movies, sorting_type):
'''
电影列表排序并返回结果
:param movies: 对象列表
:param sorting_type: 排序选项
:return:
'''
if sorting_type == "name":
sorted_movies = sorted(movies, key=lambda movie: movie.name.lower())
elif sorting_type == "rating":
sorted_movies = sorted(movies, key=lambda movie: float(movie.rating), reverse=True)
elif sorting_type == "year":
sorted_movies = sorted(movies, key=lambda movie: movie.year, reverse=True)
elif sorting_type == "random":
sorted_movies = sorted(movies, key=lambda movie: random.random())
else:
raise RuntimeError(f'Unknown sorting type: {sorting_type}')
return sorted_movies
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
文章来源: brucelong.blog.csdn.net,作者:Bruce小鬼,版权归原作者所有,如需转载,请联系作者。
原文链接:brucelong.blog.csdn.net/article/details/126570606
- 点赞
- 收藏
- 关注作者
评论(0)