Python查漏补缺

1.冒泡排序

时间复杂度O(n^2)  选择、插入都是

def bubble(data, reverse):
    for i in range(len(data)-1):
        for j in range(len(data)-i-1):
            if data[j] > data[j+1]:
                data[j], data[j+1] = data[j+1], data[j]
    if reverse:
        data.reverse()
    return data

2.快速排序

时间复杂度O(nlog⁡n)

思路先选择一个pivot,然后左右两个指针,不断向中间比较,如果碰到不符合的就交换,直到两个指针位置相等,然后返回相等位置作为分割点,之后利用递归不断进行排序。

#快速排序
def parition(arr, low, high):
    pivot = arr[low]
    while low < high:
        while low < high and arr[high] >= pivot:
            high -= 1
        arr[low], arr[high] = arr[high], arr[low]
        while low < high and arr[low] <= pivot:
            low+=1
        arr[low], arr[high] = arr[high], arr[low]
    return low
    
def quick_sort(arr, low, high):
    if low < high:
        pivot = parition(arr, low, high)
        quick_sort(arr, low, pivot-1)
        quick_sort(arr, pivot+1, high)
    return arr

list = [8, 5, 1, 3, 2, 10, 11, 4, 12, 20]
arr = quick_sort(list, 0, len(list)-1)
print(arr)

3.requests介绍,发起HTTP请求的 强大类库,调用简单,功能强大。

requests 是一个用于发送 HTTP 请求的 Python 库,被广泛用于网络请求、数据抓取、API 调用等任务。它以简单、直观的方式让开发者能够方便地进行各种类型的 HTTP 请求(如 GET、POST、PUT、DELETE 等),并处理服务器返回的响应数据。

3. 读取键盘输入

def forinput():
       input_text = input()

4.enumerate

内置函数,用于将一个可遍历的数据对象(如列表,元组,或字符串)组合为一个索引序列,同时列出数据和数据下标,一般用在for循环中;

for counter, value in enumerate(some_list):
    print(counter, value)

5.正则表达式 re模块

正则表达式(Regular Expression,简称 regex)是一种用于模式匹配和文本处理的强大工具。在 Python 中,通过 re 模块使用正则表达式,可以进行字符串查找、匹配、替换等操作。以下是正则表达式中常用符号和它们的具体用法:

cat log.txt | sort | uniq -c | sort -nr | head -n 5

  • cat log.txt 将文件内容输出到管道。
  • sort 排序,为 uniq 统计相同行做准备。
  • uniq -c 统计每行出现次数。
  • sort -nr 按次数排序,从多到少。
  • head -n 5 显示前 5 行结果。

6.字典转json字符串

dict1 = {'name': '萝卜', 'age': 18}
dict1_new = json.dumps(dict1)
print(dict1_new)
{"name": "\u841d\u535c", "age": 18}

  • {"name": "\u841d\u535c", "age": 18} 是字典转换后的 JSON 字符串。
  • \u841d\u535c萝卜 这个字符串的 Unicode 转义表示。它们对应的汉字是 ,在 JSON 格式中,汉字会被转换为 Unicode 转义序列以确保兼容性。
  • 总结

  • 这段代码的作用是将一个 Python 字典转换为 JSON 字符串,以便于数据交换、存储等。
  • 输出的字符串可以被解析回 Python 字典,也可以被其他编程语言理解,因为 JSON 是一种通用的数据格式。

7.继承

一个类继承自另一个类,可以说是一个子类、派生类,继承自父类、基类

同时获得所有的类成员。继承是我们可以重用代码,还可以更好创建和维护代码;

python支持的继承:

  • 单继承:一个子类类继承自单个基类
  • 多重继承:一个子类继承自多个基类
  • 多级继承:一个子类继承自一个基类,而基类继承自另一个基类
  • 分层继承:多个子类继承自同一个基类
  • 混合继承:两种或两种以上继承类型的组合

8.tuple和list转换

>>> tuple1 = (1, 2, 3, 4)
>>> list1 = list(tuple1)
>>> print(list1)
>>> tuple2 = tuple(list1)
>>> print(tuple2)

9.Python 的断言就是检测一个条件,如果条件为真,它什么都不做;反之它触发一个带可选错误信息的 AssertionError

10.异步非阻塞

同步异步指的是调用者和被调度用者关系

所谓同步,就是发出一个功能调用时,在没有得到结果之前,该调用不会返回,一旦调用返回就得到了结果;

异步和同步相对,调用发出之后就直接返回了,所以没有返回结果,当该异步功能完成后,被调用者可以通过状态、通知或回调来通知调用者;

阻塞非阻塞是线程或进程之间的关系。

阻塞调用是指调用结果返回之前,当前线程会被挂起(如遇到io操作)。调用线程只有在得到结果之后才会返回。函数只有在得到结果之后才会将阻塞的线程激活


非阻塞和阻塞的概念相对应,非阻塞调用指在不能立刻得到结果之前也会立刻返回,同时该函数不会阻塞当前线程

总结

  • 同步 vs 异步:同步等待任务完成才继续,异步不等待任务完成就继续。
  • 阻塞 vs 非阻塞:阻塞会暂停线程等待任务完成,非阻塞不会暂停线程,会继续执行。

11.删除操作系统上的文件

>>> f = open('test.txt', 'w') # f 文件对象
>>> f.close()
>>> os.listdir() # 当前目录下文件
['.idea',
 'test.txt',
 '__pycache__']

>>> os.remove('test.txt')
>>> os.listdir()
['.idea',
 '__pycache__']

12.简述logging模块

python内置标准模块,主要用于输出运行日志,可以设置输出日志的等级、日志保存路径、日志文件回滚等;相比print,具备如下优点:

  • 可以通过设置不同的日志等级,在 release 版本中只输出重要信息,而不必显示大量的调试信息
  • print 将所有信息都输出到标准输出中,严重影响开发者从标准输出中查看其它数据;logging 则可以由开发者决定将信息输出到什么地方,以及怎么输出。

