python自学

缩进

转义符,换行

输入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
>>> print('\\\t\\')
\ \
>>> print(r'\\\t\\')
\\\t\\

>>> print('''line1
line2
line3''')
line1
line2
line3

s = input('birth: ') #s是str

变量赋值 str指针,不可变对象

1
2
3
4
5
6
>>> a = 'abc'
>>> b = a.replace('a', 'A')
>>> b
'Abc'
>>> a
'abc'

list,tuple, set

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
len(l)
l.append()
l.pop()
l.indert(1,'a')
l = [1, 'a', [5, 'php'],True]
l[0:3]包前不包后
元组字符串也可以切片



s.add()
s.remove()
s.sort()


& |

dictionary

1
2
3
4
5
6
d = {'Michael': 95, 'Bob': 75, 'Tracy': 85}
d[Bob]=
'Bob' in d
d.get('Bob',-1)//不存在返回-1
for key in d:
print(key)

逻辑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
if <条件判断1>:
<执行1>
elif <条件判断2>:
<执行2>
elif <条件判断3>:
<执行3>
else:
<执行4>


for i in []:
for i in range(10):
d = {'x': 'A', 'y': 'B', 'z': 'C' }
>>> for k, v in d.items():
... print(k, '=', v)

函数

1
2
def a(x,n):
return x*n;

一个函数就可以接收另一个函数作为参数,这种函数就称之为高阶函数。

1
2
def add(x, y, f):
return f(x) + f(y)

特殊功能

切片,迭代……都是不分数据类型包括字符串的

列表生成式:

写列表生成式时,把要生成的元素x * x放到前面,后面跟for循环,就可以把list创建出来

1
2
>>> [x * x for x in range(1, 11) if x % 2 == 0]
[4, 16, 36, 64, 100]

if放在前面必须有else

这是因为for前面的部分是一个表达式,它必须根据x计算出一个结果。因此,考察表达式:x if x % 2 == 0,它无法根据x计算出结果,因为缺少else,必须加上else

1
2
>>> [x if x % 2 == 0 else -x for x in range(1, 11)]
[-1, 2, -3, 4, -5, 6, -7, 8, -9, 10]

运用列表生成式,可以写出非常简洁的代码。例如,列出当前目录下的所有文件和目录名,可以通过一行代码实现:

1
2
3
>>> import os # 导入os模块,模块的概念后面讲到
>>> [d for d in os.listdir('.')] # os.listdir可以列出文件和目录
['.emacs.d', '.ssh', '.Trash', 'Adlm', 'Applications'

列表生成式也可以使用两个变量来生成list:

1
2
3
>>> d = {'x': 'A', 'y': 'B', 'z': 'C' }
>>> [k + '=' + v for k, v in d.items()]
['y=B', 'x=A', 'z=C']

最后把一个list中所有的字符串变成小写:

1
2
3
>>> L = ['Hello', 'World', 'IBM', 'Apple']
>>> [s.lower() for s in L]
['hello', 'world', 'ibm', 'apple']

生成器

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

通过迭代器或生成器,可以创建无限长的序列,并按需逐步生成它们。

要创建一个generator,有很多种方法。第一种方法很简单,只要把一个列表生成式的[]改成(),就创建了一个generator:

next()函数获得generator的下一个返回值:

1
2
3
4
5
6
7
8
9
g = (x * x for x in range(10))
>>> g
<generator object <genexpr> at 0x1022ef630>

>>> next(g) #基本不用
0

for n in g:
print(n)

fib函数实际上是定义了斐波拉契数列的推算规则,可以从第一个元素开始,推算出后续任意的元素,这种逻辑其实非常类似generator。

也就是说,上面的函数和generator仅一步之遥。要把fib函数变成generator函数,只需要把print(b)改为yield b就可以了:

1
2
3
4
5
6
7
def fib(max):
n, a, b = 0, 0, 1
while n < max:
yield b
a, b = b, a + b
n = n + 1
return 'done'

这就是定义generator的另一种方法。如果一个函数定义中包含yield关键字,那么这个函数就不再是一个普通函数,而是一个generator函数,调用一个generator函数将返回一个generator

普通函数是顺序执行,遇到return语句或者最后一行函数语句就返回。而变成generator的函数,在每次调用next()的时候执行,遇到yield语句返回,再次执行时从上次返回的yield语句处继续执行。

调用该generator函数时,首先要生成一个generator对象,然后用next()函数不断获得下一个返回值:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
def odd():
print('step 1')
yield(1)
print('step 2')
yield(3)
print('step 3')
yield(5)

>>> o = odd()//创建一个新的generate对象
>>> next(o)
step 1
1
>>> next(o)
step 2
3
>>> next(o)
step 3
5
就好像在手动调试一样,yield就是一个循环中的断点

iterator,iterable

惰性序列

Map/Reduce

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

1
2
list(map(str, [1, 2, 3, 4, 5, 6, 7, 8, 9]))
['1', '2', '3', '4', '5', '6', '7', '8', '9']

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

1
reduce(f, [x1, x2, x3, x4]) = f(f(f(x1, x2), x3), x4)

filter

map()类似,filter()也接收一个函数和一个序列。和map()不同的是,filter()把传入的函数依次作用于每个元素,然后根据返回值是True还是False决定保留还是丢弃该元素。

例如,在一个list中,删掉偶数,只保留奇数,可以这么写:

1
2
3
4
def is_odd(n):
return n % 2 == 1

list(filter(is_odd, [1, 2, 4, 5, 6, 9, 10, 15]))

//////sort

模块

.py

放在文件夹里就是包

pip

模块搜索路径:

1.

当在 Python 中使用 import mymodule 试图加载一个模块时,Python 会在指定的路径下去查找对应的 .py 文件。如果没有找到该模块,就会抛出 ImportError 错误。

默认情况下,Python 会按照如下顺序来查找模块:

  • 当前工作目录:这是你运行 Python 解释器的目录。
  • 内置模块:Python 自带的模块,如 ossys 等。
  • 第三方模块:通过包管理工具(如 pip)安装的模块,通常位于 site-packages 文件夹中。
2.在运行时动态添加路径

你可以通过在 Python 程序运行时修改 sys.path 来添加自定义路径。例如,你可以使用 sys.path.append() 来添加一个新的目录。

1
2
import sys
sys.path.append('/Users/michael/my_py_scripts')

仅在当前运行有效

特殊变量:

image-20241009203613375

面向对象

类,私有变量,初始化

高级

///////

错误调试和测试

assert
1
assert n != 0, 'n is zero!'
logging!

使用 logging 模块非常简单,以下是一个基本的使用示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import logging

# 设置日志的输出级别和格式
logging.basicConfig(level=logging.DEBUG,
format='%(asctime)s - %(levelname)s - %(message)s')

# 调试信息
logging.debug('这是调试信息')

# 一般信息
logging.info('这是一般信息')

# 警告信息
logging.warning('这是警告信息')

# 错误信息
logging.error('这是错误信息')

# 严重错误
logging.critical('这是严重错误')

输出结果:

1
2
3
4
5
2024-10-09 12:00:00,000 - DEBUG - 这是调试信息
2024-10-09 12:00:00,001 - INFO - 这是一般信息
2024-10-09 12:00:00,001 - WARNING - 这是警告信息
2024-10-09 12:00:00,002 - ERROR - 这是错误信息
2024-10-09 12:00:00,002 - CRITICAL - 这是严重错误
  • format 控制了日志的输出格式。%(asctime)s 表示时间戳,%(levelname)s 表示日志级别,%(message)s 表示日志内容。
  • level 参数可以控制日志的输出级别。level=logging.DEBUG 表示输出所有级别的日志,level=logging.ERROR 表示只输出错误及以上的日志信息。
4. 将日志写入文件

如果你希望将日志写入到文件中,可以在 basicConfig 中指定 filename 参数:

1
2
3
4
5
6
7
8
import logging

# 将日志写入到文件中
logging.basicConfig(filename='app.log',
level=logging.DEBUG,
format='%(asctime)s - %(levelname)s - %(message)s')

logging.debug('这条日志会写入到文件中')

这会在当前目录下生成一个名为 app.log 的文件,所有日志内容都会写入到该文件中。

5. 设置不同的日志处理器

你可以设置多个日志处理器(Handler),例如将日志同时输出到控制台和文件中。

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
import logging

# 创建 logger
logger = logging.getLogger('my_logger')
logger.setLevel(logging.DEBUG)

# 创建控制台处理器并设置日志级别为 DEBUG
console_handler = logging.StreamHandler()
console_handler.setLevel(logging.DEBUG)

# 创建文件处理器并设置日志级别为 WARNING
file_handler = logging.FileHandler('app.log')
file_handler.setLevel(logging.WARNING)

# 创建日志格式
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
console_handler.setFormatter(formatter)
file_handler.setFormatter(formatter)

# 将处理器添加到 logger
logger.addHandler(console_handler)
logger.addHandler(file_handler)

# 记录不同级别的日志
logger.debug('调试信息,这只会打印在控制台')
logger.info('信息')
logger.warning('警告,这会打印在控制台和文件中')
logger.error('错误,这会打印在控制台和文件中')
logger.critical('严重错误,这会打印在控制台和文件中')
6. 日志中的变量

你还可以将变量值包含在日志信息中:

1
2
python复制代码name = 'Alice'
logging.info('用户 %s 已登录', name)

这条信息会输出:

1
2024-10-09 12:00:00,003 - INFO - 用户 Alice 已登录
7. 调试复杂应用

在复杂应用中使用 logging 模块可以帮助你更好地调试。例如,你可以在函数的入口和出口处打印调试信息,以跟踪函数的调用情况和执行流程。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import logging

def divide(a, b):
logging.debug(f'开始 divide 函数: a={a}, b={b}')
try:
result = a / b
logging.debug(f'除法结果: {result}')
return result
except ZeroDivisionError as e:
logging.error(f'错误: {e}')
return None

divide(10, 2)
divide(10, 0)

输出:

1
2
3
4
2024-10-09 12:00:00,004 - DEBUG - 开始 divide 函数: a=10, b=2
2024-10-09 12:00:00,004 - DEBUG - 除法结果: 5.0
2024-10-09 12:00:00,005 - DEBUG - 开始 divide 函数: a=10, b=0
2024-10-09 12:00:00,005 - ERROR - 错误: division by zero
编写单元测试,文档测试

io

同步io,异步io

文件读写

os进程线程

fork()

p=Process(target=run_proc, args=(‘test’,))

p.start()

p.join()

pool

tcp/ip

互联网上每个计算机的唯一标识就是IP地址,类似123.123.123.123。如果一台计算机同时接入到两个或更多的网络,比如路由器,它就会有两个或多个IP地址,所以,IP地址对应的实际上是计算机的网络接口,通常是网卡。

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

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

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

端口有什么作用?在两台计算机通信时,只发IP地址是不够的,因为同一台计算机上跑着多个网络程序。一个TCP报文来了之后,到底是交给浏览器还是QQ,就需要端口号来区分。每个网络程序都向操作系统申请唯一的端口号,这样,两个进程在两台计算机之间建立网络连接就需要各自的IP地址和各自的端口号。

一个进程也可能同时与多个计算机建立链接,因此它会申请很多端口。

socket

tcp

客户端

一个Socket表示“打开了一个网络链接”

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
import socket

# 创建一个socket:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 建立连接:
s.connect(('www.sina.com.cn', 80))
#作为服务器,提供什么样的服务,端口号就必须固定下来。由于我们想要访问网页,因此新浪提供网页服务的服务器必须把端口号固定在80端口,因为80端口是Web服务的标准端口。其他服务都有对应的标准端口号,例如SMTP服务是25端口,FTP服务是21端口,等等。端口号小于1024的是Internet标准服务的端口,端口号大于1024的,可以任意使用。

#发送请求,要求返回首页的内容:
s.send(b'GET / HTTP/1.1\r\n # 请求方法 + 路径 + HTTP版本
Host: www.sina.com.cn\r\n # 指定主机
Connection: close\r\n # 指定关闭连接
\r\n # 头部结束')

#接受返回数值
buffer = []
while True:
# 每次最多接收1k字节:
d = s.recv(1024)
if d:
buffer.append(d)
else:
break
data = b''.join(buffer)
#buffer 是一个包含字节块的列表(或其他可迭代对象)。这些字节块可能是从网络或文件中分段读取的二进制数据。
b'' 是一个空的字节串,用于作为 join() 方法的连接符。
b''.join(buffer):
join() 是一个字符串方法,用于将一个可迭代对象(如列表、元组)的所有元素连接成一个字符串或字节串。
在这里,buffer 中的每个元素都是字节数据,通过 b''.join(buffer),这些字节数据会被依次连接起来,最终形成一个完整的字节串。
b'' 表示连接时不需要任何分隔符,它只是用于标识这是在处理字节类型数据,而不是普通字符串。

header, html = data.split(b'\r\n\r\n', 1)
print(header.decode('utf-8'))
# 把接收的数据写入文件:
with open('sina.html', 'wb') as f:
f.write(html)

image-20241018192111310

image-20241018192125281

服务器端

1
2
socket.socket(socket.AF_INET, socket.SOCK_STREAM)

然后,我们要绑定监听的地址和端口。服务器可能有多块网卡,可以绑定到某一块网卡的IP地址上,也可以用0.0.0.0绑定到所有的网络地址,还可以用127.0.0.1绑定到本机地址。127.0.0.1是一个特殊的IP地址,表示本机地址,如果绑定到这个地址,客户端必须同时在本机运行才能连接

1
2
3
4
5
6
7
8
9
s.bind(('127.0.0.1', 9999))
s.listen(5)
print('Waiting for connection...')
while True:
# 接受一个新连接:
sock, addr = s.accept()
# 创建新线程来处理TCP连接:
t = threading.Thread(target=tcplink, args=(sock, addr))
t.start()
1
2
3
4
5
6
7
8
9
10
11
def tcplink(sock, addr):
print('Accept new connection from %s:%s...' % addr)
sock.send(b'Welcome!')
while True:
data = sock.recv(1024)
time.sleep(1)
if not data or data.decode('utf-8') == 'exit':
break
sock.send(('Hello, %s!' % data.decode('utf-8')).encode('utf-8'))
sock.close()
print('Connection from %s:%s closed.' % addr)

udp

使用UDP协议时,不需要建立连接,只需要知道对方的IP地址和端口号,就可以直接发数据包。但是,能不能到达就不知道了。