Python进阶学习

Python进阶学习

参考资料:

  • AILearning
  • 菜鸟教程
  • python之platform模块

python-version: 3.9

使用Jupyter进行练习

一、sys模块

import sys

命令行参数

sys.argv 显示传入的参数:

%%writefile print_args.py
import sys
print(sys.argv)
Writing print_args.py
# 运行它
%run print_args.py yjq 520 mhy
['print_args.py', 'yjq', '520', 'mhy']

第一个参数 (sys.args[0]) 表示的始终是执行的文件名,然后依次显示传入的参数。

# 删除生成的文件
import os
os.remove('print_args.py')

异常消息

sys.exc_info() 可以显示 Exception 的信息,返回一个 (type, value, traceback) 组成的三元组,可以与 try/catch 块一起使用:

try:
    x = 1 / 0
except Exception:
    print(sys.exc_info())
(, ZeroDivisionError('division by zero'), )

标准输入输出流

  • sys.stdin
  • sys.stdout
  • sys.stderr

退出Python

sys.exit(arg=0) 用于退出 Python。0 或者 None 表示正常退出,其他值表示异常。

Python Path

sys.path 表示 Python 搜索模块的路径和查找顺序:

sys.path
['d:\\研究生资料\\python-learning\\advanced-learning',
 'd:\\anaconda\\python39.zip',
 'd:\\anaconda\\DLLs',
 'd:\\anaconda\\lib',
 'd:\\anaconda',
 '',
 'C:\\Users\\26969\\AppData\\Roaming\\Python\\Python39\\site-packages',
 'C:\\Users\\26969\\AppData\\Roaming\\Python\\Python39\\site-packages\\win32',
 'C:\\Users\\26969\\AppData\\Roaming\\Python\\Python39\\site-packages\\win32\\lib',
 'C:\\Users\\26969\\AppData\\Roaming\\Python\\Python39\\site-packages\\Pythonwin',
 'd:\\anaconda\\lib\\site-packages',
 'd:\\anaconda\\lib\\site-packages\\win32',
 'd:\\anaconda\\lib\\site-packages\\win32\\lib',
 'd:\\anaconda\\lib\\site-packages\\Pythonwin']

操作系统信息

sys.platform 显示当前操作系统信息:

  • Windows: win32
  • Mac OSX: darwin
  • Linux: linux2
sys.platform
'win32'

返回 Windows 操作系统的版本:

sys.getwindowsversion()
sys.getwindowsversion(major=10, minor=0, build=19044, platform=2, service_pack='')

标准库中有 platform 模块提供更详细的信息。

import platform
print("操作系统名称及版本号为: {platform}".format(platform=platform.platform()))
print("操作系统版本号为: {version}".format(version=platform.version()))
print("操作系统位数为: {architecture}".format(architecture=platform.architecture()))
print("计算机类型为: {machine}".format(machine=platform.machine()))
print("计算机的网络名称为: {node}".format(node=platform.node()))
print("计算机处理器信息为: {processor}".format(processor=platform.processor()))
print("上面所有的信息汇总: {uname}".format(uname=platform.uname()))

操作系统名称及版本号为: Windows-10-10.0.19044-SP0
操作系统版本号为: 10.0.19044
操作系统位数为: ('64bit', 'WindowsPE')
计算机类型为: AMD64
计算机的网络名称为: LAPTOP-FJ90A1EQ
计算机处理器信息为: Intel64 Family 6 Model 165 Stepping 2, GenuineIntel
上面所有的信息汇总: uname_result(system='Windows', node='LAPTOP-FJ90A1EQ', release='10', version='10.0.19044', machine='AMD64')

Python 版本信息

sys.version
'3.9.12 (main, Apr  4 2022, 05:22:27) [MSC v.1916 64 bit (AMD64)]'
sys.version_info
sys.version_info(major=3, minor=9, micro=12, releaselevel='final', serial=0)

二、os模块

os 模块提供了对系统文件进行操作的方法:

import os

文件路径操作

os 模块提供了对系统文件进行操作的方法:

  • os.remove(path)os.unlink(path) :删除指定路径的文件。路径可以是全名,也可以是当前工作目录下的路径。
  • os.removedirs:删除文件,并删除中间路径中的空文件夹
  • os.chdir(path):将当前工作目录改变为指定的路径
  • os.getcwd():返回当前的工作目录
  • os.curdir:表示当前目录的符号
  • os.rename(old, new):重命名文件
  • os.renames(old, new):重命名文件,如果中间路径的文件夹不存在,则创建文件夹
  • os.listdir(path):返回给定目录下的所有文件夹和文件名,不包括 '.''..' 以及子文件夹下的目录。('.''..' 分别指当前目录和父目录)
  • os.mkdir(name):产生新文件夹
  • os.makedirs(name):产生新文件夹,如果中间路径的文件夹不存在,则创建文件夹
# os当前目录
print("当前目录为:", os.getcwd())
print("当前目录的符号为:", os.curdir)
print("当前目录下的文件为:",os.listdir(os.curdir))
当前目录为: d:\研究生资料\python-learning\advanced-learning
当前目录的符号为: .
当前目录下的文件为: ['python进阶学习.ipynb']
# 产生文件
with open("test.file","w") as f:
    print("test.file" in os.listdir(os.curdir))
True
# 重命名文件
os.rename("test.file", "test.new.file")

print("test.file" in os.listdir(os.curdir))
print("test.new.file" in os.listdir(os.curdir))
False
True
# 删除文件
os.remove('test.new.file')

系统常量

# 当前操作系统的换行符:
os.linesep
# Linux为'\n'
'\r\n'
# 当前操作系统的路径分隔符
os.sep
# Linux为'/'
'\\'
# 当前操作系统的环境变量中的分隔符
os.pathsep
';'

其他

os.environ 是一个存储所有环境变量的值的字典,可以修改。

os.environ['HOMEDRIVE']
'C:'

os.urandom(len) 返回指定长度的随机字节。

os.urandom(10)
b'\xea\x9b\x8a\x8fE\xfa\x1c&\xc8\xa4'

os.path 模块

不同的操作系统使用不同的路径规范,这样当我们在不同的操作系统下进行操作时,可能会带来一定的麻烦,而 os.path 模块则帮我们解决了这个问题。

import os.path

