0.基本知识
哈希表
哈希表简介
哈希表(Hash table,也叫散列表),是根据关键码值(Key value)而直接进行访问的数据结构 。也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。这个映射函数叫做散列函数,存放记录的数组叫做散列表。
哈希表是一种数据结构,它提供了快速的插入操作和查找操作,无论哈希表总中有多少条数据,插入和查找的时间复杂度都是为O(1),因为哈希表的查找速度非常快,所以在很多程序中都有使用哈希表,例如拼音检查器。
python的集合、字典都是基于哈希表实现的。
读取excel第三方库
第一章 变量与注释
1.1基础知识
1.1.1变量常见用法
1.变量解包:
data = ['a', 'b', 'c', 'd', 'e']
# 贪婪赋值,v2 = ['b', 'c', 'd']
# 等价于: v1, *v2, v3 = data[0], data[1:-1], data[-1],
v1, *v2, v3 = data
for username, score in [('1', 100), ('2', 99)]:
print(username)
2.单下滑线
# 会忽略_i,作为无意义的占位符
for _i in range(10):
pass
1.1.2 给变量注明类型
# 表明参数类型
def remove_invalid(items: List[int]):
pass
1.1.3变量命名原则
PEP8原则
- 普通变量采用分段式,如max_value;
- 对于常量,全部采用大写字母,使用下划线连接,比如 MAX_VALUE;
- 如果变量只是内部使用,前面加上下划线前缀,如_local_var;
- 如果与python关键字冲突,在变量末尾加上下划线,如class_;
变量描述性要强
在当前业务场景下,根据变量可以明白代表的业务意义。
要匹配类型
-
匹配布尔类型的变量名
用is、has来对变量名进行修饰
变量名 含义 说明 is_superuser 是否是超级用户 是/否 has_errors 有没有错误 有/没有 allow_empty 是否允许空值 允许/不允许 -
匹配int/float类型的变量名
1、释义为数字的单词:port、id、age;
2、使用_id结尾,如user_id;
3、使用length/count开头或结尾的单词,如length_of_username、max_length;
其他技巧:
- 在同一段代码内,不要出现多个相似的变量名,比如同时使用users、user1、user2;
1.1.4注释基础知识
用注释复述代码
不要使用:
# 调用strip()去掉空格
input_string = input_string.strip()
不要使用上面的描述,应该写清楚为什么这么做
# 如果直接把带空格的输入传递到后端处理,可能会造成后端服务崩溃
# 因此使用strip()去掉首尾空格
input_string = input_string.strip()
1.2 案例故事-冒泡排序
from typing import List
# 优化前
def magic_bubble_sort_before(numbers):
"""有魔力的冒泡排序算法,所有的偶数都被认为比奇数大"""
j = len(numbers) - 1
while j > 0:
for i in range(j):
if numbers[i] % 2 == 0 and numbers[i + 1] % 2 == 1:
numbers[i], numbers[i + 1] = numbers[i + 1], numbers[i]
continue
elif (numbers[i + 1] % 2 == numbers[i] % 2) and numbers[i] > numbers[i + 1]:
numbers[i], numbers[i + 1] = numbers[i + 1], numbers[i]
continue
j -= 1
return numbers
def magic_bubble_sort_after(numbers: List[int]):
"""有魔力的冒泡排序算法,所有的偶数都被认为比奇数大
:param numbers: 需要排序的列表,函数将会直接修改原始列表
"""
stop_position = len(numbers) - 1
while stop_position > 0:
for i in range(stop_position):
current, next_ = numbers[i], numbers[i + 1]
current_is_even, next_is_even = current % 2 == 0, next_ % 2 == 0
should_swap = False
# 交换位置的两个条件:
# - 前面是偶数,后面是奇数
# - 前面和后面同为奇数或者偶数,但是前面比后面大
if current_is_even and not next_is_even:
should_swap = True
elif current_is_even == next_is_even and current > next_:
should_swap = True
if should_swap:
numbers[i], numbers[i + 1] = numbers[i + 1], numbers[i]
stop_position -= 1
return numbers
if __name__ == "__main__":
# 把较大的数字放后面,默认所有偶数都比奇数大
a = [23, 32, 1, 3, 4, 19, 20, 2, 4]
print(magic_bubble_sort_after(a))
print(magic_bubble_sort_before(a))
1.3 编程建议
1.3.1 保持变量的一致性
1、在同一个项目中,对于一类事物的称呼不要变动;
2、不要把同一个变量名称指向不同类型的值,需要时重新启用一个新变量;
1.3.2变量定义尽量靠近使用
1、用到的时候再进行定义
1.3.8先写注释,再写代码
在编写代码前,先写清楚逻辑,把关键业务点的注释写完,再开始写代码。
第二章 数值与字符串
2.1 基础知识
2.1.1 数值基础
浮点数精度问题
使用decimal来计算浮点数:
from decimal import Decimal
res = Decimal('0.1') + Decimal('0.2')
2.1.2 布尔值其实也是数字
true和false可以作为1和0来使用
# 计算偶数个数
count = sum(i % 2 == 0 for i in number)
2.1.4 字符串操作方法
1、判断是否只有整数
str.isdigit()
2、一次性替换多个字符
# 替换',.'为',。'
str.partition(',.', ',。')
2.1.5 字符串和字节串
字符串和字节串转换:
# 字符串转字节串
.encode()
# 字节串转字符串
.decode()
open(…)中如果只使用open(‘xx.txt’, ‘w’)来操作文件,会自动获取当前环境下偏好的环境变量
Linux/mac os:UTF-8
windows:utf-8
2.2 案例故事
2.2.1 枚举Enum
from enum import Enum
class Weekday(Enum):
monday = 1
tuesday = 2
wednesday = 3
thirsday = 4
friday = 5
saturday = 6
sunday = 7
print(Weekday.wednesday) # Weekday.wednesday
print(type(Weekday.wednesday)) # <enum 'Weekday'>
print(Weekday.wednesday.name) # wednesday
print(Weekday.wednesday.value) # 3
2.2.2 sql语句处理
1、SQLAlchemy
todo
2、jinja2模版处理字符串
todo
2.2.5 字符串拼接
在python2.2以后,使用+=拼接字符串和使用join拼接字符串效率差不多。
调试
使用dis模块来查看python字节码,方便理解内部原理;
使用timeit模块对python代码进行性能测试;
第三章 容器类型
3.1基础知识
3.1.1 列表常用操作
1、列表推导式
[n * 100 for x in numbers if n % 2 == 0]
3.1.2 可变性
数据类型:
**可变:**列表list、字典dict、集合set()
**不可变:**整数int、浮点数float、字符串str、字节串str、元祖tuple
函数参数传递机制:
-
值传递:
调用函数时,传过去的是变量所指向的对象(值)的拷贝,因此对函数内部变量的任何修改,都不会影响原始变量。
-
引用传递:
调用函数时,传过去的是变量自身的引用(内存地址),因此,修改函数内的变量会直接影响原始变量。
3.1.3常用元组操作
可以用生成器推导式来遍历元祖
tuple((n * 100 for x in numbers if n % 2 == 0))
3.1.5 字典常用操作
1、访问不存在的字典key
try:
rating = movie['rating']
except KeyError:
rating = 0
2、设置默认值dict.setdefault()
不存在key时,把default写入key值中,存在key则返回已存在的值
dict.setdefault(key, default)
3、快速删除字典的值
dict.pop(key, None)
4、字典推导式
d1 = {'foo': 3, 'bar': 4}
{key: value * 10 for key, value in d1.items() if key == 'foo'}
3.1.7 集合
todo
3.1.9 深拷贝和浅拷贝
1、浅拷贝
浅拷贝对于嵌套字典无法生效
import copy
nums = [1, 2, 3, 4]
nums_copy = copy.copy(nums)
nums[2] = 30
# 修改后不再互相影响
# [1, 2, 3, 4]
对列表、元祖进行切片,也会产生浅拷贝的效果
2、深拷贝
新建一个对象,复制后的对象和原来对象id不一样
import copy
copy.deepcopy()
3.2 案例-日志文件分析
todo
3.3 编程建议
3.3.1 用按需返回替代容器
1、生成器yield
def generator(max_number):
for i in range(0, max_number):
if i % 2 == 0:
yield i
def batch_process(items):
for item in items:
# 处理item,可能耗费大量时间
# processed_item = ....
yield processed_item
3.3.2 使用集合判断成员是否 存在
-
使用列表搜索,时间复杂度为O(n)
-
使用集合搜索,集合使用了哈希表数据结构,先计算出哈希值,在去哈希表查询是否存在,时间复杂度为O(1)
字典dict的key in dict。。查询同样非常快,因为采用了哈希表结构实现
3.3.3 掌握如何快速合并字典
1、动态解包
d1 = {'name': 'apple'}
d2 = {'foo': 'bar'}
d3 = {**d1, **d2}
用解包来合并list
l1 = [1, 2]
l2 = [3, 4]
l3 = [*l1, *l2]
3.3.5 不要在遍历列表时同步修改
错误代码:
# 代码打算删除所有偶数,但由于在遍历过程中,循环所使用的索引值不断增加,而被遍历的对象numbers里的对象又不断在被删除,长度不断缩短,最终导致列表里的一些成员其实根本就没被遍历到
def remove_even(numbers):
for number in numbers:
if number % 2 == 0:
numbers.remove(number)
numbers = [1, 2, 7, 4, 8, 11]
remove_even(numbers)
print(numbers)
# [1, 7, 8, 11]
正确代码:
numbers = [1, 2, 7, 4, 8, 11]
number = [x for x in numbers if x % 2 != 0]
# [1, 7, 11]
3.3.7 让函数返回NamedTuple
如果需要新增新的返回值,只需要在Address类中新增,已有的函数调用也不会报错
class Address(NamedTuple):
"""地址信息结果"""
country: str
province: str
city: str
def lation_to_address(lat, lon):
country = 'country'
province = 'province'
city = 'city'
return Address(
country=country,
province=province,
city=city
)
lat, lon = '', ''
addr = lation_to_address(lat, lon)
# 通过属性名来使用 addr
# addr.country / addr.provice / addr.city
第四章 条件分支控制流
4.1 基础知识
4.1.1 分支惯用写法
1、不要显式和布尔值进行对比
# 不推荐的写法
if flag == True:
if flag == False:
# 推荐的写法
if flag:
if not flag
2、省略零值判断
数字0的布尔值为False
空列表的布尔值为False
# 更精确:只有为0的时候才会满足条件
if flag == 0:
# 更宽泛:当值为 0、None、空字符串时,都可以满足分子条件
if not flag:
**布尔值为False:**None、0、False、[]、()、{}、set()、frozenset(),等等
**布尔值为True:**非0的数值、True、非空的序列、元组、字典、用户定义的类和实例
4.1.3 与None比较时使用is运算符
== 对比俩个对象的值是否相等,行为可被__eq__方法重载;
is 判断俩个对象是否时内存里的同一个东西,无法被重载;
因此,执行 x is y 时,其实是在判断id(x)和id(y)的结果是否相等,二者是否是一个对象
判断对象是否为None时,应该使用is运算符
x = 6300
y = 6300
x is y
# False
# 因为在内存中是不同的俩个对象
x = 100
y = 100
x is y
# True
# 因为整型驻留,常用的小型整数(-5到256),会缓存在内存里的一个数组里,需要用到时,字节返回缓存中的对象,可以节约内存
仅当需要判断某个对象是否是 None、True、False时,使用is,其他情况下,使用==。
4.2 案例故事
使用内置的二分法来查询数据,优化大段if else
import bisect
def rank():
# 已经排好序的评级分界点
breakpoints = (6, 7, 8, 8.5)
# 各评分分区间级别名
grades = ('D', 'C', 'B', 'A', 'S')
index = bisect.bisect(breakpoints, float(rating))
return grade[index]
4.3 编程建议
4.3.1 尽量避免多层分支嵌套
4.3.2 别写太复杂的条件表达式
4.3.3 尽量降低分支内代码的相似性
4.3.4 使用“德摩根定律”
not A or not B 等价于 not (A and B)
4.3.5 使用all()/any()函数构建条件表达式
**all(iterable)😗*仅当iterable中所有成员的布尔值都为真时返回True,否则返回False;
**any(iterable)😗*只要iterable中任何一个成员的布尔值为真就返回True,否则返回False;
4.3.6 留意and 和or 的运算优先级
and优先级高于 or
4.3.7 避开 or 运算符的陷阱
# 左边满足后不会执行右边的逻辑
True or (1/0)
第五章 异常与错误处理
5.1基础知识
5.1.1 优先使用异常捕获
直接进行操作,通过try/expect来捕获异常
大量使用异常捕获在性能方面无影响
5.1.2 try语句常用知识
1、把更精确的expect语句放在前面
异常类派生关系顺序:
BaseException -> Exception -> LookupError -> KeyError
try:
pass
except Exception as e:
pass
else:
pass
5.1.4 启用上下文管理器
1、用于替代finally 语句清理资源
# 使用上下文管理器创建连接,结束后会自动调用close()
with create_conn_obj(host, port, timeout=None) as conn:
try:
conn.send_text('hello world')
except Exception as e:
print('xxx')
2、用于忽略异常
pass
3、使用contextmanager装饰器
@contextmanager位于内置模块contextlib下,可以吧任何一个生成器函数直接转换为一个上下文管理器。
from contextlib import contextmanager
@contextmanager
def create_conn_obj(host, port, timeout=None)
"""创建对象,并在退出上下文时自动关闭"""
conn = create_conn(host, port, timeout=timeout)
try:
# 以yidld关键字为界,yield前的逻辑会在进入管理器时执行(类似于__enter__),yield后的逻辑会在退出管理器时执行(类似于__exit__)
yield conn
# 如果要在上下文管理器内处理异常,必须用try语句块包裹yield语句
finally:
conn.cloase()
5.3 编程建议
5.3.1不要忽略异常
5.3.2 不要手工做数据校验
from pydantic import BaseModel
class NumberInput(BaseModel):
number: conint(ge=0, le=100)
number = input('xx')
number_input = NumberInput(number=number)
5.3.3 抛出可区分的异常
5.3.4 不要使用assert来检查参数合法性
在执行Python的时候可以使用-0选项直接跳过
使用raise在替代assert做参数校验
5.3.5 空对象模式
todo
第六章 循环与可迭代对象
6.1 基础知识
6.1.1 迭代器与可迭代对象
生成器:yield
6.2 案例故事
数字统计任务
读取大文件
1、使用with来读取文件,当文件没有换行符的时候,会一次性生成一个巨大的字符串对象,消耗大量内存
with open('xx.txt') as f:
for line in f:
for i in line:
pass
2、使用while循环加read()读取文件
def count_dights_v2():
“”“计算文件里包含了多少个数字字符,每次读取8kb ”“”
count = 0
block_size = 1024 * 8
with open('xx.txt') as f:
while True:
chunk = f.read(block_size)
# 当文件没有更多内容时,read 调用将会返回空字符串 ‘ ’
if not chunk:
break
for s in chunk:
if s.isdigit():
count += 1
第七章 函数
7.1 基础知识
7.1.1 函数常用技巧
1、别将可变类型作为参数默认值
默认值不要使用list这些可变类型,因为在参数执行过程中修改了默认值,会对之后的函数调用产生影响
2、定义特殊对象来区分是否提供了默认参数
如果默认值为None,则无法判断调用方传递的参数是否为None
3、定义仅限关键字参数
7.1.2 函数返回的常见模式
1、尽量只返回一种类型
函数只返回一种类型,避免调用方出现困惑
2、谨慎返回None值
-
操作类函数的默认返回值
不需要返回值时,返回None
-
意料之中的缺失值
查询类函数未查询到结果时返回None
-
在执行失败时代表“错误”
3、早返回,多返回
根据不同的业务节点,在执行过程中满足返回结果的要求,就直接返回。
def user_get_tweets(user):
if xxx:
return 1
if xxxx:
return 2
...
return 3
7.1.3 常用函数模块 functools
1、functools.partial()
todo
2、functools.lru_cache()
todo
7.2 案例故事
todo
第八章 装饰器
8.1 基础知识
8.1.1 装饰器基础
先跳过>。<
第九章 面向对象编程
9.1 基础知识
9.1.1 类常用知识
1、私有属性
加__代表私有属性,一般使用一个下划线来代表私有属性
2、实例内容都在字典里
实例的__dict__里,保存着当前实例的所有数据;
类的__dict__里,保存着类的文档、方法等所有数据;
9.1.2 内置类方法装饰器
1、类方法@classmethod
用@classmethod装饰器来定义类方法,属于类但是无需通过实例化就可调用。
普通方法接受类实例(self)作为参数,但类方法的第一个参数是类本身,通常使用名字cls
class Duck:
@classmethod
def create_random(cls):
color = random.choice(['yellow', 'white', 'gray'])
return cls(color=color)
d = Duck.create_random()
d.quack()
2、静态方法@staticmethod
用@staticmetod来定义静态方法,表示不需要访问当前实例的其他内容。
class Cat:
def __init__(self, name):
self.name = name
def say(self):
sound = self.get_sound()
pass
@staticmethod
def get_sound():
repeats = random.randrange(1, 10)
return
3、属性装饰器@property
属性代表状态,方法代表行为。属性通过inst.attr的方式来访问,而方法通过inst.method()来调用。
class FilePath:
@property
def basename(self):
return self.path.rsplit(os.sep, 1)[-1]
>>> p = FilePath('/tmp/foo.py')
>>> p.basename
'foo.py'
9.1.3 鸭子类型及其局限性
如果想操作某个对象,不去判断类型,直接判断是不是有你需要的方法。
大型项目不推荐使用
9.1.4 抽象类
todo
9.1.5 多重继承与MRO
Python采用MRO(method resolition order)的算法。该算法会遍历类的所有基类,并将它们安装从高到低进行排序。
class A:
def say(self):
print('I am A')
class B(A):
pass
class C(A):
def say(self):
print('I am C')
class D(B, C):
pass
>>> D.mor()
[<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>]
当调用子类的某个方法时,Python会安装上面的MRO列表从前往后寻找这个方法,加入某个类实现了这个方法,就直接返回。
尽量避免多重继承,应该会导致代码维护困难。
9.1.6 其他知识
1、Mixin模式
2、元类
9.2 案例故事
todo
第十章 面向对象设计原则(上)
10.1 类型注解基础
# type hints
from typing import List
class Duck:
# 给函数加上类型注解
def __init__(self, color: str):
self.color = color
# 通过-> 给返回值加上类型注解
def quack(self) -> None:
print(f"Hi, I'm a {self.color} duck!")
# 可以用typing模块的特殊对象List来标注列表成员的具体类型,注意,这里指的是[]符号,而不是()
def create_random_ducks(number: int) -> List[Duck]:
"""创建一批随机颜色鸭子
:param number: 需要创建的鸭子数量
"""
# 声明变量时,也可以加上类型注解
ducks: List[Duck] = []
for _ in number:
# 类型注解是可选的
color = random.choice(['yellow', 'white', 'gray'])
ducks.append(Duck(color=color))
return ducks
10.2 SRP:单一职责原则
一个类应该只承担一种职责
10.3 OCP:开放-关闭原则
在不修改类的前提下,扩展它的行为
第十一章 面向对象设计原则(下)
11.1 LSP:里式替换原则
LSP认为,所有子类(派生类)对象应该可以任意替换父类(基类)对象使用,且不会破坏程序原本的功能。
11.1.1 子类随便抛出异常
第十二章 数据模型与描述符
12.1 基础知识
12.1.1 字符串魔法方法
1、_repr_
将一个Python对象用字符串形式表现出来时,可以分为俩种场景:
-
非正式
使用print()打印、使用用str()转换为字符串。这种场景下的字符串注重可读性,格式对用户友好。
-
更为正式
一般发生在调试程序时,在调试程序时,常常需要快速获知对象的详细内容,该场景下的字符串注重内容的完整性,由类型的__repr__方法驱动。
2、_format_
定义一个Person对象
class Person:
"""
人
:name: 姓名
:age: 年龄
:favorite_color 最喜欢的颜色
"""
def __init__(self, name, age, favorite_color):
self.name = name
self.age = age
self.favorite_color = favorite_color
# 重构__format__方法
def __format__(self, format_spec):
"""
定义对象在字符串格式化时的行为
:param format_spec: 需要的格式,默认为''
:return:
"""
if format_spec == 'verbose':
return f"{self.name}({self.age})[{self.favorite_color}]"
elif format_spec == 'simple':
return f"{self.name}{self.age}"
return self.name
上面的代码给Person类新增了__format__重构方法,并实现了不同的字符串变现形式。
可以在字符串模板里使用{variable:format_spec}
语法,来触发这些不同的字符串格式。
>>> p = Persion('piglei', 18, 'black')
>>> print('{p:verbose}'.format(p=p))
piglei(18)[black]
>>> print('{p:simple}'.format(p=p))
piglei(18)
>>> print('{p}'.format(p=p))
piglei
12.1.2 比较运算符重载
比较运算符是指专门用来对比俩个对象的运算符。
方法名 | 相关运算 | 说 明 |
---|---|---|
lt | obj < other | 小于 |
le | obj <= other | 小于等于 |
eq | obj == other | 等于 |
ne | obj != other | 不等于 |
gt | obj > other | 大于 |
ge | obj >= other | 大于等于 |
class Square:
"""
正方形
:param length: 边长
"""
def __init__(self.length):
self.length = length
def area(self):
# 平方
return self.length ** 2
有俩个边长一样的正方形x和y,在进行等于运算的时候,会返回下面的结果:
>>> x = Square(4)
>>> y = Square(4)
>>> x == y
False
因为在进行运算的时候,是在对比它俩在内存里的地址(通过id()函数获取),因此,俩个不同对象的运算结果肯定是False。
使用@total_ordering
@total_ordering 是functools内置模块下的一个装饰器。它的功能是让重载比较运算符变得更简单。
12.1.3 描述符
1、无描述符时,实现属性校验功能
class Person:
"""
人
:name: 姓名
:age: 年龄
:favorite_color 最喜欢的颜色
"""
def __init__(self, name, age, favorite_color):
self.name = name
self.age = age
self.favorite_color = favorite_color
@property
def age(self):
return self._age
# 对属性值进行设置
@age.setter
def age(self, value):
"""设置年龄,只允许 0-150 之间的数值"""
try:
value = int(value)
except (TypeError, ValueError):
raise ValueError('value is not a valid integer!')
if not (0 < value < 150):
raise ValueError('value must between 0 and 150!')
self._age = value
2、描述符简介
描述符(descriptor)是Python对象模型里的一种特殊协议,它主要和4个魔法方法有关:__get__
、__set__
、__delete__
、__set_name__
描述符描述了Python获取与设置一个类(实例)成员的整个过程。
3、用描述符实现属性校验功能
4、使用__set_name__方法
12.2 案例故事-处理旅游数据
需求
找出去过吉普岛但没去过新西兰的人
测试数据
users_visited_puket = [
{
"first_name": "Sirena",
"last_name": "Gross",
"phone_number": "650-568-0388",
"date_visited": "2018-03-14",
},
{
"first_name": "James",
"last_name": "Ashcraft",
"phone_number": "412-334-4380",
"date_visited": "2014-09-16",
},
{
"first_name": "Melissa",
"last_name": "Dubois",
"phone_number": "630-225-8829",
"date_visited": "2019-01-04",
},
{
"first_name": "Albert",
"last_name": "Potter",
"phone_number": "702-249-3714",
"date_visited": "2014-03-18",
},
{
"first_name": "Marcel",
"last_name": "May",
"phone_number": "315-794-3895",
"date_visited": "2012-12-12",
},
]
users_visited_nz = [
{
"first_name": "Justin",
"last_name": "Malcom",
"phone_number": "267-282-1964",
"date_visited": "2011-03-13",
},
{
"first_name": "Albert",
"last_name": "Potter",
"phone_number": "702-249-3714",
"date_visited": "2013-09-11",
},
{
"first_name": "James",
"last_name": "Ashcraft",
"phone_number": "412-334-4380",
"date_visited": "2009-04-18",
},
{
"first_name": "Marcel",
"last_name": "May",
"phone_number": "938-121-9321",
"date_visited": "2016-07-12",
},
{
"first_name": "Barbara",
"last_name": "Davis",
"phone_number": "716-801-3922",
"date_visited": "2018-03-12",
},
]
第一次尝试-直接遍历字典
def find_potential_customers_v1():
"""找到去过普吉岛但是没去过新西兰的人
:return: 通过 Generator 返回符合条件的旅客记录
"""
for puket_record in users_visited_puket:
is_potential = True
for nz_record in users_visited_nz:
if (
puket_record['first_name'] == nz_record['first_name']
and puket_record['last_name'] == nz_record['last_name']
and puket_record['phone_number'] == nz_record['phone_number']
):
is_potential = False
break
if is_potential:
yield puket_record
for record in find_potential_customers_v1():
print(record['first_name'], record['last_name'], record['phone_number'])
缺点:简单粗暴,但是效率低,函数时间复杂度为O(n*m),执行耗时会随着旅客记录的增加呈现指数增长,不推荐使用。