- 缩进表示将语句分成代码块
if-elif-elseprint(f"{name} is {age} years old")print("asdf",end=xxx),- 读取输入 name = input("enter name :")
- 不像c++,python的空语句会报错,用
pass; 占位时pass和...功能相同,...除了占位的功能外还有空值语义/任意值/任意维度 ...是Ellipsis类的唯一实例,大多数是当临时占位用的- python 无显式声明变量直接使用
- 三元运算: 结果1 if 条件 else 结果2
- del 删除变量/元素
- int 和 str 没法比较
_是一个约定俗成的变量名,表示"我不关心这个变量的值"。for _ in range(5):- python中的函数参数有两种类型:
- 位置参数:
calc(1, 2, '+') - 关键字参数:
calc(a=1, b=2, op='+');cpp里没有这个关键字参数
- 位置参数:
round()四舍五入range()生成整数迭代对象- python的数字类型: bool, int , double, 复数
- c++中的
true/false是bool类型的值,但在python中True/False是数字1/0 - python的整数是动态长度,不会像c++一样有溢出的风险
- python里的双精度浮点数相当于c++里的double,python没有单精度和长双精度
- py中默认的
x/y是返回浮点数,x//y是返回整数(向下取整);c++的x/y返回值是整数还是浮点数取决于xy的类型 x**y阶乘- py和cpp都用
& |表示按位与/或,py用and or not表示逻辑,c++用&& || - py的
abs和cpp的std::abs适用于整数和浮点数,不要用c风格的abs和fabs - int(a) 转换,失败会报错ValueError; c++的转换:抛弃c风格,用 static_cast(a); const_cast(a)仅适合去除指针/引用变量的const属性;dynamic_cast向下转换,继承转换
- python的字符串可以用单引号,双引号,三引号(单引号和双引号作用一样于不跨行的字符串,三引号可多行);
- cpp的单引号是char,双引号才是string
- python在索引区间这方面特别喜欢左闭右开
- str[-2] 倒数第二个, str[-3:] 倒数第三个到最后,str[-3:0]返回空字符串
- 字符窜的连接: "hello"+s
- 字符串的长度: len(s)
- 字符串成员测试:
test = 'e' in s,test = "he" not in s - 字符串复制:
replication = s * 5
- 删除字符串前/后空格:
s.strip()(只能) - 大小写转换:
s.upper(),s.lower()(全部) - 替换字符串内容:
s = "hello world" s.replace("hello","hallo") - s.endswith(suffix) # 检查字符串是否以suffix结尾
- s.find(t) # t在s中首次出现的位置(索引)
- s.index(t) # t在s中首次出现的位置(索引)
- s.isalpha() # 检查字符是否都是字母
- s.isdigit() # 检查字符是否都是数字
- s.islower() # 检查字符是否都是小写
- s.isupper() # 检查字符是否都是大写
- s.join(slist) # 使用s作为分隔符连接字符串列表slist
- s.rfind(t) # 从字符串末尾开始搜索t的位置
- s.rindex(t) # 从字符串末尾开始搜索t的位置
- s.split([delim]) # 使用分隔符delim将字符串拆分为子字符串列表
- s.startswith(prefix) # 检查字符串是否以prefix开头
- 字符串一旦创建后是无法修改的,修改字符串都是新建的,
s[1]='o'是错的 - 将任何值转化成字符串:
str(3) - py查看变量类型:
print(type(s).__name__),print(type(s)),`` isinstance(a,(list,tuple))- 字节串,引号前+b:
data = b"Hello World\r\n" - 原始字符串,引号前+r:
data = r"hello\nworld",作用是让反斜杠\仅作为普通字符生效,反斜杠\不再触发转译字符功能 - 格式化字符窜f-string: f'{变量:格式说明符}'
name = ["sd","fg","kl"]- 注明类型:
name: list[Any]
- append
- insert
-
- 连接两个list
- len(name)
- in /not in
- 复制 name*4
- list 就不是为数学计算设计的,是为了通用数据储存和操作的
for na in name:- 返回第一个出现的索引:
name.index("sd") - remove/del, 删除元素
- reverse 反转
- 就地排序
name.sort()原地修改, 仅list可用,name.sort(key=func)name.sort(reverse=True)生成新的的, 可迭代对象均可使用
na = sorted(name); sorted(iterable, key=func)func(元素)得到排序键值,按照这个排序键值对原元素排序
with open('input.txt', 'rt') as f_read, open('output.txt', 'wt') as f_write:- t文本模式,b二进制模式,r只读模式,r只写模式
- with是python中专门为资源管理设计的,自动可靠的释放资源,不用手动的
file.close() - with是python中的上下文管理器,自动管理资源,确保打开和关闭成对执行
- with的核心:进入 with 块时自动调用 enter,退出时自动调用 exit(无论是否发生异常); 所以任何需要这种“进入-退出”配对操作的场景,都可以用 with,不限于传统意义上的资源管理。
read/write- 按行迭代:
for line in file: with open('outfile', 'wt') as out: print('Hello World', file=out), 使用print输出到文件中相较于file.write("hello world\n")的好处就是自动添加换行符\nnext(file)调用一次迭代一次
def fuc(n):- 可校验参数类型:
def fuc(n:int|double): - 文档字符串必须放在函数/类/模块的第一行
- 参数:
def read_prices(filename: str) -> dict:加上参数类型和返回值类型- 可以使用位置参数调用函数:
prices = read_prices('prices.csv', True) - 或者可以使用关键字参数调用该函数:
prices = read_prices(filename='prices.csv', debug=True) - 默认参数(必须末尾):
def read_prices(filename, debug=False):
return q, r # Return a tuple多的会组成一个元组- 在函数内修改全局变量需要在修改前使用
global xxx - python的函数传参都是传递引用
- 空值类型 :
name = None, 为一个后续会赋值的变量提前占位
tup = ("sd",1,1.1,1),元组,列表都是有序可重复的数据结构;但是list是可变数据结构,而tuple则是只读的不可修改,但是可以根据当前元组元素构造新的元组- 既然都有了list,那tuple设计出来的目的是什么呢: 核心目的是"不可变的",不可变就防止数据篡改,可做字典键/集合元素(只有不可变元素才能被hash)
- 也可省略()
tup = ("sd",)t1,t2,t3,t4 = tup,除了用索引外- tuple是不可变的,所以就没有append
dic = {"as": 1, "qw": "11", "qw": "12"}- 底层hash,与cpp的std::unordered_map实现一致,
无序,现在是有序的了,可修改,重复的会覆盖 - list(dic),字典转化成列表,获得所有的键
for di in dic:迭代获取的是键,不能使用for k,v in dic- dict的in/not in 都是对于键
dic.keys(),获得实时自动跟随变化的键dic.values(),获得实时自动跟随的值- 获得键值对:
for k,v in dic.items():; 字典确实是个可迭代对象,但它迭代是是键 - item 项,物品,元素
dic.get("ass", None):, 找不到有默认值- 不可变的类型 可做dictionary's key,如元组
- 无append,直接写键幅值即可
- 无序,不可重复(会自动去重)
- add
- list 有序可变可重复
- append(x)
- extend(iter) #批量增加
- insert(idx, x)
- pop(idx)
- del lst[idx]
- remove(x)
- clear()
- tuple 有序不可变可重复
- 新建只能
- dict 有序可变不可重复
- d[key] = value
- update({'a':1,'b'=2}) ; update(b=2)
- dic.setdefault(11, 12) 如果存在11,那就返回11对应的value,dic不会改变;如果不存在11,那就返回12,改变dic
- pop(key,default)
- popitem(删除最后插入的键值对,返回键值对元组)
- del dic[key]
- clear()
- set 无序可变不可重复
- add(x)
- update([1,2,3])
- remove(x) 元素不存在则报错
- discard(x) 元素不存在则无操作
- pop() 随机删除一个元素,并返回
- clear()
f"{数据:[对齐符][占宽数字][整数格式编码]}"'{name:>10s} {shares:10d} {price:10.2f}'.format(name='IBM', shares=100, price=91.1)s = { 'name': 'IBM', 'shares': 100, 'price': 91.1 } '{name:>10s} {shares:10d} {price:10.2f}'.format_map(s), 相当于是format的字典(映射)特供版
- string
- list
- tuple
a = [0,1,2,3,4,5,6,7,8] a[2:4] = [10,11,12] # [0,1,10,11,12,4,5,6,7,8],重新分配的切片不必与原来长度相同range(start,stop,step),range(10,0,-1)只能生成整数序列,range(2)生成0-1,单一参数时是stop,并且range(xx)是惰性对象,一般迭代访问/索引访问,想要得到实际数字,用list(xx)转化for n,s in enumerate(se,start):生成counter and iterationenumerate(iterable, start=0), start指定起始索引值ts = zip(tup, se)接收可迭代对象(如:list,string,tuple...)返回的是zip迭代器类型,只能迭代一次;如需多次使用,使用list(ts),tuple(ts)等
Counter是dict字典类的一个子类,访问到开始不存在的键的时候会赋值为零,从而不会报错difaultdict:一个字典的键对应多个值:from collections import defaultdictdeque:固定长度限制,频繁头部操作的双向队列
a = ["sd", 2, 3] b = [2 * x for x in a].- 过滤:
b = [2 * x for x in a if type(x) is int], [ for <variable_name> in if ]
- 代码里的数字、列表、字典、函数等都是「对象」(object); 而「对象模型」(object model)是管理这些对象的 “底层规则手册”
- 赋值变量是地址拷贝
b=a(修改一个值,所有的引用都会改变,但如果直接重新赋值数值,就变成了一个新的变量,和之前的引用就无关了),赋值数值是值拷贝b=10 is比较是不是同一地址;==比较的只是数值是否相等- 和cpp不同,python的变量从不储存数据本身,而是储存数据的地址(引用)
- 赋值有三种: (和cpp的深浅拷贝不同)