测试

  • os.path.isfile(path) :检测一个路径是否为普通文件
  • os.path.isdir(path):检测一个路径是否为文件夹
  • os.path.exists(path):检测路径是否存在
  • os.path.isabs(path):检测路径是否为绝对路径

split 和 join

  • os.path.split(path):拆分一个路径为 (head, tail) 两部分
  • os.path.join(a, *p):使用系统的路径分隔符,将各个部分合成一个路径

其他

  • os.path.abspath():返回路径的绝对路径
  • os.path.dirname(path):返回路径中的文件夹部分
  • os.path.basename(path):返回路径中的文件部分
  • os.path.slitext(path):将路径与扩展名分开
  • os.path.expanduser(path):展开 '~''~user'

三、CSV模块

标准库中有自带的 csv (逗号分隔值) 模块处理 csv 格式的文件:

import csv

读取csv文件

%%file data.csv
"alpha 1",  100, -1.443
"beat  3",   12, -0.0934
"gamma 3a", 192, -0.6621
"delta 2a",  15, -4.515
Writing data.csv

开这个文件,并产生一个文件 reader,可以按行迭代数据:

with open('data.csv') as fp:
    r = csv.reader(fp)
    for row in r:
        print(row)
['alpha 1', '  100', ' -1.443']
['beat  3', '   12', ' -0.0934']
['gamma 3a', ' 192', ' -0.6621']
['delta 2a', '  15', ' -4.515']

默认数据内容都被当作字符串处理,不过可以自己进行处理:

data = []

with open('data.csv') as fp:
    r = csv.reader(fp)
    for row in r:
        data.append([row[0], int(row[1]), float(row[2])])

data
[['alpha 1', 100, -1.443],
 ['beat  3', 12, -0.0934],
 ['gamma 3a', 192, -0.6621],
 ['delta 2a', 15, -4.515]]
# 删除
import os
os.remove('data.csv')

写csv文件

可以使用 csv.writer 写入文件,不过相应地,传入的应该是以写方式打开的文件:

data = [('one', 1, 1.5), ('two', 2, 8.0)]
# newline = ''用来取消换行
with open('out.csv', 'w', newline ='') as fp:
    w = csv.writer(fp)
    w.writerows(data)

Python进阶学习_第1张图片

import os
os.remove('out.csv')

其他选项

numpy.loadtxt()pandas.read_csv() 可以用来读写包含很多数值数据的 csv 文件:

%%file trades.csv
Order,Date,Stock,Quantity,Price
A0001,2013-12-01,AAPL,1000,203.4
A0002,2013-12-01,MSFT,1500,167.5
A0003,2013-12-02,GOOG,1500,167.5
Writing trades.csv

使用 pandas 进行处理,生成一个 DataFrame 对象:

import pandas
df = pandas.read_csv('trades.csv', index_col=0)
print(df)
             Date Stock  Quantity  Price
Order                                   
A0001  2013-12-01  AAPL      1000  203.4
A0002  2013-12-01  MSFT      1500  167.5
A0003  2013-12-02  GOOG      1500  167.5

通过名字进行索引

df['Quantity'] * df['Price']
Order
A0001    203400.0
A0002    251250.0
A0003    251250.0
dtype: float64
import os
os.remove('trades.csv')

四、正则表达式与re模块

正则表达式

正则表达式是用来匹配字符串或者子串的一种模式,匹配的字符串可以很具体,也可以很一般化。

Python 标准库提供了 re 模块。

import re

re.match & re.search

re 模块中, re.matchre.search 是常用的两个方法:

re.match(pattern, string[, flags])
re.search(pattern, string[, flags]) 复制ErrorOK!

两者都寻找第一个匹配成功的部分,成功则返回一个 match 对象,不成功则返回 None,不同之处在于 re.match 只匹配字符串的开头部分,而 re.search 匹配的则是整个字符串中的子串。

re.findall & re.finditer

re.findall(pattern, string) 返回所有匹配的对象, re.finditer 则返回一个迭代器。

re.split

re.split(pattern, string[, maxsplit]) 按照 pattern 指定的内容对字符串进行分割。

re.sub

re.sub(pattern, repl, string[, count])pattern 匹配的内容进行替换。

re.compile

re.compile(pattern) 生成一个 pattern 对象,这个对象有匹配,替换,分割字符串的方法。

正则表达式规则

正则表达式由一些普通字符和一些元字符(metacharacters)组成。普通字符包括大小写的字母和数字,而元字符则具有特殊的含义:

子表达式 匹配内容
. 匹配除了换行符之外的内容
\w 匹配所有字母和数字字符
\d 匹配所有数字,相当于 [0-9]
\s 匹配空白,相当于 [\t\n\t\f\v]
\W,\D,\S 匹配对应小写字母形式的补
[...] 表示可以匹配的集合,支持范围表示如 a-z, 0-9
(...) 表示作为一个整体进行匹配
| 表示逻辑或
^ 表示匹配后面的子表达式的补
* 表示匹配前面的子表达式 0 次或更多次
+ 表示匹配前面的子表达式 1 次或更多次
? 表示匹配前面的子表达式 0 次或 1 次
{m} 表示匹配前面的子表达式 m 次
{m,} 表示匹配前面的子表达式至少 m 次
{m,n} 表示匹配前面的子表达式至少 m 次,至多 n 次

例如:

  • ca*t 匹配: ct, cat, caaaat, ...
  • ab\d|ac\d 匹配: ab1, ac9, ...
  • ([^a-q]bd) 匹配: rbd, 5bd, ...

举例说明

string = 'hello world'
pattern = 'hello (\w+)'

match = re.match(pattern, string)
print(match)
# 一旦找到了符合条件的部分
# 我们便可以使用 group 方法查看匹配的部分:
if match:
    print(match.group(0))
    print(match.group(1))

hello world
world

通常,match.group(0) 匹配整个返回的内容,之后的 1,2,3,... 返回规则中每个括号(按照括号的位置排序)匹配的部分。

如果某个 pattern 需要反复使用,那么我们可以将它预先编译。

pattern1 = re.compile('hello (\w+)')

match = pattern1.match(string)
if match is not None:
    print(match.group(1))
world

由于元字符的存在,所以对于一些特殊字符,我们需要使用 '\' 进行逃逸字符的处理,使用表达式 '\\' 来匹配 '\'

但事实上,Python 本身对逃逸字符也是这样处理的:

pattern = '\\'
print(pattern)
\

