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())
(<class 'ZeroDivisionError'>, ZeroDivisionError('division by zero'), <traceback object at 0x000001E0D08C3780>)
标准输入输出流
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)
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.match
和 re.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))
<re.Match object; span=(0, 11), match='hello world'>
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': <function __main__.square(x)>, 'cube': <function __main__.cube(x)>}
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]
高阶函数
以函数作为参数,或者返回一个函数的函数是高阶函数,常用的例子有 map
和 filter
函数:
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)
为 True
的 s
组成的列表,相当于:
[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')
匿名函数
在使用 map
, filter
,reduce
等函数的时候,为了方便,对一些简单的函数,我们通常使用匿名函数的方式进行处理,其基本形式是:
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)
<list_iterator object at 0x000001F2E6382BE0>
__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__())
<list_reverseiterator object at 0x000001F2E638FF70>
6
4
2
迭代器的 __iter__
方法返回它本身:
print(r.__iter__())
<list_reverseiterator object at 0x000002DCC33CD250>
自定义迭代器
自定义一个 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>
<expression>
执行的结果应当返回一个实现了上下文管理器的对象,即实现这样两个方法,__enter__
和 __exit__
:
print (fp.__enter__)
print (fp.__exit__)
<built-in method __enter__ of _io.TextIOWrapper object at 0x000001F2E647F450>
<built-in method __exit__ of _io.TextIOWrapper object at 0x000001F2E647F450>
__enter__
方法在 <block>
执行前执行,而 __exit__
在 <block>
执行结束后执行:
比如可以这样定义一个简单的上下文管理器:
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
即使 <block>
中执行的内容出错,__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'<html>\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))
<class 'function'>
# 查看函数拥有的方法:
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 修饰符
numpy
的 vectorize
函数讲一个函数转换为 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': <function __main__.greeting()>}
registry.greeting
<function __main__.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
我们使用修饰符之后,square
的 metadata
完全丢失了,返回的函数名与函数的 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
包含很多与迭代器对象相关的工具,其中比较常用的是排列组合生成器 permutations
和 combinations
,还有在数据分析中常用的 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
<function len(obj, /)>
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())
<function outer.<locals>.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)
<code object <module> at 0x000001F2E6F14240, file "", line 1>
3
a = 1
c = compile("b=a+2", "", 'exec')
exec(c)
b
<code object <module> at 0x000001F2E64CB0E0, file "", line 1>
3