13.字符串字符出现个数

>>> from collections import Counter
>>> str1 = "nihsasehndciswemeotpxc"
>>> print(Counter(str1))
Counter({'s': 3, 'e': 3, 'n': 2, 'i': 2, 'h': 2, 'c': 2, 'a': 1, 'd': 1, 'w': 1, 'm': 1, 'o': 1, 't': 1, 'p': 1, 'x': 1})

14. re.compile

import re

# 编译正则表达式,匹配数字
pattern = re.compile(r'\d+')

# 使用编译后的正则表达式对象进行查找
text = "The numbers are 123, 456, and 789."
matches = pattern.findall(text)

print(matches)  # 输出 ['123', '456', '789']
  • re.compile() 将正则表达式编译成对象,便于高效、可读地执行多次匹配操作。
  • 编译后的对象包含丰富的方法,可以灵活地进行各种正则操作,提高代码的性能和组织性。

15. 捕获异常

16.反转列表,一道题目leetcode,不使用额外空间

import datetime

def dayofyear():
    year = input("请输入年份: ")
    month = input("请输入月份: ")
    day = input("请输入天: ")
    date1 = datetime.date(year=int(year), month=int(month), day=int(day))
    date2 = datetime.date(year=int(year), month=1, day=1)
    return (date1 - date2).days + 1

>>> dayofyear()
请输入年份: >? 2022
请输入月份: >? 5
请输入天: >? 23
143

5.字符编码,字符串也是一种数据类型

8个比特(bit)作为一个字节(byte),所以,一个字节能表示的最大的整数就是255(二进制11111111=十进制255),如果要表示更大的整数,就必须用更多的字节。比如两个字节可以表示的最大整数是65535,4个字节可以表示的最大整数是4294967295

由于计算机是美国人发明的,因此,最早只有127个字符被编码到计算机里,也就是大小写英文字母、数字和一些符号,这个编码表被称为ASCII编码,比如大写字母A的编码是65,小写字母z的编码是122

但是要处理中文显然一个字节是不够的,至少需要两个字节,而且还不能和ASCII编码冲突,所以,中国制定了GB2312编码,用来把中文编进去。

!!!Unicode字符集,这种编码把所有语言都统一到一套编码里,就不会出现乱码问题;

Unicode与ASCII编码区别:ASCII编码是一个字节,而Unicode编码通常是2个

!!UTF-8编码即针对Unicode的编码做了针对性的修改;

搞清楚了ASCII、Unicode和UTF-8的关系,我们就可以总结一下现在计算机系统通用的字符编码工作方式:

在计算机内存中,统一使用Unicode编码,当需要保存到硬盘或者需要传输的时候,就转换为UTF-8编码。

用记事本编辑的时候,从文件读取的UTF-8字符被转换为Unicode字符到内存里,编辑完成后,保存的时候再把Unicode转换为UTF-8保存到文件:

所以你看到很多网页的源码上会有类似<meta charset="UTF-8" />的信息,表示该网页正是用的UTF-8编码。

总结:字符编码方案发展ASCII->Unicode->UTF-8,总的来说Unicode的出现是为了解决ACSII没有中文等其他国家字符的方案,但是使用了Unicode后,虽然补充了其他字符,但是会出现存储空间的问题,就是说即使文本大多数英文但是使用了Unicode那么会比ASCII编码多一倍的存储空间,十分不划算,为了解决这种就引出了可变长编码UTF-8,相当于对不同的字符做了自适应长度编码,这样使用的时候就不会出现Unicode那样编码所需空间全为2倍的情况;

UTF-8适用于保存,传输;读取的时候转成Unicode;

Python字符用Unicode编码;

ord():字符转整数

chr():编码转字符

这段话的意思是解释 Python 中 strbytes 类型的区别:

  • 'ABC' 是一个字符串 (str),表示一串字符。Python 的 str 类型通常是 Unicode 字符串,每个字符可以占用多个字节,这取决于字符编码(如 UTF-8, UTF-16 等)。

  • b'ABC' 是一个字节序列 (bytes),表示一组原始字节数据。它是用字节来存储的,因此每个字符都严格占用一个字节(8 位)。在这种表示法中,每个字符的范围是 0 到 255,对应 ASCII 码的范围。

虽然 str 类型和 bytes 类型在内容上看起来一样(例如 'ABC'b'ABC' 都会显示 ABC),但它们的存储方式和处理方式不同:

  • 'ABC'(str 类型)中的每个字符是 Unicode 字符,通常在 Python 内部是使用多字节编码存储的,比如 UTF-8 编码时,一个字母可能会占用 1 个字节,但其他非 ASCII 字符可能会占用多个字节。

  • b'ABC'(bytes 类型)中的每个字符直接映射为一个字节(例如 ASCII 码)。每个字节严格只占用 1 个字节,因此它的表示更加紧凑。

换句话说,虽然在内容上 'ABC'b'ABC' 看起来相同,但 str 是面向字符的高层次抽象,bytes 是面向字节的低层次抽象。

计算str包含多少个字符,len('ABC')

由于Python源代码也是一个文本文件,所以,当你的源代码中包含中文的时候,在保存源代码时,就需要务必指定保存为UTF-8编码。当Python解释器读取源代码时,为了让它按UTF-8编码读取,我们通常在文件开头写上这两行

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

第一行注释是为了告诉Linux/OS X系统,这是一个Python可执行程序,Windows系统会忽略这个注释;

第二行注释是为了告诉Python解释器,按照UTF-8编码读取源代码,否则,你在源代码中写的中文输出可能会有乱码。

申明了UTF-8编码并不意味着你的.py文件就是UTF-8编码的,必须并且要确保文本编辑器正在使用UTF-8编码。

如果.py文件本身使用UTF-8编码,并且也申明了# -*- coding: utf-8 -*-,打开命令提示符测试就可以正常显示中文:

字符串格式化

  • %2d 表示以至少 2 个字符宽度的形式输出一个整数 (d 代表十进制整数)。数字 3 将被格式化为右对齐,占两个字符的位置。如果数字不足 2 位,则前面补空格。

  • %02d 表示以至少 2 个字符宽度的形式输出一个整数,并且如果不足 2 位,前面补0。数字 1 将被格式化为两位的整数,即 01

6.list And tuple

list

Python内置的一种数据类型是列表:list。list是一种有序的集合,可以随时添加和删除其中的元素。

tuple不可变

7.函数的参数:

 

di 

8.递归 

递归函数的优点是定义简单,逻辑清晰。理论上,所有的递归函数都可以写成循环的方式,但循环的逻辑不如递归清晰。

使用递归函数需要注意防止栈溢出。在计算机中,函数调用是通过栈(stack)这种数据结构实现的,每当进入一个函数调用,栈就会加一层栈帧,每当函数返回,栈就会减一层栈帧。由于栈的大小不是无限的,所以,递归调用的次数过多,会导致栈溢出。可以试试fact(1000)

9.生成器

通过列表生成式,我们可以直接创建一个列表。但是,受到内存限制,列表容量肯定是有限的。而且,创建一个包含100万个元素的列表,不仅占用很大的存储空间,如果我们仅仅需要访问前面几个元素,那后面绝大多数元素占用的空间都白白浪费了。

所以,如果列表元素可以按照某种算法推算出来,那我们是否可以在循环的过程中不断推算出后续的元素呢?这样就不必创建完整的list,从而节省大量的空间。在Python中,这种一边循环一边计算的机制,称为生成器:generator。

10.迭代器

 是的,生成器在每次遇到 yield 时会被阻塞(或说暂停)并保存当前的执行状态,等到下一次调用 __next__() 方法时继续从上次暂停的地方执行。而迭代器__next__() 方法执行完之后不会自动暂停或阻塞,而是继续执行或抛出 StopIteration 异常。

生成器的阻塞机制

  • 当生成器函数执行到 yield 时,它会将当前的值“产出”(yield 出去)并暂停执行。这意味着函数的执行状态(局部变量、代码的当前行等)被保存起来,直到下次调用 __next__() 时继续执行。
  • 生成器的这种暂停机制类似于一种协程的行为,使它非常适合按需生成数据,避免一次性生成大量数据带来的内存开销。

迭代器不会自动阻塞

  • 迭代器是通过手动实现 __next__() 方法来定义每次迭代的逻辑。迭代器的 __next__() 方法会一直执行,直到返回下一个值,或抛出 StopIteration 异常。迭代器的执行过程是线性的,不会像生成器那样在特定的位置暂停。
  • 迭代器执行完一次迭代后会直接返回下一个值,不会在中间的某个地方保存状态并暂停。

11.函数式编程

1.高阶函数

函数和函数调用:变量可以指向函数,函数名也是变量,其实就是指向函数的变量

>>> f = abs
>>> f(-10)
10

 既然变量可以指向函数,函数可以接受变量,那么函数也可以接收函数

小结

把函数作为参数传入,这样的函数称为高阶函数,函数式编程就是指这种高度抽象的编程范式。

2.map和reduce()

map()函数接收两个参数,一个是函数,一个是Iterablemap将传入的函数依次作用到序列的每个元素,并把结果作为新的Iterator返回

r = map(int,[1,2,3,-4,-5])

list(r)

此处返回的r是一个迭代器,是惰性序列需要通过list()函数将整个序列计算出来并返回一个list;

再看reduce的用法。reduce把一个函数作用在一个序列[x1, x2, x3, ...]上,这个函数必须接收两个参数,reduce把结果继续和序列的下一个元素做累积计算,其效果就是:

3.filter()

 注意到filter()函数返回的是一个Iterator,也就是一个惰性序列,所以要强迫filter()完成计算结果,需要用list()函数获得所有结果并返回list。

4.sorted()

返回一个新的list

3.返回函数

这里最关键的说就是闭包,内部函数掉的是外部函数的引用 [lambda x:i*x for i in range(4)]

请再注意一点,当我们调用lazy_sum()时,每次调用都会返回一个新的函数,即使传入相同的参数:

 对外层变量赋值,但是内层函数又没有实现初始化,会报错的因此需要nonlocal x来声明

对,你理解得很准确!每次调用 f()fn() 都会访问并修改 inc() 作用域中保存的那个 x,因为 inc() 的作用域在 fn() 内部函数被返回后依然存在。这个过程确保了每次调用 f() 时,x 都会基于上次的值继续递增。

总的来说,inc() 的作用域持续存在,并且 fn() 持有了对这个作用域的引用,因此每次调用 f() 时,都在对同一个 x 变量进行操作。这就是为什么 x 的值会不断增加的原因。

你可以把这个看作是闭包(closure)的一个经典应用,闭包允许函数 "记住" 它们所在的作用域,即使这个作用域的函数已经执行完毕并返回。

很高兴你理解了这个概念!

4.匿名函数

5.装饰器

 import functools

def log(func):

        @functools.wraps(func)

        def wrapper(*args,**kw):

                print('call %s():' % func.__name__)

                return func(*args,**kw)

        return wrapper

import functools

def log(text):
    def decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kw):
            print('%s %s():' % (text, func.__name__))
            return func(*args, **kw)
        return wrapper
    return decorator

带有参数的装饰器,再嵌套一层;

所以说装饰器就是在不修改原函数本身的基础上额外添加功能,实现就是通过返回函数;

即使用wrapper函数,里面添加额外功能,然后函数返回值是原函数,并且执行流程是:

我们来剖析上面的语句,首先执行log('execute'),返回的是decorator函数,再调用返回的函数,参数是now函数,返回值最终是wrapper函数。

装饰器就是基于闭包实现的,通过接受一个函数作为参数,在内部定义一个新函数来扩展或修改原函数的行为,最总返回新函数;

闭包是指一个函数(通常是内嵌函数)记住了它所在作用域中的变量,即使这个作用域已经结束,该函数依然可以访问这些变量。换句话说,闭包是“带有环境变量的函数对象”。