因为逃逸字符的问题,我们需要使用四个 '\\\\' 来匹配一个单独的 '\\'

pattern = '\\\\'
path = "C:\\foo\\bar\\baz.txt"
print(re.split(pattern, path))
['C:', 'foo', 'bar', 'baz.txt']

这样看起来十分麻烦,好在 Python 提供了 raw string 来忽略对逃逸字符串的处理,从而可以这样进行匹配:

pattern = r'\\'
path = r"C:\foo\bar\baz.txt"
print(re.split(pattern, path))
['C:', 'foo', 'bar', 'baz.txt']

如果规则太多复杂,正则表达式不一定是个好选择。

Numpy的fromregex()

fromregex(file, pattern, dtype)

dtype 中的内容与 pattern 的括号一一对应:

%%file test.dat 
1312 foo
1534    bar
444  qux
Writing test.dat
pattern = "(\d+)\s+(...)"
dt = [('num', 'int64'), ('key', 'S3')]

from numpy import fromregex
output = fromregex('test.dat', pattern, dt)
print(output)
# 显示num项
print(output['num'])
[(1312, b'foo') (1534, b'bar') ( 444, b'qux')]
[1312 1534  444]
import os
os.remove('test.dat')

五、datetime模块

import datetime as dt

datetime 提供了基础时间和日期的处理。

datetime格式字符表

字符 含义
%a 星期英文缩写
%A 星期英文
%w 一星期的第几天,[0(sun),6]
%b 月份英文缩写
%B 月份英文
%d 日期,[01,31]
%H 小时,[00,23]
%I 小时,[01,12]
%j 一年的第几天,[001,366]
%m 月份,[01,12]
%M 分钟,[00,59]
%p AM 和 PM
%S 秒钟,[00,61] (大概是有闰秒的存在)
%U 一年中的第几个星期,星期日为第一天,[00,53]
%W 一年中的第几个星期,星期一为第一天,[00,53]
%y 没有世纪的年份
%Y 完整的年份

date对象

可以使用 date(year, month, day) 产生一个 date 对象:

d1 = dt.date(2007, 9, 25)
d2 = dt.date(2008, 9, 25)
# 格式化date对象的输出
print(d1)
print(d1.strftime('%A, %m/%d/%y'))
print(d1.strftime('%a, %m-%d-%Y'))
# 查看日期差,返回的是一个 timedelta 对象
d = d2 - d1
print("日期差:",d)
print(d.days)
print(d.seconds)
# 查看今天的日期
print("今天的日期:",dt.date.today())
2007-09-25
Tuesday, 09/25/07
Tue, 09-25-2007
日期差: 366 days, 0:00:00
366
0
今天的日期: 2022-12-09

time对象

可以使用 time(hour, min, sec, us) 产生一个 time 对象:

t1 = dt.time(15, 38)
t2 = dt.time(18)
# 改变显示格式
print (t1)
print (t1.strftime('%I:%M, %p'))
print (t1.strftime('%H:%M:%S, %p'))
15:38:00
03:38, PM
15:38:00, PM

因为没有具体的日期信息,所以 time 对象不支持减法操作。

datetime对象

可以使用 datetime(year, month, day, hr, min, sec, us) 来创建一个 datetime 对象。

d1 = dt.datetime.now()
print (d1)
2022-12-09 15:31:26.462137

给当前的时间加上 30 天,timedelta 的参数是 timedelta(day, hr, min, sec, us)

d2 = d1 + dt.timedelta(30)
print (d2)
2023-01-08 15:31:26.462137

除此之外,我们还可以通过一些指定格式的字符串来创建 datetime 对象:

print (dt.datetime.strptime('2/10/01', '%m/%d/%y'))

2001-02-10 00:00:00

六、SQL数据库

Python 提供了一系列标准的数据库的 API,这里我们介绍 sqlite 数据库的用法,其他的数据库的用法大同小异:

import sqlite3 as db

连接到一个数据库

connection = db.connect("yjq_db.sqlite")

一旦建立连接,我们可以利用它的 cursor() 来执行 SQL 语句:

cursor = connection.cursor()
cursor.execute("""CREATE TABLE IF NOT EXISTS orders(
 order_id TEXT PRIMARY KEY,
 date TEXT,
 symbol TEXT,
 quantity INTEGER,
 price NUMBER)""")
orders = [
          ("A0002","2013-12-01","MSFT",1500,167.5),
          ("A0003","2013-12-02","GOOG",1500,167.5)
]
cursor.executemany("""INSERT INTO orders VALUES
 (?, ?, ?, ?, ?)""", orders)
connection.commit()

query 语句执行之后,我们需要进行 commit,否则数据库将不会接受这些变化,如果想撤销某个 commit,可以使用 rollback() 方法撤销到上一次 commit() 的结果:

try:
    ... # perform some operations
except:
    connection.rollback()
    raise
else:
    connection.commit() 

使用 SELECT 语句对数据库进行查询:

stock = 'MSFT'
cursor.execute(
    """
    SELECT *
    FROM orders
    WHERE symbol=?
    ORDER BY quantity
    """, (stock,))
for row in cursor:
    print(row)
('A0002', '2013-12-01', 'MSFT', 1500, 167.5)

cursor.fetchone() 返回下一条内容, cursor.fetchall() 返回所有查询到的内容组成的列表(可能非常大):

stock = 'GOOG'
cursor.execute(
    """
    SELECT *
    FROM orders
    WHERE symbol=?
    ORDER BY quantity
    """, (stock,))
cursor.fetchall()

[('A0003', '2013-12-02', 'GOOG', 1500, 167.5)]
# 关闭数据库
cursor.close()
connection.close()

七、对象关系映射

例如对于上一节中的数据库:

Order Date Stock Quantity Price
A0002 2013-12-01 MSFT 1500 167.5
A0003 2013-12-02 GOOG 1500 167.5

可以用一个类来描述:

Attr. Method
Order id Cost
Date
Stock
Quant.
Price

可以使用 sqlalchemy 来实现这种对应:

from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column, Date, Float, Integer, String

Base = declarative_base()

class Order(Base):
    __tablename__ = 'orders'

    order_id = Column(String, primary_key=True)
    date = Column(Date)
    symbol = Column(String)
    quantity = Column(Integer)
    price = Column(Float)

    def get_cost(self):
        return self.quantity*self.price

生成一个 Order 对象:

import datetime
order = Order(order_id='A0004', date=datetime.date.today(), symbol='MSFT', quantity=-1000, price=187.54)
# 调用方法
order.get_cost()
-187540.0

使用上一节生成的数据库产生一个 session

from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker

engine = create_engine("sqlite:///yjq_db.sqlite")   # 相当于 connection
Session = sessionmaker(bind=engine) # 相当于 cursor
session = Session()

使用这个 session 向数据库中添加刚才生成的对象:

session.add(order)
session.commit()

显示是否添加成功:

for row in engine.execute("SELECT * FROM orders"):
    print (row)
('A0002', '2013-12-01', 'MSFT', 1500, 167.5)
('A0003', '2013-12-02', 'GOOG', 1500, 167.5)
('A0004', '2022-12-09', 'MSFT', -1000, 187.54)

使用 filter 进行查询,返回的是 Order 对象的列表

for order in session.query(Order).filter(Order.symbol=="GOOG"):
    print (order.order_id, order.date, order.get_cost())

A0003 2013-12-02 251250.0
# 返回列表的第一个
order_2 = session.query(Order).filter(Order.order_id=='A0002').first()
order_2.symbol
'MSFT'

八、函数进阶

函数是基本类型

Python 中,函数是一种基本类型的对象,这意味着

  • 可以将函数作为参数传给另一个函数
  • 将函数作为字典的值储存
  • 将函数作为另一个函数的返回值
def square(x):
    """Square of x."""
    return x*x

def cube(x):
    """Cube of x."""
    return x*x*x
# 作为字典的值
funcs = {
    'square': square,
    'cube': cube,
}
funcs
{'square': , 'cube': }
x = 2

print (square(x))
print (cube(x))

for func in sorted(funcs):
    print (func, funcs[func](x))
4
8
cube 8
square 4

函数参数

引用传递

Python 中的函数传递方式是 call by reference 即引用传递,例如,对于这样的用法:

x = [10, 11, 12]
f(x) 

传递给函数 f 的是一个指向 x 所包含内容的引用,如果我们修改了这个引用所指向内容的值(例如 x[0]=999),那么外面的 x 的值也会被改变。不过如果我们在函数中赋给 x 一个新的值(例如另一个列表),那么在函数外面的 x 的值不会改变:

def mod_f(x):
    x[0] = 999
    return x

x = [1, 2, 3]

print (x)
print (mod_f(x))
print (x)
[1, 2, 3]
[999, 2, 3]
[999, 2, 3]
def no_mod_f(x):
    x = [4, 5, 6]
    return x

x = [1,2,3]

print (x)
print (no_mod_f(x))
print (x)
[1, 2, 3]
[4, 5, 6]
[1, 2, 3]

默认参数是可变的

函数可以传递默认参数,默认参数的绑定发生在函数定义的时候,以后每次调用默认参数时都会使用同一个引用。

这样的机制会导致这种情况的发生:

def f(x = []):
    x.append(1)
    return x

理论上说,我们希望调用 f() 时返回的是 [1], 但事实上:

print (f())
print (f())
print (f())
print (f(x = [9,9,9]))
print (f())
print (f())
[1]
[1, 1]
[1, 1, 1]
[9, 9, 9, 1]
[1, 1, 1, 1]
[1, 1, 1, 1, 1]

而我们希望看到的应该是这样:

def f(x = None):
    if x is None:
        x = []
    x.append(1)
    return x

print (f())
print (f())
print (f())
print (f(x = [9,9,9]))
print (f())
print (f())
[1]
[1]
[1]
[9, 9, 9, 1]
[1]
[1]

高阶函数

以函数作为参数,或者返回一个函数的函数是高阶函数,常用的例子有 mapfilter 函数:

map(f, sq) 函数将 f 作用到 sq 的每个元素上去,并返回结果组成的列表,相当于:

[f(s) for s in sq]
def square(x):
    return x * x
print(list(map(square,range(5))))
[0, 1, 4, 9, 16]

filter(f, sq) 函数的作用相当于,对于 sq 的每个元素 s,返回所有 f(s)Trues 组成的列表,相当于:

[s for s in sq if f(s)]
def is_even(x):
    return x % 2 == 0

print(list(filter(is_even, range(5))))
[0, 2, 4]

reduce(f, sq) 函数接受一个二元操作函数 f(x,y),并对于序列 sq 每次合并两个元素:

from functools import reduce


def my_add(x, y):
    return x + y

reduce(my_add, [1,2,3,4,5])
15

返回一个函数:

def make_logger(target):
    def logger(data):
        with open(target, 'a') as f:
            f.write(data + '\n')
    return logger

foo_logger = make_logger('foo.txt')
foo_logger('Hello')
foo_logger('World')

import os
os.remove('foo.txt')

匿名函数

在使用 mapfilterreduce 等函数的时候,为了方便,对一些简单的函数,我们通常使用匿名函数的方式进行处理,其基本形式是:

lambda <variables>: <expression>
# 例如,我们可以将这个:
print(list(map(square, range(5))))
# 用匿名函数替换为:
print(list(map(lambda x: x*x, range(5))))

[0, 1, 4, 9, 16]
[0, 1, 4, 9, 16]

匿名函数虽然写起来比较方便(省去了定义函数的烦恼),但是有时候会比较难于阅读。

global变量

一般来说,函数中是可以直接使用全局变量的值的

x = 15

def print_x():
    print (x)

print_x()
15

但是要在函数中修改全局变量的值,需要加上 global 关键字。

x = 15

def print_newx():
    global x
    x = 18
    print (x)

print_newx()

print (x)
18
18

九、迭代器

简介

迭代器对象可以在 for 循环中使用:

x = [2, 4, 6]

for n in x:
    print (n)
2
4
6

其好处是不需要对下标进行迭代,但是有些情况下,我们既希望获得下标,也希望获得对应的值,那么可以将迭代器传给 enumerate 函数,这样每次迭代都会返回一组 (index, value) 组成的元组:

x = [2, 4, 6]

for i, n in enumerate(x):
    print ('pos', i, 'is', n)
pos 0 is 2
pos 1 is 4
pos 2 is 6

迭代器对象必须实现 __iter__ 方法:

x = [2, 4, 6]
i = x.__iter__()
print (i)

__iter__() 返回的对象支持 __next__() 方法,返回迭代器中的下一个元素:

print(i.__next__())
2