- 深拷贝:
import copy
- python中的数字,字符,函数,异常,类,实例...都是对象,都能像数据一样被传递,放入容器,不存在特殊类型,这就是是所谓的"一等对象(first class)"
items = [abs, math, ValueError ]
items[0](-45) # abs
items[1].sqrt(2) # math
except items[2]: # ValueError- 函数名也是对象:
def aa() bb = aa bb() - python中绝大多数原地修改的方法都会返回
None
- propagate 传递
BaseException
├─ KeyboardInterrupt(用户中断)
└─ Exception(程序逻辑错误,也是你大部分需要处理的异常)
├─ ArithmeticError(算术错误)
│ ├─ ZeroDivisionError(除零)
│ └─ OverflowError(数值溢出)
├─ AssertionError(断言失败)
├─ EOFError(文件/输入提前结束)
├─ ImportError(导入失败)
├─ IndexError(索引越界)
├─ KeyError(字典键不存在)
├─ MemoryError(内存不足)
├─ NameError(变量未定义)
├─ ReferenceError(弱引用失效)
├─ RuntimeError(运行时通用错误)
├─ SyntaxError(语法错误)
├─ SystemError(解释器系统错误)
├─ TypeError(类型不匹配)
└─ ValueError(值无效)
- 捕获并处理异常
try:
xxx
except xxx:
xxxexcept除了,表示排除- exception 异常情况,异常
except RuntimeError as e:e是该异常的一个实例- raise:抛出异常
raise RuntimeError("关联值信息") - 异常可关联一个关联值,包含异常更详细的信息,这个关联值是异常的一部分
except Exception:捕获所有异常except LookupError as e: ... except RuntimeError as e:捕获多个异常;或者except (LookupError,RuntimeError) as e:- 重新抛出异常:
raise - 处理不了的异常就不要用try-except捕获
try-except-finally,finally是一定会被执行的,可以用try-with-except的with语法糖代替
print(__name__)运行当前文件时,会输出__main__,不是文件名; 当被其他文件导入时则输出文件名, 用以区分此文件"当作模块导入"和"模块直接运行"- 模块名不能以数字,'-'开头
- 模块是隔离的:
foo.x和foc.x不同 import math as mas关键字from math import sin, 降低臃肿,不用前缀sys.modulessys.path导入模块的搜索路径
-
最先运行的文件就是主模块__main__
-
if __name__ == "__main__":后面写仅需仅想直接运行时执行的代码,不需要被import后执行的代码 -
python不强制是main函数,只是为了对齐其他语言习惯罢了
def main(): ... if __name__ == '__main__': main()
-
basilk
- 命令行参数列表:
sys.argv
- 文件描述符,系统级的特殊文件,能像文件一样读写的特殊对象:
sys.stdoutsys.stdinsys.stderr, 专门的错误输出通道,即使stdout被重定向了,error信息依旧会显示到终端上
print就是在往sys.stdout写内容,是简化的sys.stdout.write("hello world\n")input就是简化的sys.stdin.readline().strip()- 代码重定向:
sys.stdout = f sys.stdout = sys.__stdout__ - shell重定向符号
> - shell管道符号
|, 将上一个命令的sys.stdout作为下一个命令的sys.stdin
- 设置不同的环境变量切换运行环境/传递信息/控制代码行为...
- zsh/bash 设置环境变量:
export learn="ing";查询:print(os.environ["learn"])
- 任意位置直接整个程序.提供错误码,错误信息,
raise SystemExit - raise SystemExit # 退出程序,默认退出码 0
- raise SystemExit(exitcode)# 带退出码退出(非0表示错误)
- raise SystemExit('提示信息') # 带提示信息退出
- 等同于:
sys.exit(exitcode),他的底层就是raise SystemExit(exitcode) sys.exit(0)正常退出,非零异常
- 如何不用
python3 xx.py,直接用文件名xx.py运行呢?:使用#!/usr/bin/env python3,自动查找python3解释器的路径
__init__相当于cpp的构造函数,成员函数是写到__init__函数里的self就是调用这个方法的实例本身,不写这个参数的话,会找不到是哪个实例的成员/方法,所以在类中调用成员/方法时,都需要self.x,self.do()- 在类的方法里调用同类的其他方法时,就不需要手动传 self 参数(Python 会自动帮你传)
class Animals: class Dog(Animals):super()是父类的一个代理对象,不是普通的父类实例,只能调用父类的方法,无法调用父类的成员- 在子类中调用父类的方法:
super().do() - 如果子类有新的成员需要初始化,需要重写__init__函数,用父类的init函数
super().__init__(xxxx) object是最父的父类,class都继承自它- 可多重继承:
class Child(Mother,Father): - python的 访问控制约定 :python里没有private,protected,public这一套访问控制,只能约定俗成._xx是protected, __xx是private
- 直接写函数实现功能当然没问题,但是使用类中的特殊方法会更简便:
# 实现 __add__:让 + 运算符生效
def __add__(self, other):
return Vector(self.x + other.x, self.y + other.y)
# 实现 __str__:让 print() 输出友好
def __str__(self):
return f"Vector({self.x}, {self.y})"
# 实现 __repr__:让交互式解释器输出详细信息
def __repr__(self):
return f"Vector({self.x}, {self.y})"
# 实现 __len__:让 len() 能获取“长度”(这里约定为整数长度)
def __len__(self):
return int((self.x**2 + self.y**2)**0.5)dog.do不加括号只能返回方法对象;dog.do()加上括号才会执行方法- c++这样来说也不报错
do;,获取函数指针但不使用,不报错但没意义
- 可判断,可动态的,静态时等同于
.的属性获取方法getattr,setattr,hasattr,delattr:
getattr(obj, 'name') # 等价于 obj.name
setattr(obj, 'name', value) # 等价于 obj.name = value
delattr(obj, 'name') # 等价于 del obj.name
hasattr(obj, 'name') # 检查属性是否存在- 自定义异常通过类来实现,必须继承自Exception,类通常是空的,因为也用不到成员和方法:
class NetworkError(Exception):
pass- 本节会讲解 Python 对象的一些底层实现逻辑。从其他编程语言转过来的开发者,常常觉得 Python 的类设计 “缺少某些特性”—— 比如: 没有「访问控制」的概念(例如 private、protected 关键字); self 参数的存在总让人觉得怪异; 操作对象时有时感觉像 “无拘无束、毫无规则”。 这些感受确实没错,让我们搞清楚这一切的运作原理,以及一些常用的编程范式 —— 这些范式能更好地封装对象的内部逻辑。
- 字典存放着module里的所有全局变量,函数,类
xxx.__dict__,globals() - 自定义的对象(object)会把「实例数据」和「类本身」都存储在字典中。事实上Python整个对象系统本质上就是构建在字典之上的一层封装。当在
__init__中给self.xxx赋值时,其实就是在填充这个 __dict__字典 - 每个类的实例的私有字典__dict__都是独立的,类实例的__dict__储存着成员变量,类的__dict__储存着成员函数,并且子类不会储存父类的成员函数
print(type(player))
print(player.__class__) # 是一样的- 查找类的父类:
xx.__base__ - 成员函数的查找沿着子类->父类的顺序向上查找(方法解析顺序method resolution order(mro))
@property,@xx.setter使调用者可以像调用成员变量一样调用成员函数(不加括号),(相当于成员函数变成成员变量了):
@property
def shares(self):
return self._shares
@shares.setter
def shares(self, value): # setter的装饰名必须和@property的函数名保持一致
if not isinstance(value, int):
raise TypeError('Expected int')
self._shares = value但我感觉不好,在调用时应该进行区分,像cpp一样写get/set函数
class Stock: __slots__ = ('name','_shares','price') # 只允许这3个属性, 用__slots__元组来代替占内存的__dict__字典,限制类的成员变量只是附带功能,主要用途是提高性能
- 迭代协议iteration protocol:
for x in obj:的底层迭代协议为迭代器__iter__和with循环:_iter = obj.__iter__() # Get iterator object,获得迭代器 while True: try: x = _iter.__next__() # Get next item # statements ... except StopIteration: # No more items break
__iter__是可迭代对象必须实现的方法, 所以自定义可迭代类要自己实现def __iter__(self):, 如果想要自定义可迭代器类实例本身还可以是迭代器,那么就需要实现def __iter__(self):,def __next__(self):next(xx)参数是迭代器,不是可迭代对象- 迭代器是一个可迭代对象,但是可迭代对象不一定是一个迭代器
_iter.__next__()和next(_iter)是一样的
- 关键字yield:让普通的while循环函数变成生成器函数,没有return但是会返回一个生成器对象,在调用__next__()后会暂停while,只输出一个;
- 语法:
yield [表达式],变量 = yield [表达式],可以用[生成器].send(xx)给变量传值def generator(n: int): while n >= 0: yield n - 1 n -= 1 x = generator(10) for i in x: print(i)
- 有列表推导式,字典推导式,集合推导式,没有元组表达式,因为括号被生成器表达式占用了
- 用的时候才会迭代生成元素,不会把所有元素都加载到内存中,仅保存生成规则,所以占用内存极低
- 列表表达式可以反复使用,生成器表达式只能使用一次
(<expression> for i in s if <conditional>)
- 可变位置参数:
def func(xx, *args):, 额外的参数形成一个元组 - 可变关键字参数:
def func(xx=xx, **kwargs):, 额外的参数形成一个字典 *和**是打包/解包的关键字,在函数定义时打包参数,在函数调用时解包可迭代对象(这样的话可以把可迭代对象当作参数传递), 这两个符号,在函数定义中都是打包,在函数调用时都是解包
lambda 参数: 表达式, 没有return,表达式的结果就是返回值
- 内层函数能记住(留住)传入外层函数的参数,形成一个闭包
- 闭包:当内层函数引用了外层函数的变量,且内层函数被返回,外层函数被调用赋值时,python会创建一个闭包closure--把内层函数和外层函数变量绑定在一起
- 这种延迟执行的特性导致很适合做回调函数
- 应用:
- 回调函数
- 延迟执行
- 装饰器函数
# 这是一个装饰器定义函数
def log_decorator(func):
def wrapper():
print("函数开始执行")
func()
print("函数执行结束")
return wrapper
# 使用装饰器
@log_decorator
def hello():
print("Hello")
# 相当于:
# hello = log_decorator(hello)- 工厂模式:把 “对象创建逻辑” 从构造函数中抽离出来,用专门的方法代替负责创建,让代码更清晰灵活。
- 预定义的装饰器:
@staticmethod: 相当于cpp的类静态成员函数static@classmethod: 第一个参数是clscls.count += 1 # 直接操作类属性,子类继承后自动适配- 工厂模式,构建实例
testing rocks, debugging sucks 测试超重要,调试太糟心
assert isinstance(10, int), 'Expected int',用于函数内进行内部自检- 契约式编程: 使用assert在函数的所有输入参数,返回值...上加上断言
- 必须要把要测试的内容写到类中,类函数中,这个类要继承
unittest.TestCase,测试函数名以test_开头,运行时用unittest.main(), 需要用预定义的assert版本
# Assert that expr is True
self.assertTrue(expr)
# Assert that x == y
self.assertEqual(x,y)
# Assert that x != y
self.assertNotEqual(x,y)
# Assert that x is near y, 比较到浮点数小数点后places位
self.assertAlmostEqual(x,y,places)
# Assert that callable(arg1,arg2,...) raises exc ,callable是否抛出异常
self.assertRaises(exc, callable, arg1, arg2, ...)- 无需类,只需
test_开头的函数即可,普通的assert即可,pytest会自动美化报错,执行测试用:python -m pytest (xx.py)或者pytest xx.py
- 用print输出错误信息的话,无法控制什么情况下显示,什么时候不显示,无法写入文件,所以需要用到日志模块:logging
log = logging.getLogger(__name__)log.warning("Couldn't parse : %s", line)log.debug("Reason : %s", e)
- 用 . 代表「当前包」,无需写死包名,直接导入同包内的模块,
from . import 同包内的模块 from .. import 上级包内的模块- 直接运行包内的子模块里的脚本:
python3 -m 包名.子模块脚本 - uv: 代替pip+venv
- 当类实现了
__call__方法, 类的实例就可以当函数一样用obj(xx) - vars(x)等价于x.dict
- 伪随机数生成器(PRNG)是一个确定性的数学算法。 给它相同的“种子”(初始状态),它就会产生完全相同的随机数__序列__, 这就像一个公式:相同的输入 → 完全相同的输出。
- ABC 是 Python 中 Abstract Base Class(抽象基类)的缩写,用于定义不能被实例化的抽象类,强制规定子类重写实现某些方法。
from __future__ import annotations遇到类型注解时先当作字符串存起来等整个文件读完后再进行类型检查- None 在 Python 中既是唯一的值,也用作类型注解中的类型占位符
from typing import Any, NamedTuple, NamedTuple类比tuple更清晰,比class更简单的只读的类
class ReplayBufferSamples(NamedTuple):
observations: th.Tensor
actions: th.Tensor
next_observations: th.Tensor
dones: th.Tensor
rewards: th.Tensor- class xxx(NamedTuple) 就相当于cpp里的结构体
@dataclass class XX也相当于cpp里的带值结构体- isinstance (变量,类型) → 判断这个变量是不是这个类型
NotImplementedError未实现异常,表示这个功能还没有实现- extend有延伸,拓展,批量的意思
- 在同文件夹下导入用
from .xx.xx,from . import xx - 在cpp中函数名相同参数不同属于函数重载, 函数名相同参数相同会报错重复定义;在pyton中函数名相同就会直接覆盖
- 在cpp类中调用一个函数只会在当前类中寻找,不会去父类中寻找(除非制定了父类作用域); python查找完子类后还会自动查找父类
- python中返回值需要用return传递, 不写return就会丢弃调用函数的返回值,隐式添加return None
- numpy.ndarray 是数据类型, numpy.array是创建ndarray类型数据的函数
- python中的/是浮点数除法,//是整数除法(向下取整)
- python中的赋值是赋值地址(引用), cpp的赋值是值拷贝,引用需要用&
- permutation 序列
- amplitude 振幅
- hypotenuse 斜边
- -1是一个占位符, 让框架自动计算该轴的长度.