放到装饰其中,原函数就是变量,内部函数调用这个变量,

装饰器如何使用闭包

当装饰器定义一个内部函数(如上例中的 wrapper),并且该内部函数引用了外部函数(decorator)中的变量时,就形成了闭包。这个闭包会记住 func(被装饰的函数),即使外部的 decorator 函数执行完毕,wrapper 仍然可以访问并调用 func

装饰器中的闭包作用:

  1. 状态保持:装饰器中的闭包可以记住外部函数中的状态变量,并在需要时对其操作。这非常适合扩展函数功能,而不改变函数本身的定义。

  2. 延迟执行:闭包会延迟对装饰器中引用变量的操作,直到闭包被调用时才进行操作。这允许灵活的控制函数的执行时机和行为。  就是Wrapper这个函数名,返回的只是函数名;

6.偏函数

int2 = functools.partial(func,base=?)

就是把某个函数的参数固定;

4.

模块

有点像类的私有成员变量只能通过公有函数来访问 

2.关于模块导入 也是经常碰到的问题

这里提供两种解决方案:1直接再环境变量里面添加

2.通过 sys.path先查看,然后sys.apth.append(path)

5. 面向对象编程OOP,是一种程序设计思想;对象作为程序的基本单元,一个对象包含了数据和操作数据的函数;

面向过程的程序设计将计算机程序看做一系列命令的集合,即一组函数的顺序执行;

面向对象的程序设计将计算机程序看作一组对象的集合,而每个对象都能接收到其他对象发过来的消息,并处理这些消息;计算机程序的执行就是一些列消息在各个对象之间传递;

2.访问限制:有点像C++的private,只能通过方法来访问私有变量;

3.继承和多态

当我们定义一个class的时候,可以从某个现有的class继承,新的class称为子类(Subclass),而被继承的class称为基类、父类或超类(Base class、Super class)。

继承有什么好处?最大的好处是子类获得了父类的全部功能。由于Animial实现了run()方法,因此,DogCat作为它的子类,什么事也没干,就自动拥有了run()方法:

继承的第二个好处需要我们对代码做一点改进。你看到了,无论是Dog还是Cat,它们run()的时候,显示的都是Animal is running...,符合逻辑的做法是分别显示Dog is running...Cat is running...,因此,对DogCat类改进如下:

当子类和父类都存在相同的run()方法时,我们说,子类的run()覆盖了父类的run(),在代码运行的时候,总是会调用子类的run()。这样,我们就获得了继承的另一个好处:多态。

继承:获得父类的全部功能,重写功能

多态:

多态的好处就是,当我们需要传入DogCatTortoise……时,我们只需要接收Animal类型就可以了,因为DogCatTortoise……都是Animal类型,然后,按照Animal类型进行操作即可。由于Animal类型有run()方法,因此,传入的任意类型,只要是Animal类或者子类,就会自动调用实际类型的run()方法,这就是多态的意思:

这个解释更加清楚,正是由于python是动态类型语言不像C++一样使用int a作静态类型,导致了只要对象里面有这个方法,不管是不是依赖于继承都能够实现.sound()输出; 

4.

type()

 isinstance() 支持继承的检查,type不支持

总是优先使用isinstance()判断类型,可以将指定类型及其子类“一网打尽”。

 总结:类和实例、访问限制、继承和多态、获取对象信息、实例属性和类属性

 6.面向对象高级编程

1.__slots__:

通常情况下,上面的set_score方法可以直接定义在class中,但动态绑定允许我们在程序运行的过程中动态给class加上功能,这在静态语言中很难实现。

2.使用@property

装饰器:返回函数的函数 

def fun(f):

        def inner():

                return 100

        return inner

#

@fun 

def fun2():

        return 200

fun = fun(fun2)

print(fun()) #返回100

property 内置函数,与类结合,将类的方法重构成属性,使得实例对象可以像使用属性一样;

@property 是 Python 中的一个内置装饰器,用于将类中的方法转换为属性,使得调用者可以像访问普通属性一样访问该方法,而无需显式调用它。

主要作用:

  • 将方法伪装为属性@property 使得类的方法可以通过点号运算符直接访问,而不需要加括号。
  • 控制属性访问@property 使得可以对属性的访问进行控制,比如在获取属性时执行一些逻辑处理,或者保护属性免于直接修改。
  • 实现 getter 和 setter:通过 @property 和相应的 setter,可以为类的属性定义获取和修改行为。

说明:

  1. @property装饰器:将 radius() 方法转换为属性,当我们访问 c.radius 时,实际上调用的是 radius() 方法。
  2. @radius.setter 装饰器:它允许我们定义 radius 属性的 setter,控制属性的设置行为。例如,c.radius = 10 会调用 setter 方法,在其中可以添加额外的逻辑(如有效性检查)。
  3. @property 只读属性area() 方法没有定义 setter,这意味着 area 是一个只读属性,不能通过 c.area = 100 来修改。

优点:

  1. 隐藏实现细节:使用 @property 可以将类的内部属性封装起来,并通过 getter 和 setter 控制对属性的访问,而无需暴露底层实现。
  2. 提升可读性:类用户可以像访问普通属性一样访问方法,代码更加简洁。
  3. 增强安全性:通过 setter,可以防止属性被非法修改,或者在属性访问时进行检查。

总结:

  • @property 将类中的方法转换为属性,使得可以像访问变量一样调用类的方法。
  • @property 通常和 setter、getter 一起使用,用来控制属性的访问和修改行为,增强封装性与安全性。
class Circle:
    def __init__(self, radius):
        self._radius = radius  # 使用下划线表示内部属性
    
    @property
    def radius(self):
        # 定义 getter 方法,通过 @property 伪装为属性
        return self._radius
    
    @radius.setter
    def radius(self, value):
        # 定义 setter 方法,限制属性的修改行为
        if value < 0:
            raise ValueError("Radius cannot be negative.")
        self._radius = value
    
    @property
    def area(self):
        # 定义只读属性,不定义 setter
        return 3.1416 * (self._radius ** 2)