当下一个元素不存在时,会 raise 一个 StopIteration 错误

print(i.__next__())
print(i.__next__())
print(i.__next__())
4
6



---------------------------------------------------------------------------

StopIteration                             Traceback (most recent call last)

Cell In [7], line 3
      1 print(i.__next__())
      2 print(i.__next__())
----> 3 print(i.__next__())


StopIteration: 

很多标准库函数返回的是迭代器:

r = reversed(x)
print (r)
print(r.__next__())
print(r.__next__())
print(r.__next__())


6
4
2

迭代器的 __iter__ 方法返回它本身:

print(r.__iter__())

自定义迭代器

自定义一个 list 的取反迭代器:

class ReverseListIterator(object):

    def __init__(self, list):
        self.list = list
        self.index = len(list)
    
    def __iter__(self):
        return self

    def __next__(self):
        self.index -= 1
        if self.index >= 0:
            return self.list[self.index]
        else:
            raise StopIteration        
x = range(10)
print(list(ReverseListIterator(x)))
[9, 8, 7, 6, 5, 4, 3, 2, 1, 0]

十、生成器

在 Python 中,使用了 yield 的函数被称为生成器(generator)。

跟普通函数不同的是,生成器是一个返回迭代器的函数,只能用于迭代操作,更简单点理解生成器就是一个迭代器。

在调用生成器运行的过程中,每次遇到 yield 时函数会暂停并保存当前所有的运行信息,返回 yield 的值, 并在下一次执行 next() 方法时从当前位置继续运行。

调用一个生成器函数,返回的是一个迭代器对象。

while 循环通常有这样的形式:

<do setup>
result = []
while True:
    <generate value>
    result.append(value)
    if <done>:
        break

使用迭代器实现这样的循环:

class GenericIterator(object):
    def __init__(self, ...):
        <do setup>
        # 需要额外储存状态
        <store state>
    def next(self): 
        <load state>
        <generate value>
        if <done>:
            raise StopIteration()
        <store state>
        return value

更简单的,可以使用生成器:

def generator(...):
    <do setup>
    while True:
        <generate value>
        # yield 说明这个函数可以返回多个值!
        yield value
        if <done>:
            break

举例说明:

import sys
 
def fibonacci(n): # 生成器函数 - 斐波那契
    a, b, counter = 0, 1, 0
    while True:
        if (counter > n): 
            return
        yield a
        a, b = b, a + b
        counter += 1
f = fibonacci(10) # f 是一个迭代器,由生成器返回生成
 
print(list(f))
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55]

十一、with语句与上下文管理器

with语句

# create/aquire some resource
...
try:
    # do something with the resource
    ...
finally:
    # destroy/release the resource
    ...

处理文件,线程,数据库,网络编程等等资源的时候,我们经常需要使用上面这样的代码形式,以确保资源的正常使用和释放。

好在Python 提供了 with 语句帮我们自动进行这样的处理,例如之前在打开文件时我们使用:

with open('my_file', 'w') as fp:
    # do stuff with fp
    data = fp.write("Hello world")

这等效于下面的代码,但是要更简便:

fp = open('my_file', 'w')
try:
    # do stuff with f
    data = fp.write("Hello world")
finally:
    fp.close()

上下文管理器

其基本用法如下:

with <expression>:
    <block>

执行的结果应当返回一个实现了上下文管理器的对象,即实现这样两个方法,__enter____exit__

print (fp.__enter__)
print (fp.__exit__)


__enter__ 方法在 执行前执行,而 __exit__ 执行结束后执行:

比如可以这样定义一个简单的上下文管理器:

class ContextManager(object):

    def __enter__(self):
        print ("Entering")

    def __exit__(self, exc_type, exc_value, traceback):
        print ("Exiting")
with ContextManager():
    print ("  Inside the with statement")
Entering
  Inside the with statement
Exiting

即使 中执行的内容出错,__exit__ 也会被执行:

with ContextManager():
    print (1/0)
Entering
Exiting



---------------------------------------------------------------------------

ZeroDivisionError                         Traceback (most recent call last)

Cell In [17], line 2
      1 with ContextManager():
----> 2     print (1/0)


ZeroDivisionError: division by zero

__enter__的返回值

如果在 __enter__ 方法下添加了返回值,那么我们可以使用 as 把这个返回值传给某个参数:

class ContextManager(object):

    def __enter__(self):
        print ("Entering")
        return "my value"

    def __exit__(self, exc_type, exc_value, traceback):
        print ("Exiting")
# 将 __enter__ 返回的值传给 value 变量:
with ContextManager() as value:
    print (value)
Entering
my value
Exiting

一个通常的做法是将 __enter__ 的返回值设为这个上下文管理器对象本身,文件对象就是这样做的:

class ContextManager(object):

    def __enter__(self):
        print ("Entering")
        return self

    def __exit__(self, exc_type, exc_value, traceback):
        print ("Exiting")
with ContextManager() as value:
    print (value)
Entering
<__main__.ContextManager object at 0x000001F2E63A6F10>
Exiting
fp = open('my_file', 'r')
print (fp.__enter__())
fp.close()
<_io.TextIOWrapper name='my_file' mode='r' encoding='cp936'>
import os
os.remove('my_file')

错误管理

上下文管理器对象将错误处理交给 __exit__ 进行,可以将错误类型,错误值和 traceback 等内容作为参数传递给 __exit__ 函数:

class ContextManager(object):

    def __enter__(self):
        print ("Entering")
    def __exit__(self, exc_type, exc_value, traceback):
        print ("Exiting")
        if exc_type is not None:
            print ("  Exception:", exc_value)

如果没有错误,这些值都将是 None, 当有错误发生的时候:

with ContextManager():
    print (1/0)
Entering
Exiting
  Exception: division by zero



---------------------------------------------------------------------------

ZeroDivisionError                         Traceback (most recent call last)

Cell In [24], line 2
      1 with ContextManager():
----> 2     print (1/0)


ZeroDivisionError: division by zero

在这个例子中,只是简单的显示了错误的值,并没有对错误进行处理,所以错误被向上抛出了,如果不想让错误抛出,只需要将 __exit__ 的返回值设为 True

class ContextManager(object):

    def __enter__(self):
        print ("Entering")

    def __exit__(self, exc_type, exc_value, traceback):
        print ("Exiting")
        if exc_type is not None:
            print (" Exception suppresed:", exc_value)
            return True
