Python学习文档

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 比较运算符重载

​ 比较运算符是指专门用来对比俩个对象的运算符。

方法名相关运算说 明
ltobj < other小于
leobj <= other小于等于
eqobj == other等于
neobj != other不等于
gtobj > other大于
geobj >= 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),执行耗时会随着旅客记录的增加呈现指数增长,不推荐使用。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值