# 使用 @property 之后,可以像访问属性一样调用方法
c = Circle(5)
print(c.radius)  # 调用 radius() 方法,输出:5
print(c.area)    # 调用 area() 方法,输出:78.54

c.radius = 10    # 调用 setter,修改 _radius 值为 10
print(c.radius)  # 输出:10

# c.area = 100   # 如果尝试修改,只读属性 area 会抛出 AttributeError

 使用@peoperty装饰后,像使用属性一样去调用,但是实际上使用的是@name.setter装饰的函数

就是说实际上用的是类的方法,但是伪装成了属性,因此可以使用属性去调用;

3.多重继承

小结

由于Python允许使用多重继承,因此,MixIn就是一种常见的设计。

只允许单一继承的语言(如Java)不能使用MixIn的设计。

from enum import Enum

class Gender(Enum):

        MALE = 1

        FEMALE = 2

class Student:

def __init__(self):

        self.gender = Gender.MALE

6.元类

动态语言和静态语言最大的区别是,函数和类的定义,不是在编译时定义的,而是在运行时动态创建的(从下面这段代码也可以看出来,在程序运行期间动态创建类)

元类创建类创建实例,元类可以对他产生的类造成限制

作用就是通过元类限制类创建实例过程中的一些操作,因为 

  • 在使用类创建实例时,Python 会检查该类的元类,并调用元类__call__ 方法。
  • 您可以在 __call__ 方法中添加自定义逻辑,以限制类的实例化或修改实例化的行为。
  • 这使得元类成为一个强大的工具,可以在类的创建和使用过程中提供灵活性和控制。

12.错误、调试和测试

1.

有的错误是程序编写有问题造成的,比如本来应该输出整数结果输出了字符串,这种错误我们通常称之为bug,bug是必须修复的。

有的错误是用户输入造成的,比如让用户输入email地址,结果得到一个空字符串,这种错误可以通过检查用户输入来做相应的处理。

还有一类错误是完全无法在程序运行过程中预测的,比如写入文件的时候,磁盘满了,写不进去了,或者从网络抓取数据,网络突然断掉了。这类错误也称为异常,在程序中通常是必须处理的,否则,程序会因为各种问题终止并退出。

Python内置了一套异常处理机制,来帮助我们进行错误处理。

此外,我们也需要跟踪程序的执行,查看变量的值是否正确,这个过程称为调试。Python的pdb可以让我们以单步方式执行代码。

2.调试

import logging

logging.basicConfig(level=logging.INFO) 

import logging

# 设置输出的格式
LOG_FORMAT = "时间:%(asctime)s - 日志等级:%(levelname)s - 日志信息:%(message)s"
# 对logger进行配置——日志等级&输出格式
logging.basicConfig(level=logging.WARNING, format=LOG_FORMAT)

# logging.level(message)创建一条level级别的日志
logging.debug("This is a debug log")
logging.info("This is a info log")
logging.warning("This is a warning log")
logging.error("This is a error log")
logging.critical("This is a critical log")
 

3.单元测试  ?又到了测试理论。。 unittest库用来测试自己写的这个Dict类

13.IO编程,CPU和内存 与 磁盘读取速度相差巨大

第一种是CPU等着,也就是程序暂停执行后续代码,等100M的数据在10秒后写入磁盘,再接着往下执行,这种模式称为同步IO;

另一种方法是CPU不等待,只是告诉磁盘,“您老慢慢写,不着急,我接着干别的事去了”,于是,后续代码可以立刻接着执行,这种模式称为异步IO。

很明显,使用异步IO来编写程序性能会远远高于同步IO,但是异步IO的缺点是编程模型复杂。想想看,你得知道什么时候通知你“汉堡做好了”,而通知你的方法也各不相同。如果是服务员跑过来找到你,这是回调模式,如果服务员发短信通知你,你就得不停地检查手机,这是轮询模式。总之,异步IO的复杂度远远高于同步IO。

1.文件读写

 with open('log.txt', 'r') as f:

        f.read() #一次读取所有可能会爆掉

        f.readlines() #一次读取所有内容并按行返回list

        f.readline() #一次读一行

with open('log.txt' 'w') as f:

        f.write('Hello World')

#如果文件里面已经有内容使用w模式会覆盖

a会追加

3.操作文件和目录

  1. 利用os模块编写一个能实现dir -l输出的程序。
  2. 编写一个程序,能在当前目录以及当前目录的所有子目录下查找文件名包含指定字符串的文件,并打印出相对路径。
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

from datetime import datetime
import os

pwd = os.path.abspath(".")

print("      Size     Last Modified  Name")
print("------------------------------------------------------------")

for f in os.listdir(pwd):
    fsize = os.path.getsize(f)
    mtime = datetime.fromtimestamp(os.path.getmtime(f)).strftime("%Y-%m-%d %H:%M")
    flag = "/" if os.path.isdir(f) else ""
    print("%10d  %s  %s%s" % (fsize, mtime, f, flag))

4.序列化

把变量从内存中变成可存储或传输的过程称之为序列化,在Python中叫pickling,在其他语言中也被称之为serialization,marshalling,flattening等等,都是一个意思。

序列化之后,就可以把序列化后的内容写入磁盘,或者通过网络传输到别的机器上。