with ContextManager():
    print (1/0)
Entering
Exiting
 Exception suppresed: division by zero

数据库举例

对于数据库的 transaction 来说,如果没有错误,我们就将其 commit 进行保存,如果有错误,那么我们将其回滚到上一次成功的状态。

class Transaction(object):

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

    def __enter__(self):
        return self.connection.cursor()

    def __exit__(self, exc_type, exc_value, traceback):
        if exc_value is None:
            # transaction was OK, so commit
            self.connection.commit()
        else:
            # transaction had a problem, so rollback
            self.connection.rollback()
"""建立一个数据库,保存一个地址表:"""
import sqlite3 as db
connection = db.connect(":memory:")

with Transaction(connection) as cursor:
    cursor.execute("""CREATE TABLE IF NOT EXISTS addresses (
 address_id INTEGER PRIMARY KEY,
 street_address TEXT,
 city TEXT,
 state TEXT,
 country TEXT,
 postal_code TEXT
 )""")
# 插入数据
with Transaction(connection) as cursor:
    cursor.executemany("""INSERT OR REPLACE INTO addresses VALUES (?, ?, ?, ?, ?, ?)""", [
        (0, '515 Congress Ave', 'Austin', 'Texas', 'USA', '78701'),
        (1, '245 Park Avenue', 'New York', 'New York', 'USA', '10167'),
        (2, '21 J.J. Thompson Ave.', 'Cambridge', None, 'UK', 'CB3 0FA'),
        (3, 'Supreme Business Park', 'Hiranandani Gardens, Powai, Mumbai', 'Maharashtra', 'India', '400076'),
    ])
# 假设插入数据之后出现了问题:
with Transaction(connection) as cursor:
    cursor.execute("""INSERT OR REPLACE INTO addresses VALUES (?, ?, ?, ?, ?, ?)""",
        (4, '2100 Pennsylvania Ave', 'Washington', 'DC', 'USA', '78701'),
    )
    raise Exception("out of addresses")

---------------------------------------------------------------------------

Exception                                 Traceback (most recent call last)

Cell In [29], line 6
      2 with Transaction(connection) as cursor:
      3     cursor.execute("""INSERT OR REPLACE INTO addresses VALUES (?, ?, ?, ?, ?, ?)""",
      4         (4, '2100 Pennsylvania Ave', 'Washington', 'DC', 'USA', '78701'),
      5     )
----> 6     raise Exception("out of addresses")


Exception: out of addresses
# 那么最新的一次插入将不会被保存,而是返回上一次 commit 成功的状态:
cursor.execute("SELECT * FROM addresses")
for row in cursor:
    print(row)

(0, '515 Congress Ave', 'Austin', 'Texas', 'USA', '78701')
(1, '245 Park Avenue', 'New York', 'New York', 'USA', '10167')
(2, '21 J.J. Thompson Ave.', 'Cambridge', None, 'UK', 'CB3 0FA')
(3, 'Supreme Business Park', 'Hiranandani Gardens, Powai, Mumbai', 'Maharashtra', 'India', '400076')

contextlib模块

很多的上下文管理器有很多相似的地方,为了防止写入很多重复的模式,可以使用 contextlib 模块来进行处理。

最简单的处理方式是使用 closing 函数确保对象的 close() 方法始终被调用:

urllib 包 包含以下几个模块:

  • urllib.request - 打开和读取 URL。
  • urllib.error - 包含 urllib.request 抛出的异常。
  • urllib.parse - 解析 URL。
  • urllib.robotparser - 解析 robots.txt 文件。
from contextlib import closing
from urllib.request import urlopen

with closing(urlopen('https://www.baidu.com/')) as url:
    html = url.readline()

print(html)

b'\r\n'

另一个有用的方法是使用修饰符 @contextlib

from contextlib import contextmanager

@contextmanager
def my_contextmanager():
    print ("Enter")
    yield
    print ("Exit")

with my_contextmanager():
    print("  Inside the with statement")
Enter
  Inside the with statement
Exit

yield 之前的部分可以看成是 __enter__ 的部分,yield 的值可以看成是 __enter__ 返回的值,yield 之后的部分可以看成是 __exit__ 的部分。

@contextmanager
def my_contextmanager():
    print ("Enter")
    yield ("my value")
    print ("Exit")

# 使用 yield 的值:
with my_contextmanager() as value:
    print (value)
Enter
my value
Exit

错误处理可以用 try 块来完成:

@contextmanager
def my_contextmanager():
    print ("Enter")
    try:
        yield
    except Exception as exc:
        print ("   Error:", exc)
    finally:
        print ("Exit")
with my_contextmanager():
    print (1/0)
Enter
   Error: division by zero
Exit

对于之前的数据库 transaction 我们可以这样定义:

@contextmanager
def transaction(connection):
    cursor = connection.cursor()
    try:
        yield cursor
    except:
        connection.rollback()
        raise
    else:
        connection.commit()

十二、修饰符

函数是一种对象

Python 中,函数是也是一种对象。

def foo(x):
    print(x)

print(type(foo))

# 查看函数拥有的方法:
dir(foo)
['__annotations__',
 '__call__',
 '__class__',
 '__closure__',
 '__code__',
 '__defaults__',
 '__delattr__',
 '__dict__',
 '__dir__',
 '__doc__',
 '__eq__',
 '__format__',
 '__ge__',
 '__get__',
 '__getattribute__',
 '__globals__',
 '__gt__',
 '__hash__',
 '__init__',
 '__init_subclass__',
 '__kwdefaults__',
 '__le__',
 '__lt__',
 '__module__',
 '__name__',
 '__ne__',
 '__new__',
 '__qualname__',
 '__reduce__',
 '__reduce_ex__',
 '__repr__',
 '__setattr__',
 '__sizeof__',
 '__str__',
 '__subclasshook__']

在这些方法中,__call__ 是最重要的一种方法:

foo.__call__(42)
# 相当于:
foo(42)
42
42

因为函数是对象,所以函数可以作为参数传入另一个函数

def bar(f, x):
    x += 1
    f(x)
bar(foo, 4)
5

修饰符

修饰符是这样的一种函数,它接受一个函数作为输入,通常输出也是一个函数:

def dec(f):
    print ('I am decorating function', id(f))
    return f
# 将 len 函数作为参数传入这个修饰符函数
declen = dec(len)
# 使用这个新生成的函数
declen([1,2,3])
I am decorating function 2142658442240





