python

225 阅读14分钟

Python 原生的数据类型主要包括以下几种:

  1. 数字类型(Number):包括整数(int)、浮点数(float)、复数(complex)。

  2. 字符串类型(String):由零个或多个字符组成的有序字符序列,用单引号(')或双引号(")括起来。

  3. 列表类型(List):由一组有序的值组成,用中括号([ ])括起来,各个值之间用逗号分隔。

  4. 元组类型(Tuple):与列表类似,也由一组有序的值组成,但是它一旦创建,元素就不能修改了,用小括号(( ))括起来,各个值之间用逗号分隔。

  5. 集合类型(Set):由一个或多个唯一的、不可变的对象组成的无序集合,用大括号({ })括起来,各个对象之间用逗号分隔。

  6. 字典类型(Dictionary):由键值对组成的无序集合,用大括号({ })括起来,键值对之间用冒号(:)分隔,各个键值对之间用逗号分隔。

除了这些常见的数据类型之外,Python 还提供了一些其他的数据类型,比如布尔型(bool)、空值(NoneType)等。


1.关于数字类型的一些基本操作如下

在 Python 中,数字类型包括整数(int)、浮点数(float)和复数(complex)。以下是数字类型常见的操作:

  1. 数字运算:加法(+)、减法(-)、乘法(*)、除法(/)、整除(//)、取余(%)等等。

  2. 数字比较:等于(==)、不等于(!=)、大于(>)、小于(<)、大于等于(>=)、小于等于(<=)等等。

  3. 数字转换:可以将整数、浮点数和字符串类型相互转换,如将整数转换为浮点数(float())、将浮点数转换为整数(int())、将字符串转换为数字(int()或float())等等。

  4. 数字函数:Python 提供了许多用于处理数字的内置函数,如绝对值(abs())、最大值(max())、最小值(min())、四舍五入(round())等等。

  5. 数字常量:Python 还定义了一些常用的数字常量,如圆周率(math.pi)、自然对数的底数(math.e)等等,可以通过导入 math 模块来使用它们。

示例代码:

# 数字运算
a = 10
b = 3
print(a + b)  # 13
print(a - b)  # 7
print(a * b)  # 30
print(a / b)  # 3.3333333333333335
print(a // b)  # 3
print(a % b)  # 1

# 数字比较
x = 5
y = 7
print(x == y)  # False
print(x != y)  # True
print(x > y)  # False
print(x < y)  # True
print(x >= y)  # False
print(x <= y)  # True

# 数字转换
c = 3.14
d = int(c)
print(d)  # 3
e = str(d)
print(e)  # '3'
f = float(e)
print(f)  # 3.0

# 数字函数
print(abs(-5))  # 5
print(max(1, 2, 3))  # 3
print(min(1, 2, 3))  # 1
print(round(3.14159, 2))  # 3.14

# 数字常量
import math
print(math.pi)  # 3.141592653589793
print(math.e)  # 2.718281828459045

注意:在 Python 3 中,除法运算符(/)得到的结果是浮点数类型,即使是整数相除也是如此。如果需要得到整数类型的结果,可以使用整除运算符(//)。


关于列表的一些常用操作

在 Python 中,列表是一种有序的可变序列类型,可以存储多个值,每个值可以是任意类型的对象。以下是列表类型常见的操作:

  1. 创建列表:可以使用中括号([])或 list() 函数来创建列表,例如 a = [1, 2, 3]b = list(range(5))

  2. 访问元素:可以使用索引(从 0 开始)或切片来访问列表中的元素,例如 a[0] 表示访问列表 a 中的第一个元素,a[1:3] 表示访问列表 a 中的第二个和第三个元素。

  3. 修改元素:可以使用索引或切片来修改列表中的元素,例如 a[0] = 0 表示将列表 a 中的第一个元素修改为 0。

  4. 添加元素:可以使用 append() 方法向列表末尾添加一个元素,或使用 insert() 方法在任意位置插入一个元素,例如 a.append(4)a.insert(1, 5)

  5. 删除元素:可以使用 del 关键字或 remove() 方法删除列表中的元素,例如 del a[0]a.remove(2)

  6. 列表方法:Python 提供了许多用于处理列表的内置方法,如排序(sort())、反转(reverse())、计数(count())、查找(index())等等。

  7. 列表复制:可以使用切片或 copy() 方法复制一个列表,例如 b = a[:]c = a.copy()

示例代码:

# 创建列表
a = [1, 2, 3]
b = list(range(5))
print(a)  # [1, 2, 3]
print(b)  # [0, 1, 2, 3, 4]

# 访问元素
print(a[0])  # 1
print(b[1:3])  # [1, 2]

# 修改元素
a[0] = 0
print(a)  # [0, 2, 3]

# 添加元素
a.append(4)
print(a)  # [0, 2, 3, 4]
a.insert(1, 5)
print(a)  # [0, 5, 2, 3, 4]

# 删除元素
del a[0]
print(a)  # [5, 2, 3, 4]
a.remove(2)
print(a)  # [5, 3, 4]注意这里删除元素,如果有两个元素2只会删除一个

# 列表方法
a.sort()
print(a)  # [3, 4, 5]
a.reverse()
print(a)  # [5, 4, 3]
print(a.count(4))  # 1
print(a.index(3))  # 2

# 列表复制
b = a[:]
print(b)  # [5, 4, 3]
c = a.copy()
print(c)  # [5, 4, 3]

注意:列表是可变类型,因此在修改列表时会改变原列表的值。


关于字符串的一些常用操作

在 Python 中,字符串是一种不可变的序列类型,用于存储文本数据。以下是字符串类型常见的操作:

  1. 创建字符串:可以使用单引号、双引号或三引号(用于多行字符串)来创建字符串,例如 s = 'hello's = "world"

  2. 访问字符:可以使用索引(从 0 开始)或切片来访问字符串中的字符,例如 s[0] 表示访问字符串 s 中的第一个字符,s[1:3] 表示访问字符串 s 中的第二个和第三个字符。

  3. 字符串拼接:可以使用加号(+)或 join() 方法来拼接字符串,例如 s1 = 'hello' + 'world's2 = ''.join(['hello', 'world'])

  4. 字符串格式化:可以使用格式化字符串或 format() 方法来将值插入字符串中,例如 s3 = f'My name is {name}'s4 = 'My age is {}'.format(age)

  5. 字符串方法:Python 提供了许多用于处理字符串的内置方法,如转换大小写(upper()、lower())、替换(replace())、分割(split())、查找(find())等等。

  6. 字符串判断:可以使用 in 关键字或 startswith()、endswith() 方法判断一个字符串是否包含另一个字符串,例如 'l' in ss.startswith('he')

示例代码:

# 创建字符串
s = 'hello'

# 访问字符
print(s[0])  # 'h'
print(s[1:3])  # 'el'

# 字符串拼接
s1 = 'hello' + 'world'
print(s1)  # 'helloworld'
s2 = ''.join(['hello', 'world'])
print(s2)  # 'helloworld'

# 字符串格式化
name = 'Alice'
age = 18
s3 = f'My name is {name}'
print(s3)  # 'My name is Alice'
s4 = 'My age is {}'.format(age)
print(s4)  # 'My age is 18'

# 字符串方法
print(s.upper())  # 'HELLO'
print(s.replace('l', 'L'))  # 'heLLo'
print(s.split('l'))  # ['he', '', 'o'] 因为l作为分隔符,两个l中间没有值,所以是空值
print(s.find('l'))  # 2  返回的是找的参数的index

# 字符串判断
print('l' in s)  # True
print(s.startswith('he'))  # True
print(s.endswith('lo'))  # True

注意:由于字符串是不可变类型,因此在修改字符串时需要重新创建一个新的字符串对象。如果需要修改字符串中的某个字符,可以先将字符串转换为列表类型进行修改,再将列表转换回字符串类型。例如:

s = 'hello'
# 修改第一个字符为 'H'
s = 'H' + s[1:]
print(s)  # 'Hello'

# 将字符串转换为列表进行修改
lst = list(s)
lst[0] = 'h'
s = ''.join(lst)
print(s)  # 'hello

关于元组的一些常用操作

  1. 创建元组:使用小括号 ()tuple() 函数可以创建一个元组。

  2. 访问元素:使用索引操作符 [] 可以访问元组中的元素,索引从 0 开始计数。

  3. 切片操作:可以使用切片操作符 : 对元组进行切片,语法为 tuple[start:stop:step],其中 start 表示切片起始位置,stop 表示切片结束位置(不包括该位置),step 表示步长。

  4. 拼接元组:可以使用加号 + 将两个元组拼接成一个新元组。

  5. 元组长度:使用 len() 函数可以获取元组中元素的个数。

  6. 元素出现次数:使用 count() 函数可以获取指定元素在元组中出现的次数。

  7. 元素索引:使用 index() 函数可以获取指定元素在元组中第一次出现的索引。

下面是一些示例:

# 创建元组
t1 = ()
t2 = (1, 2, 3)
t3 = tuple([4, 5, 6])

# 访问元素
t = (1, 2, 3)
print(t[0])   # 1
print(t[1])   # 2
print(t[2])   # 3

# 切片操作
t = (1, 2, 3, 4, 5)
print(t[1:4])     # (2, 3, 4)
print(t[::2])     # (1, 3, 5)

# 拼接元组
t1 = (1, 2, 3)
t2 = (4, 5, 6)
t3 = t1 + t2
print(t3)         # (1, 2, 3, 4, 5, 6)

# 元组长度
t = (1, 2, 3, 4, 5)
print(len(t))     # 5

# 元素出现次数
t = (1, 2, 3, 2, 4, 2)
print(t.count(2))  # 3

# 元素索引
t = (1, 2, 3, 2, 4, 2)
print(t.index(2))  # 1

关于集合的常用操作

  1. 创建集合:使用大括号 {}set() 函数可以创建一个集合。注意,使用大括号创建空集合时,需要使用 set() 函数。

    s1 = {1, 2, 3}            # 创建集合
    s2 = set([4, 5, 6])       # 使用 set() 函数创建集合
    s3 = set()                # 创建空集合
    
  2. 添加元素:使用 add() 方法可以向集合中添加一个元素,使用 update() 方法可以向集合中添加多个元素。

    s = {1, 2, 3}
    s.add(4)                # 添加一个元素
    s.update({5, 6, 7})     # 添加多个元素
    print(s)                # {1, 2, 3, 4, 5, 6, 7}
    
  3. 删除元素:使用 remove()discard() 方法可以从集合中删除一个元素,区别在于当要删除的元素不存在时,remove() 方法会抛出异常,而 discard() 方法不会。

    s = {1, 2, 3, 4, 5}
    s.remove(3)     # 删除元素 3
    s.discard(4)    # 删除元素 4
    print(s)        # {1, 2, 5}
    
  4. 清空集合:使用 clear() 方法可以清空集合中的所有元素。

    s = {1, 2, 3}
    s.clear()        # 清空集合
    print(s)         # set()
    
  5. 判断元素是否在集合中:可以使用 in 关键字或 not in 关键字来判断元素是否在集合中。

    s = {1, 2, 3}
    print(2 in s)    # True
    print(4 not in s)  # True
    
  6. 集合运算:可以使用交集、并集、差集、对称差集等运算符和方法对集合进行运算。

    s1 = {1, 2, 3}
    s2 = {3, 4, 5}
    # 交集
    print(s1 & s2)      # {3}
    print(s1.intersection(s2))  # {3}
    # 并集
    print(s1 | s2)      # {1, 2, 3, 4, 5}
    print(s1.union(s2))  # {1, 2, 3, 4, 5}
    # 差集
    print(s1 - s2)      # {1, 2}
    print(s1.difference(s2))  # {1, 2}
    # 对称差集
    print(s1 ^ s2)      # {1, 2, 4, 5}
    print(s1.symmetric_difference(s2))  # {1, 2, 4, 5}
    

    差集指的是只属于一个集合而不属于另一个集合的元素集合,而对称差集则是两个集合中不同的元素的集合。因此,两者的区别在于差集只包含一个集合中独有的元素,而对称差集则包含两个集合中互不相同的元素

7.利用set给列表去重

l = [1, 2, 3, 4, 1, 3]
s = set(l)
l = list(s)
print(l) # [1, 2, 3, 4]

Python的字典(dictionary)是一种无序的可变容器,用于存储键值对(key-value pairs)。 字典的键可以是任何不可变的对象(immutable objects),如整数、浮点数、字符串、元组等,而值可以是任何对象。这里需要注意的是,因为键必须是不可变的,所以不能使用列表、集合等可变对象作为键。如果尝试使用可变对象作为键,则会抛出TypeError异常。

以下是Python字典的一些常用操作:

  1. 创建字典:可以使用花括号{}或者dict()函数创建一个空字典,也可以直接在花括号中添加键值对创建一个非空字典。
empty_dict = {}
empty_dict = dict()

my_dict = {'apple': 1, 'banana': 2, 'orange': 3}
  1. 访问字典中的值:使用键访问字典中的值,如果键不存在则会抛出KeyError异常。
my_dict = {'apple': 1, 'banana': 2, 'orange': 3}

print(my_dict['apple']) # 输出1

# 使用get()方法可以避免KeyError异常,如果键不存在则返回默认值(默认为None)
print(my_dict.get('pear')) # 输出None
print(my_dict.get('pear', 0)) # 输出0
  1. 添加或修改键值对:可以使用赋值语句或者update()方法添加或修改键值对。
my_dict = {'apple': 1, 'banana': 2, 'orange': 3}

my_dict['pear'] = 4 # 添加一个键值对

my_dict['banana'] = 5 # 修改一个键的值

my_dict.update({'grape': 6, 'pineapple': 7}) # 添加多个键值对
  1. 删除键值对:可以使用del语句或者pop()方法删除一个键值对。
my_dict = {'apple': 1, 'banana': 2, 'orange': 3}

del my_dict['orange'] # 删除一个键值对

my_dict.pop('banana') # 删除一个键值对并返回对应的值
  1. 判断键是否存在:可以使用in关键字判断一个键是否存在于字典中。
my_dict = {'apple': 1, 'banana': 2, 'orange': 3}

print('apple' in my_dict) # 输出True
print('pear' in my_dict) # 输出False
  1. 获取字典的键、值或键值对:可以使用keys()、values()或者items()方法获取字典的键、值或键值对。
my_dict = {'apple': 1, 'banana': 2, 'orange': 3}

print(my_dict.keys()) # 输出dict_keys(['apple', 'banana', 'orange'])
print(my_dict.values()) # 输出dict_values([1, 2, 3])
print(my_dict.items()) # 输出dict_items([('apple', 1), ('banana', 2), ('orange', 3)])
  1. 清空字典:可以使用clear()方法清空字典。
my_dict = {'apple': 1, 'banana': 2, 'orange': 3}

my_dict.clear() # 清空字典

8.遍历字典

my_dict = {'key1': 1, 'key2': 2, 'key3': 3}
for key in my_dict:
    print(key)
    
for value in my_dict.values(): 
    print(value)
    
 
for key, value in my_dict.items(): 
    print(key, value)


单例模式的实现

用装饰器实现

from functools import wraps

def singleton(cls):
    """单例类装饰器"""
    instances = {}

    @wraps(cls)
    def wrapper(*args, **kwargs):
        if cls not in instances:
            instances[cls] = cls(*args, **kwargs)
        return instances[cls]

    return wrapper


@singleton
class President:
    pass

用类变量实现

class Singleton:
    __instance = None
    
    def __new__(cls):
        if cls.__instance is None:
            cls.__instance = super().__new__(cls)
        return cls.__instance

在Python中,可以通过定义一个类级别的变量来实现单例模式,该变量用于存储唯一的实例,在这个实现中,我们使用了类级别的变量 __instance 来存储唯一的实例。当需要创建新实例时,我们首先检查 __instance 是否为 None,如果是,则创建新实例并将其分配给 __instance,否则返回已有的实例。这种实现确保了在整个应用程序中只有一个实例存在。 这样创建的每一个类对象的id都是一样的,注意这里的__instance并不是关键字或者固定不变的,只要不跟其他变量重复也可以是其他名称

类级别的变量:当我们在类定义中定义一个变量时,它就成为了一个类级别的变量,这意味着它是类的属性,而不是实例的属性

class MyClass:
    class_variable = 42

    def __init__(self, instance_variable):
        self.instance_variable = instance_variable

在这个示例中,class_variable 是一个类级别的变量,它是在类定义中定义的,而不是在实例初始化时创建的。因此,所有的 MyClass 实例都共享相同的 class_variable 变量,而且它们可以通过 MyClass.class_variable 或实例的 self.class_variable 属性来访问它。

另一方面,instance_variable 是一个实例级别的变量,它是在实例初始化时创建的。每个 MyClass 实例都有自己的 instance_variable 变量,这意味着它们可以具有不同的值。


[[1,2],[3,4],[5,6]]一行代码展开该列表,得出[1,2,3,4,5,6]

用列表推导式的方法

a= [[1,2],[3,4],[5,6]]
res = [j for i in a for j in i]

a = (1,) 创建了一个只包含一个元素的元组。这是因为当你在括号中只包含一个元素时,必须在元素后面加上一个逗号,以告诉 Python 这是一个元组

b = (1) 创建了一个整数。这是因为括号在这里被解释为用于表示表达式或函数调用中的参数列表,而不是用于表示元组。因此,b 是整数 1


实现 过滤出列表中所有的奇数

a = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

1.用filter和lambda表达式

res = list(filter(lambda i:i%2==1,a))

2.用列表推导式

res = [i for i in l if i%2==1]

lambda用法

在 Python 中,lambda 是一个关键字,用于创建匿名函数。这种函数没有函数名,通常只在定义它的位置被调用一次,然后被丢弃。

lambda 函数的语法如下:

lambda arguments: expression

其中,arguments 是参数列表,可以包含零个或多个参数,用逗号分隔;expression 是一个表达式,用于计算函数返回值。

以下是一个使用 lambda 函数的简单示例:

# 创建一个 lambda 函数,用于计算两个数的和
sum = lambda x, y: x + y

# 调用 lambda 函数并输出结果
print(sum(3, 5))   # 输出 8

lambda 函数通常在需要传递一个简单的函数作为参数时使用。例如,在 map()filter()reduce() 等函数中,我们可以使用 lambda 创建一个简单的函数来处理序列中的元素。

以下是一个使用 lambda 函数的 map() 示例:

# 使用 lambda 函数对序列中的元素进行平方计算
seq = [1, 2, 3, 4, 5]
squared = map(lambda x: x**2, seq)
print(list(squared))  # 输出 [1, 4, 9, 16, 25]

image.png


image.png


image.png


image.png


项目中安装的第三方包想要统一放到文件中,使用一个命令

pip freeze > requirements.txt

image.png


image.png

image.png

image.png

一般模块里面只放方法的定义,不执行方法,如果执行了,如下图

def func1():
        print("func1()")
    
func1()

这样其他地方import这个模块都会执行func1(),所以一般我们通过__name__来判断当前是执行该文件还是import来确定方法是否执行,import的时候__name__值是这个模块的名字,执行的时候是“main”,我们为了避免import的时候执行代码,可以修改上面代码如下

def func1():
        print("func1()")
        
if __name__ == "__main__":
    func1()

上下文管理器

解决问题:资源泄露

场景:上下文管理器,通常应用在文件的打开关闭和数据库的连接关闭等场景中,可以确保用过的资源得到迅速释放,有效提高了程序的安全性

两种实现:

1.基于类的上下文管理器

示例


class FileManager:
    def __init__(self, name, mode):
        print('calling __init__ method')
        self.name = name
        self.mode = mode 
        self.file = None
        
    def __enter__(self):
        print('calling __enter__ method')
        self.file = open(self.name, self.mode)
        return self.file


    def __exit__(self, exc_type, exc_val, exc_tb):
        print('calling __exit__ method')
        if self.file:
            self.file.close()
            
with FileManager('test.txt', 'w') as f:
    print('ready to write to file')
    f.write('hello world')
    
## 输出
calling __init__ method
calling __enter__ method
ready to write to file
calling __exit__ method

当我们用类来创建上下文管理器时,必须保证这个类包括方法”enter()”和方法“exit()”。其中,方法“enter()”返回需要被管理的资源,方法“exit()”里通常会存在一些释放、清理资源的操作,比如这个例子中的关闭文件等等。

而当我们用 with 语句,执行这个上下文管理器时:


with FileManager('test.txt', 'w') as f:
    f.write('hello world')

下面这四步操作会依次发生:

方法“init()”被调用,程序初始化对象 FileManager,使得文件名(name)是"test.txt",文件模式 (mode) 是'w';

方法“enter()”被调用,文件“test.txt”以写入的模式被打开,并且返回 FileManager 对象赋予变量 f;

字符串“hello world”被写入文件“test.txt”;

方法“exit()”被调用,负责关闭之前打开的文件流。因此,这个程序的输出是:


calling __init__ method
calling __enter__ method
ready to write to file
calling __exit__ meth

方法“exit()”中的参数“exc_type, exc_val, exc_tb”,分别表示 exception_type、exception_value 和 traceback。当我们执行含有上下文管理器的 with 语句时,如果有异常抛出,异常的信息就会包含在这三个变量中,传入方法“exit()” 。你可以自行定义相关的操作对异常进行处理,而处理完异常后,也别忘了加上“return True”这条语句,否则仍然会抛出异常。

2.基于生成器的上下文管理器

可以使用装饰器 contextlib.contextmanager,来定义自己所需的基于生成器的上下文管理器,用以支持 with 语句。还是拿前面的类上下文管理器 FileManager 来说,我们也可以用下面形式来表示:

from contextlib import contextmanager

@contextmanager
def file_manager(name, mode):
    try:
        f = open(name, mode)
        yield f
    finally:
        f.close()
        
with file_manager('test.txt', 'w') as f:
    f.write('hello world')

这段代码中,函数 file_manager() 是一个生成器,当我们执行 with 语句时,便会打开文件,并返回文件对象 f;当 with 语句执行完后,finally block 中的关闭文件操作便会执行。你可以看到,使用基于生成器的上下文管理器时,我们不再用定义“enter()”和“exit()”方法,但请务必加上装饰器 @contextmanager,这一点新手很容易疏忽。

无论你使用哪一种,请不用忘记在方法“exit()”或者是 finally block 中释放资源,这一点尤其重要



l = [1, 2, 3]
l.__sizeof__()
64
tup = (1, 2, 3)
tup.__sizeof__()
48

对列表和元组,我们放置了相同的元素,但是元组的存储空间,却比列表要少 16 字节。

1.由于列表是动态的,所以它需要存储指针,来指向对应的元素(上述例子中,对于 int 型,8 字节)。

2.另外,由于列表可变,所以需要额外存储已经分配的长度大小(8 字节),这样才可以实时追踪列表空间的使用情况,当空间不足时,及时分配额外空间

可以得出结论:元组要比列表更加轻量级一些,所以总体上来说,元组的性能速度要略优于列表

当然,如果你想要增加、删减或者改变元素,那么列表显然更优。原因你现在肯定知道了,那就是对于元组,你必须得通过新建一个元组来完成。

想创建一个空的列表,我们可以用下面的 A、B 两种方式,请问它们在效率上有什么区别吗?我们应该优先考虑使用哪种呢


# 创建空列表
# option A
empty_list = list()

# option B
empty_list = []

区别主要在于list()是一个function call,Python的function call会创建stack,并且进行一系列参数检查的操作,比较expensive,反观[]是一个内置的C函数,可以直接被调用,因此效率高

相比于列表和元组,字典的性能更优,特别是对于查找、添加和删除操作,字典都能在常数时间复杂度内完成。

而集合和字典基本相同,唯一的区别,就是集合没有键和值的配对,是一系列无序的、唯一的元素组合。首先我们来看字典和集合的创建,通常有下面这几种方式:


d1 = {'name': 'jason', 'age': 20, 'gender': 'male'}
d2 = dict({'name': 'jason', 'age': 20, 'gender': 'male'})
d3 = dict([('name', 'jason'), ('age', 20), ('gender', 'male')])
d4 = dict(name='jason', age=20, gender='male') 
d1 == d2 == d3 ==d4
True

s1 = {1, 2, 3}
s2 = set([1, 2, 3])
s1 == s2
True

想要判断一个元素在不在字典或集合内,我们可以用 value in dict/set 来判断


s = {1, 2, 3}
1 in s
True
10 in s
False

d = {'name': 'jason', 'age': 20}
'name' in d
True
'location' in d
False

每个类都有构造函数,继承类在生成对象的时候,是不会自动调用父类的构造函数的

抽象类是一种特殊的类,它生下来就是作为父类存在的,一旦对象化就会报错。同样,抽象函数定义在抽象类之中,子类必须重写该函数才能使用。相应的抽象函数,则是使用装饰器 @abstractmethod 来表示。


from abc import ABCMeta, abstractmethod

class Entity(metaclass=ABCMeta):
    @abstractmethod
    def get_title(self):
        pass

    @abstractmethod
    def set_title(self, title):
        pass

class Document(Entity):
    def get_title(self):
        return self.title
    
    def set_title(self, title):
        self.title = title

document = Document()
document.set_title('Harry Potter')
print(document.get_title())

entity = Entity()

########## 输出 ##########

Harry Potter

---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-7-266b2aa47bad> in <module>()
     21 print(document.get_title())
     22 
---> 23 entity = Entity()
     24 entity.set_title('Test')

TypeError: Can't instantiate abstract class Entity with abstract methods get_title, set_title

比较操作符'is'效率优于'==',因为'is'操作符无法被重载,执行'is'操作只是简单的获取对象的 ID,并进行比较;而'=='操作符则会递归地遍历对象的所有值,并逐一比较。在 Python 中,每个对象的身份标识,都能通过函数 id(object) 获得。因此,'is'操作符,相当于比较对象之间的 ID 是否相等

浅拷贝中的元素,是原对象中子对象的引用,因此,如果原对象中的元素是可变的,改变其也会影响拷贝后的对象,存在一定的副作用

pip install 可以把项目需要的所有第三方包都写在一个文件里面,通常是requirements.txt文件,然后一键执行安装所有包,安装的时候可能会下载很慢,这个时候设置国内清华园代理,如下,这种是暂时性的,只会在本次下载使用清华园代理

pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple

hasattr 是 Python 内置函数之一,用于检查一个对象是否有指定名称的属性或方法。 hasattr(object, name) 其中,object 是需要检查的对象,name 是一个字符串,表示需要检查的属性或方法的名称。注意这里name是包括属性和方法两个类型的

如果对象有指定名称的属性或方法,则返回 True,否则返回 False。

class MyClass:
    def __init__(self):
        self.x = 10

obj = MyClass()

# 检查对象 obj 是否有属性 x 和方法 foo
if hasattr(obj, 'x'):
    print("obj has attribute 'x'")
else:
    print("obj does not have attribute 'x'")

if hasattr(obj, 'foo'):
    print("obj has method 'foo'")
else:
    print("obj does not have method 'foo'")

输出结果为:

obj has attribute 'x'
obj does not have method 'foo'


一个变量的赋值可以用一句话的形式表示比较优雅,下面判断是否有该属性,如果有就赋值给变量,如果没有就赋值else后面的None

token_data = request.state.token_data if hasattr(request.state, 'token_data') else None

如果方法中有一个参数不是必须的,有可能传None,可以用Optional来表示,不用每次如果是空,还穿一个空字符串,这样不是很准确,没有就用None,这样写着也好看

async def device_connect(self, req_body: DeviceConnectReq, code: Optional[str], token_data: Optional[TokenData], ip: str):

随机获取6位数字

import string,random
''.join(random.choices(string.digits, k=6))

string.digits 是0-9的数字,random.choices返回k个随机数的list,如

['0', '9', '0', '2', '7', '6']

''.join把列表的数字生成字符串

另一种生成6位数字的方式,不确定哪一种方式好

random.randint(100000, 999999)

JSON Web Tokens 由使用 (.) 分开的 3 个部分组成的,这 3 个部分分别是:

  • 头部(Header)
  • 负载(Payload)
  • 签名(Signature)

正是因为上面的组织形式,因此一个 JWT 通常看起如下面的表现形式。

xxxxx.yyyyy.zzzzz

jwt 符号分开的三部分实际是 image.png image.png


从两个变量中选择有值的,如果第一个数有值直接取第一个,没有取第二个

username=req_body.username or req_body.phone

Dict[str, Any]是Python中的一种类型注解,表示一个字典类型,其中键是字符串类型,值可以是任意类型。

具体来说,Dict是一个泛型类型,接受两个参数,第一个参数是键的类型,第二个参数是值的类型。在Dict[str, Any]中,第一个参数是str,表示字典中的键都是字符串类型,第二个参数是Any,表示字典中的值可以是任何类型


第一种用法

**extra是Python中一种字典拆包的写法。在函数调用时,如果传递的参数是一个字典,可以使用**运算符将该字典拆包成多个关键字参数,从而使得函数的参数列表更加灵活。

extra = {'c': 3, 'd': 4}
foo(1, 2, **extra)

这里使用了**运算符将字典extra拆包成关键字参数c=3d=4,从而使得foo函数可以接受任意多个关键字参数。

第二种用法

{**extra}是字典拆包的一种简写方式,它可以用于将一个字典合并到另一个字典中

a = {'x': 1, 'y': 2}
b = {'y': 3, 'z': 4}
c = {**a, **b}
print(c) # 输出 {'x': 1, 'y': 3, 'z': 4}

在这个例子中,我们定义了两个字典ab,然后使用{**a, **b}的形式将它们合并到一个新的字典c中。注意,如果两个字典中存在相同的键,后面的字典会覆盖前面的字典。在上面的例子中,由于b字典中有一个键为y的项,所以它会覆盖a字典中的同名项


@property是Python中的一个装饰器,用于将类中的方法转换为属性调用。在使用@property装饰器后,该方法可以被像属性一样访问,而不是像普通的方法一样需要加上括号调用。

使用@property装饰器可以使得类的接口更加简洁易用,同时可以避免直接访问类的实例变量,从而更好地控制类的内部状态。

class MyClass:
    def __init__(self, x):
        self._x = x
    
    @property
    def x(self):
        return self._x
    
    @x.setter
    def x(self, value):
        self._x = value

在这个例子中,我们定义了一个名为MyClass的类,它包含一个名为x的属性。我们使用@property装饰器将x方法转换为属性调用,从而可以使用obj.x的形式访问x属性


asyncio 实现并发

import asyncio
import time

async def work1():
    start_time = time.perf_counter()
    print("work1 start")
    await asyncio.sleep(1)
    end_time = time.perf_counter()
    print(f"work1 end {end_time-start_time}")

async def work2():
    print("work2 start")
    start_time = time.perf_counter()
    await asyncio.sleep(2)
    end_time = time.perf_counter()
    print(f"work2 end {end_time-start_time}")

async def work3():
    print("work3 start")
    start_time = time.perf_counter()
    await asyncio.sleep(3)
    end_time = time.perf_counter()
    print(f"work3 end {end_time-start_time}")

async def main():
    start_time = time.perf_counter()
    tasks = [asyncio.create_task(task) for task in [work1(),work2(),work3()]]
    await asyncio.gather(*tasks)
    end_time = time.perf_counter()
    print(f" finish {end_time-start_time}")

asyncio.run(main())

Asyncio 工作原理

事实上,Asyncio 和其他 Python 程序一样,是单线程的,它只有一个主线程,但是可以进行多个不同的任务(task),这里的任务,就是特殊的 future 对象。这些不同的任务,被一个叫做 event loop 的对象所控制。你可以把这里的任务,类比成多线程版本里的多个线程。为了简化讲解这个问题,我们可以假设任务只有两个状态:一是预备状态;二是等待状态。所谓的预备状态,是指任务目前空闲,但随时待命准备运行。而等待状态,是指任务已经运行,但正在等待外部的操作完成,比如 I/O 操作。在这种情况下,event loop 会维护两个任务列表,分别对应这两种状态;并且选取预备状态的一个任务(具体选取哪个任务,和其等待的时间长短、占用的资源等等相关),使其运行,一直到这个任务把控制权交还给 event loop 为止。当任务把控制权交还给 event loop 时,event loop 会根据其是否完成,把任务放到预备或等待状态的列表,然后遍历等待状态列表的任务,查看他们是否完成。如果完成,则将其放到预备状态的列表;如果未完成,则继续放在等待状态的列表。而原先在预备状态列表的任务位置仍旧不变,因为它们还未运行。这样,当所有任务被重新放置在合适的列表后,新一轮的循环又开始了:event loop 继续从预备状态的列表中选取一个任务使其执行…如此周而复始,直到所有任务完成。值得一提的是,对于 Asyncio 来说,它的任务在运行时不会被外部的一些因素打断,因此 Asyncio 内的操作不会出现 race condition 的情况,这样你就不需要担心线程安全的问题了。

asyncio.run(coro) 是 Asyncio 的 root call,表示拿到 event loop,运行输入的 coro,直到它结束,最后关闭这个 event loop。事实上,asyncio.run() 是 Python3.7+ 才引入的;

asyncio.gather(*aws, loop=None, return_exception=False),则表示在 event loop 中运行aws序列的所有任务

Asyncio 内部任务切换的损耗,远比线程切换的损耗要小;并且 Asyncio 可以开启的任务数量,也比多线程中的线程数量多得多。