反过来,把变量内容从序列化的对象重新读到内存里称之为反序列化,即unpickling。

  1. 序列化(Serialization)

    • 定义:序列化是指将数据结构或对象转换为可传输或存储的格式,通常是字节流或字符串的形式。序列化的结果可以被传输到另一个系统、进程或存储在文件中,而后可以被反序列化以重新构建原始的数据结构或对象。
    • 目的:主要目的是将数据转换为可传输或存储的形式,以便在不同系统之间进行通信或在不同时间点进行数据持久化。
  2. 编码(Encoding)

    • 定义:编码是将数据转换为另一种形式的过程,通常是通过特定的规则或算法将数据从一种形式转换为另一种形式。编码可以是将文本从一种字符集转换为另一种字符集,也可以是将数据从一种数据格式转换为另一种数据格式。
    • 目的:编码的目的是为了在特定的环境中有效地表示数据,通常是为了满足传输、存储或处理数据的需求。例如,将文本数据从 Unicode 编码转换为 UTF-8 编码,以便在网络上传输。
  3. 总结

  4. 序列化主要涉及将数据结构或对象转换为可传输或存储的形式,而编码涉及将数据从一种形式转换为另一种形式,可能涉及字符集、数据格式等方面的转换。
  5. 序列化通常用于在不同系统之间传输数据或在不同时间点持久化数据,而编码用于在特定环境中有效地表示数据
import json

json_obj = '{'name':'Tom', 'age'= 20}'
dic = json.loads(json_obj)
print(dic)
print(type(dic))

with open('log.txt', 'w') as f:
    json.dump(dic,f,ensure_ascii=False)

with open('log.txt', 'r') as f:
    json.load(f)



import pickle

class test:
    
    def func():
        return 

a = test()
a.func

p_str = pickle.dumps(a) #将对象序列化
b = pickle.loads(p_str) #反序列化
#这里的b和原来的a已经不是同一个了
#wb以二进制写入否则会出错
with open('log.txt','wb') as f:
    pickle.dump(a,f)
 
with open('log.txt', 'r') as f:
    pickle.load(f)

第三方库 requests

requests库是Python中一个非常流行的HTTP库,提供了简单易用的API来发送HTTP请求。requests.get()requests.post()是两种常用的请求方法,它们在用法和目的上有所不同。

requests.get()

  • 用法: 用于发送HTTP GET请求。GET请求通常用于从服务器获取数据。
  • 参数:
    • url: 请求的URL。
    • params: 可选参数,字典类型,表示URL中的查询字符串参数。
    • 其他参数如headers, cookies, timeout等。
  • import requests

    response = requests.get('https://api.example.com/data', params={'key': 'value'})

requests.post()

  • 用法: 用于发送HTTP POST请求。POST请求通常用于向服务器发送数据,比如表单提交。
  • 参数:
    • url: 请求的URL。
    • data: 可选参数,可以是字典、字节流或文件,表示要发送的表单数据。
    • json: 可选参数,如果需要发送JSON数据,可以传入一个字典,会自动转换为JSON格式。
    • 其他参数同样适用。

import requests

response = requests.post('https://api.example.com/data', data={'key': 'value'})
 

总结

  • 返回对象 r

    无论是requests.get()还是requests.post(),都会返回一个Response对象,通常命名为r。这个对象包含了许多与请求相关的信息,包括:

  • 状态码: r.status_code,HTTP响应的状态码,比如200表示成功,404表示未找到。
  • 响应内容: r.text,服务器返回的内容,以字符串形式表示。
  • JSON数据: r.json(),如果响应内容是JSON格式,可以直接解析成Python对象。
  • 请求的URL: r.url,实际请求的URL。
  • 响应头: r.headers,包含响应的HTTP头信息。
  • requests.get()用于获取数据,requests.post()用于发送数据。
  • 返回的Response对象提供了访问响应内容和元数据的方法。

19.网络编程

TCP/IP介绍

IP协议负责把数据从一台计算机通过网络发送到另一台计算机。数据被分割成一小块一小块,然后通过IP包发送出去。由于互联网链路复杂,两台计算机之间经常有多条线路,因此,路由器就负责决定如何把一个IP包转发出去。IP包的特点是按块发送,途径多个路由,但不保证能到达,也不保证顺序到达。

IP地址实际上是一个32位整数(称为IPv4),以字符串表示的IP地址如192.168.0.1实际上是把32位整数按8位分组后的数字表示,目的是便于阅读。

IPv6地址实际上是一个128位整数,它是目前使用的IPv4的升级版,以字符串表示类似于2001:0db8:85a3:0042:1000:8a2e:0370:7334

TCP协议则是建立在IP协议之上的。TCP协议负责在两台计算机之间建立可靠连接,保证数据包按顺序到达。TCP协议会通过握手建立连接,然后,对每个IP包编号,确保对方按顺序收到,如果包丢掉了,就自动重发。

许多常用的更高级的协议都是建立在TCP协议基础上的,比如用于浏览器的HTTP协议、发送邮件的SMTP协议等。

一个TCP报文除了包含要传输的数据外,还包含源IP地址和目标IP地址,源端口和目标端口。

TCP编程:socket是网络编程的一个抽象概念,在以往学习计算机网络时发现应用层和传输层之间的协议通过socket套接字实现,通常我们用一个Socket表示“打开了一个网络链接”,而打开一个Socket需要知道目标计算机的IP地址和端口号,再指定协议类型即可。

客户端:大多数连接都是可靠的TCP连接,创建TCP连接时,主动发起的连接的叫客户端,被动响应的是服务端;

举个例子,当我们在浏览器中访问新浪时,我们自己的计算机就是客户端,浏览器会主动向新浪的服务器发起连接。如果一切顺利,新浪的服务器接受了我们的连接,一个TCP连接就建立起来的,后面的通信就是发送网页内容了。

所以,我们要创建一个基于TCP连接的Socket,可以这样做:

import socket

s = socket.socket(socket.AF_INET,socket.SOCK_STREAM)

s.connect(('www.sina.com.cn' 80))

也就是socket.socket创建需要IP协议和TCP协议;

连接需要域名和端口号,才能指定到对应的应用程序

创建Socket时,AF_INET指定使用IPv4协议,如果要用更先进的IPv6,就指定为AF_INET6SOCK_STREAM指定使用面向流的TCP协议,这样,一个Socket对象就创建成功,但是还没有建立连接。

客户端要主动发起TCP连接,必须知道服务器的IP地址和端口号。新浪网站的IP地址可以用域名www.sina.com.cn自动转换到IP地址,但是怎么知道新浪服务器的端口号呢?