3

上面的例子中,我们仅仅返回了函数的本身,也可以利用这个函数生成一个新的函数,看一个新的例子:

def loud(f):
    def new_func(*args, **kw):
        print ('calling with', args, kw)
        rtn = f(*args, **kw)
        print ('return value is', rtn)
        return rtn
    return new_func
loudlen = loud(len)
print(loudlen([10, 20, 30]))
calling with ([10, 20, 30],) {}
return value is 3
3

用 @ 来使用修饰符

Python 使用 @ 符号来将某个函数替换为修饰符之后的函数:

例如这个函数:

def foo(x):
    print(x)

foo = dec(foo)
I am decorating function 2142764553840

可以替换为:

@dec
def foo(x):
    print (x)
I am decorating function 2142734793744

事实上,如果修饰符返回的是一个函数,那么可以链式的使用修饰符:

@dec1
@dec2
def foo(x):
    print (x)
@loud
def foo(x):
    print(x)
foo(12)
calling with (12,) {}
12
return value is None

例子

定义两个修饰器函数,一个将原来的函数值加一,另一个乘二:

def plus_one(f):
    def new_func(x):
        return f(x) + 1
    return new_func

def times_two(f):
    def new_func(x):
        return f(x) * 2
    return new_func

定义函数,先乘二再加一:

@plus_one
@times_two
def times_two_plus_one(x):
    return int(x)
times_two_plus_one(12)
25

修饰器工厂

decorators factories 是返回修饰器的函数,例如:

def super_dec(x, y, z):
    def dec(f):
        def new_func(*args, **kw):
            print (x + y + z)
            return f(*args, **kw)
        return new_func
    return dec

它的作用在于产生一个可以接受参数的修饰器,例如我们想将 loud 输出的内容写入一个文件去,可以这样做:

def super_loud(filename):
    fp = open(filename, 'w')
    def loud(f):
        def new_func(*args, **kw):
            fp.write('calling with' + str(args) + str(kw))
            # 确保内容被写入
            fp.flush()
            fp.close()
            rtn = f(*args, **kw)
            return rtn
        return new_func
    return loud

可以这样使用这个修饰器工厂:

@super_loud('test.txt')
def foo(x):
    print (x)

# 调用foo就会在文件中写入内容
foo(12)
12
import os
os.remove('test.txt')

@classmethod修饰符

Python 标准库中,有很多自带的修饰符,例如 classmethod 将一个对象方法转换了类方法:

class Foo(object):
    @classmethod
    def bar(cls, x):
        print ('the input is', x)
    def __init__(self):
        pass

# 类方法可以通过 类名.方法 来调用:
Foo.bar('yjq')
the input is yjq

@property修饰符

有时候,我们希望像 Java 一样支持 getters 和 setters 的方法,这时候就可以使用 property 修饰符:

class Foo1(object):
    def __init__(self, data):
        self.data = data

    @property
    def x(self):
        return self.data
# 此时可以使用 .x 这个属性查看数据(不需要加上括号):
foo = Foo1(23)
print(foo.x) # 23
# 这样做的好处在于,这个属性是只读的
# 如果想让它变成可读写,可以加上一个修饰符 @x.setter
class Foo2(object):
    def __init__(self, data):
        self.data = data

    @property
    def x(self):
        return self.data

    @x.setter
    def x(self, value):
        self.data = value
foo2 = Foo2(23)
print(foo2.x) # 23
foo2.x = 12
print(foo2.x) # 12
23
23
12

Numpy 的 @vectorize 修饰符

numpyvectorize 函数讲一个函数转换为 ufunc,事实上它也是一个修饰符:

from numpy import vectorize, arange

@vectorize
def f(x):
    if x <= 0:
        return x
    else:
        return 0

f(arange(-10.0,10.0))
array([-10.,  -9.,  -8.,  -7.,  -6.,  -5.,  -4.,  -3.,  -2.,  -1.,   0.,
         0.,   0.,   0.,   0.,   0.,   0.,   0.,   0.,   0.])

注册一个函数

class Registry(object):
    def __init__(self):
        self._data = {}
    def register(self, f, name=None):
        if name == None:
            name = f.__name__
        self._data[name] = f
        setattr(self, name, f)

register 方法接受一个函数,将这个函数名作为属性注册到对象中。

产生该类的一个对象:

registry = Registry()

使用该对象的 register 方法作为修饰符:

@registry.register
def greeting():
    print ("hello world")

这样这个函数就被注册到 registry 这个对象中去了:

registry._data
{'greeting': }
registry.greeting

flask ,一个常用的网络应用,处理 url 的机制跟这个类似。

@wraps

一个通常的问题在于:

def logging_call(f):
    def wrapper(*a, **kw):
        print ('calling {}'.format(f.__name__))
        return f(*a, **kw)
    return wrapper

@logging_call
def square(x):
    '''
 square function.
 '''
    return x ** 2

print (square.__doc__, square.__name__)

None wrapper

我们使用修饰符之后,squaremetadata 完全丢失了,返回的函数名与函数的 docstring 都不对。

一个解决的方法是从 functools 模块导入 wraps 修饰符来修饰我们的修饰符:

from functools import wraps

def logging_call(f):
    @wraps(f)
    def wrapper(*a, **kw):
        print ('calling {}'.format(f.__name__))
        return f(*a, **kw)
    return wrapper

@logging_call
def square(x):
    '''
 square function.
 '''
    return x ** 2

print (square.__doc__, square.__name__)
 square function.
  square

现在这个问题解决了,所以在自定义修饰符方法的时候为了避免出现不必要的麻烦,尽量使用 wraps 来修饰修饰符!

十三、operator, functools, itertools模块

operator模块

import operator as op

operator 模块提供了各种操作符(+,*,[])的函数版本方便使用:

print("加法:1 + 1 = ", op.add(1,1))
print("减法:1 - 1 = ", op.sub(1,1))
print("乘法:1 * 2 = ", op.mul(1,2))
加法:1 + 1 =  2
减法:1 - 1 =  0
乘法:1 * 2 =  2
my_list = [('a', 1), ('bb', 4), ('ccc', 2), ('dddd', 3)]

# 标准排序
print (sorted(my_list))

# 使用元素的第二个元素排序
print (sorted(my_list, key=op.itemgetter(1)))

# 使用第一个元素的长度进行排序:
print (sorted(my_list, key=lambda x: len(x[0])))
[('a', 1), ('bb', 4), ('ccc', 2), ('dddd', 3)]
[('a', 1), ('ccc', 2), ('dddd', 3), ('bb', 4)]
[('a', 1), ('bb', 4), ('ccc', 2), ('dddd', 3)]

functools 模块

functools 包含很多跟函数相关的工具,比如之前看到的 wraps 函数,不过最常用的是 partial 函数,这个函数允许我们使用一个函数中生成一个新函数,这个函数使用原来的函数,不过某些参数被指定了:

from functools import partial
from functools import reduce

# 将 reduce 的第一个参数指定为加法,得到的是类似求和的函数
sum_ = partial(reduce, op.add)

# 将 reduce 的第一个参数指定为乘法,得到的是类似求连乘的函数
prod_ = partial(reduce, op.mul)

print (sum_([1,2,3,4]))
print (prod_([1,2,3,4]))

10
24

itertools 模块

itertools 包含很多与迭代器对象相关的工具,其中比较常用的是排列组合生成器 permutationscombinations,还有在数据分析中常用的 groupby 生成器:

from itertools import cycle, groupby, islice, permutations, combinations

cycle 返回一个无限的迭代器,按照顺序重复输出输入迭代器中的内容,islice 则返回一个迭代器中的一段内容:

print (list(islice(cycle('abcd'), 0, 10)))
['a', 'b', 'c', 'd', 'a', 'b', 'c', 'd', 'a', 'b']

groupby 返回一个字典,按照指定的 key 对一组数据进行分组,字典的键是 key,值是一个迭代器:

animals = sorted(['pig', 'cow', 'giraffe', 'elephant',
                  'dog', 'cat', 'hippo', 'lion', 'tiger'], key=len)

# 按照长度进行分组
for k, g in groupby(animals, key=len):
    print (k, list(g))

3 ['pig', 'cow', 'dog', 'cat']
4 ['lion']
5 ['hippo', 'tiger']
7 ['giraffe']
8 ['elephant']

排列:

for p in permutations('abc'):
    print(p)
('a', 'b', 'c')
('a', 'c', 'b')
('b', 'a', 'c')
('b', 'c', 'a')
('c', 'a', 'b')
('c', 'b', 'a')

组合:

for c in combinations([1,2,3,4], r=2):
    print(list(c))
[1, 2]
[1, 3]
[1, 4]
[2, 3]
[2, 4]
[3, 4]

十四、作用域

在函数中,Python 从命名空间中寻找变量的顺序如下:

  • local function scope
  • enclosing scope
  • global scope
  • builtin scope

例子:

local作用域

def foo(a,b):
    c = 1
    d = a + b + c

这里所有的变量都在 local 作用域。

global 作用域

c = 1
def foo(a,b):
    d = a + b + c

这里的 c 就在 global 作用域。

built-in 作用域

def list_length(a):
    return len(a)

a = [1,2,3]
print (list_length(a))
3

这里函数 len 就是在 built-in 作用域中:

__builtins__.len

class中的作用域

# global
var = 0

class MyClass(object):
    # class variable
    var = 1

    def access_class_c(self):
        print ('class var:', self.var)

    def write_class_c(self):
        MyClass.var = 2
        print ('class var:', self.var)

    def access_global_c(self):
        print ('global var:', var)

    def write_instance_c(self):
        self.var = 3
        print ('instance var:', self.var)
Global MyClass
var = 0
MyClass
access_class var = 1
access_class
obj = MyClass()

查询 self.var 时,由于 obj 不存在 var,所以跳到 MyClass 中:

Global MyClass obj
var = 0
MyClass
[access_class
self]
obj var = 1
access_class
obj.access_class_c()
class var: 1

查询 var 直接跳到 global 作用域:

Global MyClass obj
var = 0
MyClass
[access_class
self]
obj var = 1
access_class
obj.access_global_c()
global var: 0

修改类中的 MyClass.var

Global MyClass obj
var = 0
MyClass
[access_class
self]
obj var = 2
access_class
obj.write_class_c()
class var: 2

修改实例中的 var 时,会直接在 obj 域中创建一个:

Global MyClass obj
var = 0
MyClass
[access_class
self]
obj var = 2
access_class var = 3
obj.write_instance_c()
instance var: 3
MyClass.var
2

MyClass 中的 var 并没有改变。

词法作用域

对于嵌套函数:

def outer():
    a = 1
    def inner():
        print ("a =", a)
    inner()

outer()
a = 1

如果里面的函数没有找到变量,那么会向外一层寻找变量,如果再找不到,则到 global 作用域。

返回的是函数的情况:

def outer():
    a = 1
    def inner():
        return a
    return inner

func = outer()
print(func)
print ('a (1):', func())
.inner at 0x000001F2E6534310>
a (1): 1

func() 函数中调用的 a 要从它定义的地方开始寻找,而不是在 func 所在的作用域寻找。

十五、动态编译

动态执行的时候要注意,不要执行不信任的用户输入,因为它们拥有 Python 的全部权限。

标准编程语言

对于 C 语言,代码一般要先编译,再执行。

.c -> .exe

解释器语言

shell 脚本

.sh -> interpreter

字节码编译

Python, Java 等语言先将代码编译为 byte code(不是机器码),然后再处理:

.py -> .pyc -> interpreter

eval函数

eval(statement, glob, local)

使用 eval 函数动态执行代码,返回执行的值:

a = 1
eval("a+1")
2

可以接收空间参数:

local = dict(a = 2)
glob =  dict(a = 1)
eval("a+1",glob,local)
3

这里 local 中的 a 先被找到。

exec函数

exec(statement, glob, local) 

使用 exec 可以添加修改原有的变量。

a = 1

exec("b = a+1")

print(b)
2
local = dict(a=2)
glob = {}
exec("b = a+1", glob, local)
# 执行之后,b 在 local 命名空间中。
print(local)
{'a': 2, 'b': 3}

complie函数生成byte code

compile(str, filename, mode)
a = 1
c = compile("a+2", "", 'eval')
print(c)
eval(c)
 at 0x000001F2E6F14240, file "", line 1>





3
a = 1
c = compile("b=a+2", "", 'exec')
exec(c)
b
 at 0x000001F2E64CB0E0, file "", line 1>





3

你可能感兴趣的:(python机器学习,python,学习,人工智能)