答案是作为服务器,提供什么样的服务,端口号就必须固定下来。由于我们想要访问网页,因此新浪提供网页服务的服务器必须把端口号固定在80端口,因为80端口是Web服务的标准端口。其他服务都有对应的标准端口号,例如SMTP服务是25端口,FTP服务是21端口,等等。端口号小于1024的是Internet标准服务的端口,端口号大于1024的,可以任意使用。

服务器

和客户端编程相比,服务器编程就要复杂一些。

服务器进程首先要绑定一个端口并监听来自其他客户端的连接。如果某个客户端连接过来了,服务器就与该客户端建立Socket连接,随后的通信就靠这个Socket连接了。

所以,服务器会打开固定端口(比如80)监听,每来一个客户端连接,就创建该Socket连接。由于服务器会有大量来自客户端的连接,所以,服务器要能够区分一个Socket连接是和哪个客户端绑定的。一个Socket依赖4项:服务器地址、服务器端口、客户端地址、客户端端口来唯一确定一个Socket。

但是服务器还需要同时响应多个客户端的请求,所以,每个连接都需要一个新的进程或者新的线程来处理,否则,服务器一次就只能服务一个客户端了。

我们来编写一个简单的服务器程序,它接收客户端连接,把客户端发过来的字符串加上Hello再发回去。

小结

用TCP协议进行Socket编程在Python中十分简单,对于客户端,要主动连接服务器的IP和指定端口,对于服务器,要首先监听指定端口,然后,对每一个新的连接,创建一个线程或进程来处理。通常,服务器程序会无限运行下去。

同一个端口,被一个Socket绑定了以后,就不能被别的Socket绑定了。

21.访问数据库

程序运行时,数据都在内存。程序终止时,通常都需要将数据保存到磁盘;无论是保存到本地磁盘还是通过网络保存到服务器,都需要将数据写入磁盘;(就像数据在传输过程中使用json更加统一)

如何定义存储格式就是一个大问题

为了便于程序保存和读取数据,而且,能直接通过条件快速查询到指定的数据,就出现了数据库(Database)这种专门用于集中存储和查询的软件。

MySQL是Web世界中使用最广泛的数据库服务器。SQLite的特点是轻量级、可嵌入,但不能承受高并发访问,适合桌面和移动应用。而MySQL是为服务器端设计的数据库,能承受高并发访问,同时占用的内存也远远大于SQLite。

22.WEB开发

1.HTTP协议

在Web应用中,服务器把网页传给浏览器,实际上就是把网页的HTML代码发送给浏览器,让浏览器显示出来。而浏览器和服务器之间的传输协议是HTTP,所以:

  • HTML是一种用来定义网页的文本,会HTML,就可以编写网页;
  • HTTP是在网络上传输HTML的协议,用于浏览器和服务器的通信。

当浏览器读取到新浪首页的HTML源码后,它会解析HTML,显示页面,然后,根据HTML里面的各种链接,再发送HTTP请求给新浪服务器,拿到相应的图片、视频、Flash、JavaScript脚本、CSS等各种资源,最终显示出一个完整的页面。所以我们在Network下面能看到很多额外的HTTP请求。

原来是这样,返回的HTML并不是说包含了所有视频 图片等等,而是包含这些东西的一个网址,得到HTML源码后会继续去根据里面的链接发送HTTP请求给服务器获得相应的资源;

HTTP协议同时具备极强的扩展性,虽然浏览器请求的是http://www.sina.com.cn/的首页,但是新浪在HTML中可以链入其他服务器的资源,比如<img src="http://i1.sinaimg.cn/home/2013/1008/U8455P30DT20131008135420.png">,从而将请求压力分散到各个服务器上,并且,一个站点可以链接到其他站点,无数个站点互相链接起来,就形成了World Wide Web,简称“三达不溜”(WWW)。

也就是说返回的HTML可以将其他服务器的资源贴上,无形之中就将压力分散

2.HTML简介

网页就是HTML,定义了一套语法规则告诉浏览器怎么把一个丰富的页面显示出来;

<html>

<head>

        <title>Hello</title>

<\head>

<body>

        <h1>Hello,world!</h1>

</body>

</html>

HTML文档就是一系列的Tag组成,最外层的Tag是<html>。规范的HTML也包含<head>...</head><body>...</body>(注意不要和HTTP的Header、Body搞混了),由于HTML是富文档模型,所以,还有一系列的Tag用来表示链接、图片、表格、表单等等。

23.异步IO

我们已经知道,CPU的速度远远快于磁盘、网络等IO。在一个线程中,CPU执行代码的速度极快,然而,一旦遇到IO操作,如读写文件、发送网络数据时,就需要等待IO操作完成,才能继续进行下一步操作。这种情况称为同步IO。

在IO操作的过程中,当前线程被挂起,而其他需要CPU执行的代码就无法被当前线程执行了。

因为一个IO操作就阻塞了当前线程,导致其他代码无法执行,所以我们必须使用多线程或者多进程来并发执行代码,为多个用户服务。每个用户都会分配一个线程,如果遇到IO导致线程被挂起,其他用户的线程不受影响。

多线程和多进程的模型虽然解决了并发问题,但是系统不能无上限地增加线程。由于系统切换线程的开销也很大,所以,一旦线程数量过多,CPU的时间就花在线程切换上了,真正运行代码的时间就少了,结果导致性能严重下降。

由于我们要解决的问题是CPU高速执行能力和IO设备的龟速严重不匹配,多线程和多进程只是解决这一问题的一种方法。

另一种解决IO问题的方法是异步IO。当代码需要执行一个耗时的IO操作时,它只发出IO指令,并不等待IO结果,然后就去执行其他代码了。一段时间后,当IO返回结果时,再通知CPU进行处理。

同步异步就相当于串行并行:同步必须等待IO完成才能进行下一步操作,异步不需要等待结果而是去执行其他代码,当IO操作完成后,再通知CPU。

总结:

  • 同步:需要等待 I/O 完成,线程被阻塞。
  • 异步:不需要等待 I/O 完成,线程可以继续执行其他任务。

因此,在异步 I/O 中,线程在执行 I/O 时不会被阻塞,而是能够处理其他任务,提高了效率和响应能力。

看到更加精辟的解释:

老张爱喝茶,废话不说,煮开水。 出场人物:老张,水壶两把(普通水壶,简称水壶;会响的水壶,简称响水壶)。 1 老张把水壶放到火上,立等水开。(同步阻塞) 老张觉得自己有点傻 2 老张把水壶放到火上,去客厅看电视,时不时去厨房看看水开没有。(同步非阻塞) 老张还是觉得自己有点傻,于是变高端了,买了把会响笛的那种水壶。水开之后,能大声发出嘀~~~~的噪音。 3 老张把响水壶放到火上,立等水开。(异步阻塞) 老张觉得这样傻等意义不大 4 老张把响水壶放到火上,去客厅看电视,水壶响之前不再去看它了,响了再去拿壶。(异步非阻塞) 老张觉得自己聪明了。

所谓同步异步,只是对于水壶而言。 普通水壶,同步;响水壶,异步。 虽然都能干活,但响水壶可以在自己完工之后,提示老张水开了。这是普通水壶所不能及的。 同步只能让调用者去轮询自己(情况2中),造成老张效率的低下。

所谓阻塞非阻塞,仅仅对于老张而言。 立等的老张,阻塞;看电视的老张,非阻塞。 情况1和情况3中老张就是阻塞的,媳妇喊他都不知道。虽然3中响水壶是异步的,可对于立等的老张没有太大的意义。所以一般异步是配合非阻塞使用的,这样才能发挥异步的效用。

1. 协程与线程的区别

  • 轻量级:协程是用户态的任务调度机制,切换协程时没有内核态的上下文切换,因此开销较小。相比于线程,协程更为轻量,因为协程的调度是在用户态完成的,不涉及系统调用和内核干预。

  • 协作式调度:协程的切换是协作式的,这意味着协程会在特定的时刻(如遇到 I/O 操作或手动调用await等)主动放弃执行权,交还给调度器。这与线程的抢占式调度不同,线程是由操作系统调度,线程之间的切换是由系统决定的。

  • 没有并发:协程本身是单线程内的执行机制,它们不会同时并行运行,而是通过异步方式让多个任务交替进行。因此,协程可以避免并发问题中常见的竞争条件(race condition)。在单个线程内运行的协程,由于没有真正的并发,不需要使用锁等机制来保护共享资源。

2. 协程的优点

  • 无线程切换开销:因为协程切换是在用户态完成的,不涉及内核上下文切换,因此性能更高,尤其是在 I/O 密集型任务中。

  • 不需要锁:因为协程是单线程内的运行机制,多个协程在同一时间不会真正地并发执行,因此通常不需要使用锁来保护共享资源。这大大简化了并发编程,减少了因加锁带来的复杂性和性能开销。

  • 高效处理 I/O 操作:协程最适合处理 I/O 密集型任务(如网络请求、文件读写等)。通过异步 I/O,协程可以在等待 I/O 的时候执行其他任务,极大提高了任务的执行效率。

3. 协程的局限

  • 单线程限制:协程在单个线程中运行,因此无法利用多核 CPU 来并行执行 CPU 密集型任务。如果需要并行执行,可以结合多线程或多进程的方式。

4. 总结

协程是一种比线程更轻量的并发机制,适用于 I/O 密集型任务,因为它们没有线程切换的开销,且在单线程内执行时不需要加锁。但是协程本身并不是真正的并行执行,而是在单线程内通过异步调度机制来交替运行任务。

是的,正是这样!在异步 I/O 中,线程不会因为 I/O 操作而被阻塞或挂起,而是可以去处理其他任务,但由于操作系统使用的是抢占式调度,线程切换仍然可能发生,具体表现为:

  1. 异步 I/O 的特性
    • 当线程执行 I/O 操作时,它可以继续执行其他任务,而不需要等待 I/O 操作完成。这是异步 I/O 的核心优势——通过让线程不被 I/O 阻塞,可以提高系统的并发性和效率。
  2. 抢占式调度
    • 操作系统会根据优先级和时间片等调度策略,在不同线程之间进行抢占式调度。即便是异步 I/O,操作系统的调度器仍可能在任何时刻暂停当前线程,切换到另一个线程去执行任务。
    • 抢占式调度意味着,当前线程即使没有等待 I/O,也可能在执行其他任务时被操作系统强制暂停,切换到其他线程执行。
  3. 线程切换
    • 当线程在等待 I/O 期间执行其他任务时,操作系统可能决定在该线程处理其他任务的过程中进行线程切换,将 CPU 分配给其他线程。
    • 当 I/O 操作完成时,系统会通过回调、事件循环等机制通知线程。若该线程在被抢占时,必须等到调度器重新分配 CPU 给这个线程,才能继续处理 I/O 操作的结果。
  4. 影响
    • 虽然异步 I/O 避免了线程在等待 I/O 期间被阻塞,但由于抢占式调度的存在,线程在运行其他任务时,仍然可能由于调度器的决定,发生线程切换。这会导致多个线程轮流占用 CPU 的执行时间。

总结:

  • 异步 I/O:线程在发起 I/O 操作后不会被阻塞,可以去处理其他任务,从而提高并发性能。
  • 抢占式调度:线程调度是由操作系统控制的,线程在执行任务时仍可能被抢占,并且发生线程切换。
  • 因此,在异步 I/O 的场景下,虽然线程不会因 I/O 操作被挂起,但线程切换仍可能发生,因为操作系统会根据需要将 CPU 资源分配给其他线程。

你可以把异步 I/O 看作是一种减少阻塞、提高任务并发的机制,但操作系统层面的抢占式调度会继续影响线程的执行。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值