一、引言
上次我们已经学习了 Python 安装与配置的相关内容。这次将运用已学的编程知识,简要介绍 Python 的 4 个基础模块:数据结构、流程控制、函数用法、类与对象。在开启编程学习之旅前,我们需要明确为什么要学编程,以及该怎么学。
二、编程
为什么学习编程
我们为什么需要编程?文科生学量化真的需要编程吗?个人学习量化需具备编程、数理、金融、交易 4 项技能,其中数理知识门槛相当高,但好在身处 AI 时代 —— 尽管编程有难度,却能借助大模型逐行解释难懂代码、解决报错、阐释复杂概念,借此攻克代码关后,数理(高数、统计、计量)门槛会大大降低,因为大部分复杂数学公式和算法只需理解原理,实际计算可交给编程;而且量化分析本质是 “用数据和模型解决金融问题”,编程是实现这一过程的 “桥梁”,没有编程能力就无法处理海量数据、回测策略、落地实盘,更难在量化竞争中构建优势,所以编程能力是自学量化的核心。
如今在 AI 时代,编程也不再只是程序员的专属技能,而是每个人都应掌握的基本素养。
什么是编程
接下来我会介绍编程的定义与学习前提:**计算机发明后,为指挥它解决问题,“编程”应运而生。它就像人类与计算机的“翻译官”,能将人的意图转化为计算机可执行的指令。**编程的核心目的本质是 “指挥计算机按人类意愿行动、解决特定问题”,具体体现为 沟通(人与计算机的交互)、控制(掌控计算机行为)、自动化(替代人工完成重复任务)。理解这一点要 聚焦于问题解决,而非技术细节,明确编程是工具而非目的,多思考如何用它高效解决实际问题。
而编程本身,是以特定编程语言为媒介,通过向计算机下达清晰准确的步骤指令实现目标的过程。从本质看,它既是 给计算机下指令(像详细告知朋友从家去地铁站的步骤),也是 人与计算机的沟通(以编程语言为桥梁,让计算机理解人的想法),更能帮我们解决问题(不仅写代码,还能锻炼逻辑思维、用创意方法解决问题)。
编程语言
前面我们提到,编程就是给计算机下达指令的过程。那么,计算机该如何听懂我们的指令呢?这就引出了一个关键概念 —— 编程语言。编程语言和人类语言一样,种类繁多,常见的有 Python、Java、C++、JavaScript 等等。
为什么会有这么多编程语言?因为每种语言都有其特定的应用场景与优缺点。但对于我们这些量化入门者来说,建议直接选择 Python。无需在语言选择上纠结,重要的是迈出学习的第一步。
Jupyter Notebook
编程小白入门建议优先用 Jupyter Notebook 作为代码编辑器,它是好用的网页式交互式编辑器,打开网页就能直接输代码并即时运行,操作便捷,还能同步写文档 —— 可在同一文件里写代码注释、学习心得,支持简洁的 Markdown 排版语法,也能通过 LaTeX 写规范数学公式且无需额外下载插件,能边学边做笔记,一键生成网页链接即可转发共享;它还有个突出优势是代码块支持逐行执行,不用写完整个文件再编译运行,后续学算法频繁修改个别参数时,改完只需运行修改后的几行代码或对应代码块,不用重新执行整个文件,能大幅提升效率。
编程平台IDE
处理复杂项目或多文件场景时,Jupyter Notebook 的适用性不足,此时需要使用 IDE(集成开发环境) —— 它是程序员的核心编程工具,就像写文档时选择功能更强的 Word 而非简单的 txt 文件,能提供多种辅助编程的功能,其核心作用是将编程语言代码传递给计算机执行。
计算机仅能理解二进制指令(0 和 1),而 Python 等高级语言更接近人类自然语言。IDE 的重要功能之一,就是将高级语言转换成计算机可执行的二进制指令。常见的 IDE 有 PyCharm、VS Code 等,我常用 PyCharm,学完 Cursor 后会将其与 PyCharm 搭配使用。
用 Cursor 编程时,它就是一个高效的编程平台。这类工具能提供便捷的开发环境,帮助我们专注于编程的核心概念,而无需过多操心技术细节。关于 Cursor 的编程说明,可参考下方链接:
Cursor 实操指南(https://www.pandaai.online/community/article/164)
如何开始
我们学编程的最终目的是服务量化实践,无需为成为专业程序员过度投入,重点是理解技术核心逻辑、掌握工具用法,让技术助力想法落地、解决量化领域的实际问题。因此,不必纠结技术细节,核心是要抓住“用技术解决问题”的思维。
虽然 AI 迭代可能改变编程方式,但底层逻辑和解决问题的思维始终是核心;没必要像啃编程书卡在第一章、背词典从“abandon”开始那样低效学习,更高效的方式是带着实际需求学习,遇到问题就问 AI、边用边学。
三、一起学习 Python吧!
为了学习量化,既然已经决定学习 Python,那我们就正式开始吧。
关于 Python 的安装,可以参考下方链接中的说明:
安装Python 开发体系(https://www.pandaai.online/community/article/122)
然后,我们用 pip install jupyter notebook
命令安装好相关库后,在 CMD(命令提示符)中直接输入 jupyter notebook
命令,就能启动 Notebook 了。
打开 Notebook 后,我们可以直接在单元格中编写代码,之后按 Ctrl+Enter 键,就能运行选中的单元格了。
Python 逻辑
其实 Python,或者说任何一门编程语言,核心目的都是 “给计算机‘讲故事’”—— 也就是传递我们的指令。而它的基础逻辑,无外乎 “按顺序执行、按条件判断、按需求重复” 这三类。
人们通过按顺序(in sequence)组织句子、根据条件(by conditions)调整叙事和适当重复(with repetitions)关键内容这三种方式来编织生动的故事。类比到 Python 编程中,编写程序同样需要掌握三类基础逻辑:按顺序编写语句(流程控制的基础)、通过条件判断灵活调整执行路径(如 if
语句)、利用循环结构高效处理重复任务(如 for
/while
循环)。
学习语言需先掌握词汇与语法这两大基石,进而通过组合词汇形成句子,最终以句子为单元编织出完整的故事(或思想表达)。
(1)、词汇
1. 关键字(也称“保留字”)
与人类语言不同,Python 的关键字(keyword)数量相当少。之所以称这些词汇为 “关键字”,是因为它们在 Python 语言体系中具有特定的语法意义,不能被随意用作变量名、函数名等标识符。
2. 变量名
与人类语言的表达逻辑相似,在编写 Python 代码时,我们可以自主创建一类具有指代意义的词汇 —— 即变量名(variable name)。变量的命名自由度较高,核心规则是不能与 Python 保留字重名,同时需符合标识符的基础规范(如不能以数字开头、不能包含特殊符号等)。不过,在掌握保留字知识和变量命名规则后,我们还需要通过 “正确的语法”,将这些 “词汇”(关键字、变量名等)组织成可执行的代码逻辑(类似 “句子” 的功能)。
(2)语法:
语法(syntax)指规范一门语言中句子结构、字词顺序的一整套规则与流程。汉语和英语有各自的语法,Python 作为编程语言也不例外。以一个最常见的语法规则为例:字符串必须用引号包裹,若未按此规则编写,程序运行时就会报错。
定义字符串时,使用单引号或双引号均可,但前后引号必须匹配。例如上图中最后一个示例缺少后引号,运行程序时就会触发语法错误。Python 程序报错时,会自动输出错误信息,帮助我们定位问题。
(3)、句子和故事:
1、句子
在 Python 中,“句子” 被称为语句(statement),它可以是一次变量命名、一次数值计算,或是一条执行特定操作的指令。就像一段故事由多个句子串联而成,一段程序(program) 也由多条语句组成 —— 从这个角度来说,编写程序就如同 “给计算机讲一个有逻辑的故事”。
Python 中的语句主要分为两类:
- 简单语句:通常仅需一两行代码即可完成,例如
a = 10
(变量赋值)、print("hello")
(打印内容); - 复合语句:一般包含多行代码,且带有嵌套结构,例如
if...else
(条件判断)、for
循环(重复执行)等。
2、故事
我们讲故事时,常用三种核心逻辑:按顺序叙述、按条件分支叙述(比如 “如果… 就…,否则…”)、按重复情节叙述(比如 “每天都做…”)。这三种逻辑对应到程序中,就是 “流程控制” 的核心思想 —— 后续介绍 “流程控制” 模块时,会对此展开更详细的说明。
接下来,我将从数据结构、流程控制、函数用法、面向对象(类与对象) 四个核心模块,系统介绍 Python 的基础知识。
(一)、数据结构
学习任何编程语言,首先都应该接触其数据结构。在 Python 中有两种通用数据类型:元素型(element type)和容器型(container type)。
元素型包括整数型(int)、浮点型(float)、复数型(complex)、布尔型(bool)和 None 型,它们只存储一个值。容器型包括字符串(str)、元组(tuple)、列表(list)、字典(dict)和集合(set),它们存储一组值。
容器型数据都由特定符号包裹:引号包裹字符串,方括号包裹列表,圆括号包裹元组,大括号包裹字典和集合。此外,Python 还有专为特殊场景设计的容器型数据结构 ——Collections 模块。
1、表达式
表达式(expression)由常量、变量和运算符组成,是程序中类似“句子”的基本结构——数据(分常量与变量,其值可否修改为划分依据)本身无独立价值,需通过运算符操作或比较才能发挥作用,而数据与运算符的结合便形成了表达式。
# 表达式
x=1
y=10 * x * (1.2 + x)
# 常量:1, 10, 1.2
# 变量:x, y
# 运算符:+,*
变量:
从规则上来说,变量可以使用任意名称,但需满足两个核心条件:
- 名称不能是 Python 的关键字(如
if
、for
、lambda
等); - 名称不能以数字开头(如
1stock
、2_bonds
等均为不合法变量名)。
除了避免使用上述 “不合法” 的变量名,建议使用可读性强的变量名(例如用 user_age
表示 “用户年龄”,而非 x
或 a1
),方便后续代码的阅读与维护。
Python VS C语言
这里简单说明:在 Python 中创建变量时,不需要像 C 语言那样在变量前指定变量类型,两种语言创建变量的语法存在明显区别。通过对比可见,Python 定义变量时无需声明数据类型,因此 Python 属于动态类型(dynamic typed)语言。
看到这里,你可能会觉得 Python 不够严谨,甚至产生 “定义变量怎么都不用带变量类型呢” 这类疑问。其实原因很简单:Python 中的变量本质上只是一个 “名字”。就像 x
,它仅仅是一个变量名,作用是 “指向” 一个引用对象 PyObject
;而 PyObject
本质是计算机分配的一块内存,包含类型、大小、引用计数等属性。
总而言之,对初学者而言,Python 会更简单 —— 无需考虑复杂的变量类型声明,直接给变量赋值即可开始使用。
运算符:
运算符(operator)用于操作和比较数据,具体可分为七类,分别是算术运算符、按位运算符、比较运算符、赋值运算符、逻辑运算符、身份运算符和成员运算符。
# 算术运算符
x=(4 + 6) * (5 - 3)
print(x) # 输出 20
# 按位运算符
a = 10
b = 7
print( a & b ) # 输出 0b10
print(bin(a & b)) # 输出 0b10
# 比较运算符
print(11 % 2 == 1) # 输出 True
a = 11
print(10 < a <= 15) # 输出 True
# 赋值运算符
a = 95
print(a) # 输出 95
# 逻辑运算符
a = 6
print((a > 4) and (a < 8)) # 输出 True
# 身份运算符
a = 1
b = 1
print( a == b ) # 输出 True
print( a is b ) # 输出 True
# 成员运算符
print(1 in [1, 2, 3]) # 输出 True
print(1 not in [1, 2, 3]) # 输出 False
运算优先级:
在运算级别相同时,按从左到右的顺序计算;不同级别时,则遵循 PEMDAS 原则。
PEMDAS 由 Parantheses(括号)、Exponential(指数)、Multiply(乘法)、Divide(除法)、Add(加法)和 Subtract(减法)六个英文单词的首字母组成,其字母顺序直观体现了运算的优先级规则,具体遵循以下优先顺序:
简单来说,PEMDAS 本质上和我们学数学时常用的 “运算优先级规则” 是一致的。
下面将分别介绍元素型(element type)数据和容器型(container type)数据。
2、元素型数据
整数 :
整数(integer,Python 中对应类型为 int
)是最简单的数据类型之一,它与浮点数(float
)的核心区别在于:整数的小数点后无有效数值(如 5
、-10
),而浮点数的小数点后带有有效数值(如 5.0
、-10.5
)。
需要特别说明的是:在 Python 中 “万物皆对象”,整数也不例外。只要是对象,就会包含相应的字段(fields) 和方法(methods),这两者统称为对象的属性(attributes)。
还有,任何对象都有内存地址,用 id(object)
函数用于获取对象的内存地址;任何对象都有类型, type(object)
函数用于获取对象的类型;任何对象都有值,用 print(object)
函数用于获取对象的值。
# 整数型
a = 2025
print(id(a)) # 输出 140410028173296
print(type(a)) # 输出 <class 'int'>
print(a) # 输出 2025
浮点型:
浮点型(floating point,Python 中对应类型为 float
)可存储小数,其涵盖范围大致对应数学中的实数。举例如下:f = 1.
—— 在整数 1
后加一个小数点,即可创建浮点型数据。我们可以通过之前提到的函数,查看浮点数 f
的内存地址、类型和值。
# 浮点型
print(1,type(1)) # 输出 1 <class 'int'>
print(1.,type(1.)) # 输出 1.0 <class 'float'>
x = 1/3
print(x) # 输出 0.3333333333333333
print(type(x)) # 输出 <class 'float'>
复数型:
复数(complex number,Python 中对应类型为 complex
)是由实部(real part)和虚部(imaginary part)组成的数字,因此其范围比实数更广。创建复数有两种方法:一是使用 complex()
类,二是直接给虚部加 j
后缀(如 3 + 2j
)。
# 复数型
c=3+4j
print(c) # 输出 (3+4j)
print(type(c)) # 输出 <class 'complex'>
布尔型:
布尔型(boolean,Python 中对应类型为 bool
)变量仅可取两个值:True
和 False
。例如定义两个变量:T = True
、F = False
。除了直接赋值 True
或 False
,还可通过 bool(X)
函数创建布尔变量,其中 X
既可以是元素型数据(如整数、浮点数),也可以是容器型数据(如列表、字符串)。
需要注意的是,若将布尔变量用于数字运算,True
会对应数值 1
,False
会对应数值 0
。因此 T + 2
的结果为 3
,F + 10.31
的结果为 10.31
(原句中 T + 2
结果表述有误,此处修正)。
此外,使用比较操作符(==
、!=
、>
、<
、>=
、<=
)对变量或常量进行比较时,会返回布尔值。后续将介绍的 “条件判断”,正是通过 “变量或常量之间的比较” 来构建,再根据比较结果为 True
或 False
,决定是否执行对应代码块。
布尔值也可通过逻辑操作符(and
、or
、not
)进行运算:多个简单条件配合逻辑操作符,可组合成复合条件(compound conditions)。
# 布尔型
T = True
F = False
print( 1 == 2025 ) # 输出 False
print( 1 != 2025 ) # 输出 True
print( 1 > 2025 ) # 输出 False
print( 1 < 2025 ) # 输出 True
print( 1 >= 2025 ) # 输出 False
print( 1 <= 2025 ) # 输出 True
print( T and T ) # 输出 True
print( T and F ) # 输出 False
print( F and T ) # 输出 False
print( F and F ) # 输出 False
# 整数型和浮点型变量 --> 布尔型变量
print( T + 2 ) # 输出 3
print(type(0), bool(0),bool(1)) # 输出 <class 'int'> False True
print(type(10.31),bool(0.00),bool(10.31)) # 输出 <class 'float'> False True
None型:
Python 中还有一种特殊的数据类型 ——None 型(对应类为 NoneType
),它仅有一个取值:None
。None
是不可修改的关键字,本质是一个特殊对象,需特别强调:None
不等于 False
,不等于 0
,也不等于空白字符串(""
)。在实际使用中,None
通常用于表示 “缺失值”:例如一个函数若未明确返回任何内容(即缺失返回值),则默认返回 None
。
# None型
print(type(None)) # 输出 <class 'NoneType'>
print(None == False) # 输出 False
print(None == 0) # 输出 False
print(None == '') # 输出 False
3、容器型数据
上一节介绍的整型、浮点型、复数型和布尔型,都属于 “单一值数据”(即元素型数据);若将这些单一值数据放入 “容器”,就能得到 “容器型数据”。例如:
- 字符串是存储字符(char)的容器。需要注意的是,Python 中没有单独的
char
类型,通常会将长度为 1 的字符串(如'a'
)视为字符来使用。 - 元组、列表、字典和集合是可存储任意类型变量的容器(既可以存元素型数据,也可以存其他容器型数据)。
字符串:
字符串(string,Python 中对应类型为 str
)是若干字符组成的序列,本质上可看作字符的容器。创建字符串(包括单行和多行字符串)有以下五种常用方法:
- 单引号
'
:s = 'Python'
- 双引号
"
:s = "Python"
- 内置函数
str()
:例如s = str(123)
(将数字转为字符串) - 三单引号
'''
:用于创建多行字符串,例如s = '''第一行内容 第二行内容'''
- 三双引号
"""
:功能与三单引号一致,例如s = """第一行内容 第二行内容"""
字符串通过索引获取单个字符(从0开始计数,支持负索引反向计数),通过切片获取多个连续字符(语法s[start:stop]
,左闭右开,含起始索引字符、不含结束索引字符)。
此外,字符串常用操作分为两大类:
- 内容处理(分合查替):如分割、拼接、查找、替换;
- 格式调整(格式化):如大小写转换、删除/补充字符、格式化输出等。
具体方法可通过dir(str)
查看。
# 创建字符串
s = 'PandaAI!'
print(s,type(s)) # 输出 PandaAI! <class 'str'>
print('ID:',id(s)) # 输出 ID: 140012029770800
print('Type:',type(s)) # 输出 Type: <class 'str'>
print('Value:',s) # 输出 Value: PandaAI!
# 切片和索引
print(s[0]) # 输出 P
print(s[-5]) # 输出 d
print(s[1:4]) # 输出 and
print(s[-4:-1]) # 输出 aAI
print(s[:3]) # 输出 Pan
print(s[5:]) # 输出 AI!
print(s[:]) # 输出 PandaAI!
print(s[1:5:2]) # 输出 ad
print(s[::2]) # 输出 PnaI
print(s[::-1]) # 输出 !IAadnaP
# 分合查替:分隔、合并、查找、替换
s = 'Stock price: 1558'
s.partition(':') # 输出 ('Stock price', ':', ' 1558')
s.split(':')# 输出 ['Stock price', ' 1558']
list1 = ['Python', 'is', 'simple']
','.join(list1)# 输出 'Python,is,simple'
s = 'I am learning machine learning'
s.find('learning')# 输出 5
s.find('Learning')# 输出 -1
s = 'Python for finance'
s.replace('f','F')# 输出 'Python For Finance'
# # 调大小写
s = 'python for finance'
s.upper() # 输出 'PYTHON FOR FINANCE'
s.lower() # 输出 'python for finance'
s.capitalize() # 输出 'Python for finance'
s.title() # 输出 'Python For Finance'
# # 删处补充
s = '*****Python for finance**'
s.strip('*') # 输出 'Python for finance'
s = 'Finance'
s.center(10,'*') # 输出 '*Finance**'
'1234'.zfill(8) # 输出 '00001234'
# 格式化
print(f'this is a float {83.1031:.8f}') # 输出 this is a float 83.10310000
# 运算符
'love' + ' ' + 'you' # 输出 'love you'
'love '*3 # 输出 'love love love '
'Python' > 'R' # 输出 False
'P' in 'Python' # 输出 True
'py' is 'py' # 输出 True
列表:
列表(list,Python 中对应类型为 list
)是Python中存储有序对象的可变容器,用中括号[]
和逗号,
定义(如[元素1, 元素2,...]
),也可通过list()
将字符串/元组转换而来(如list("Python")
→['P','y','t','h','o','n']
)。
基础操作:通过索引(从0开始,支持负索引)和切片(语法[start:stop]
,左闭右开)访问元素,规则与字符串一致。
核心特性:列表是可修改(mutable)类型,操作(增删改查等)不会改变内存地址(id
不变),而不可变类型(如字符串)操作后会生成新对象。
常用操作分类(通过dir(list)
查看全部方法):
- 加:
append(单个元素)
(尾部添加)、extend(可迭代对象)
(尾部批量添加)、insert(索引, 元素)
(中间插入)。 - 减:
remove(元素值)
(删首个匹配值)、pop(索引)
(删指定索引元素,可选默认末尾)。 - 排:
sort()
(原地升序/降序排序)、reverse()
(原地反转索引顺序)。 - 更:按索引(如
l[0]=新值
)或切片(如l[1:3]=[新值1,新值2]
)更新元素。
运算符支持:支持+
(列表拼接)、*
(列表复制拼接)、in/not in
(成员判断)、is/is not
(身份判断),比较运算符按元素顺序逐一比对。
# 创建列表
l = [1, 10.31, 'Python']
print(l) # 输出 [1, 10.31, 'Python']
list((1, 10.31, 'Python')) # 输出 [1, 10.31, 'Python']
list('Python') # 输出 [1, 10.31, 'Python']
# 切片和索引
l = [7, 2, 9, 10, 1, 3, 7, 2, 0, 1]
l[0] # 输出 7
l[-1] # 输出 1
print(l[3:]) # 输出 [10, 1, 3, 7, 2, 0, 1]
print(l[-4:]) # 输出 [7, 2, 0, 1]
print(l[2:4]) # 输出 [9, 10]
print(l[1:5:2]) # 输出 [2, 10]
print(l[:5:2]) # 输出 [7, 9, 1]
print(l[1::2]) # 输出 [2, 10, 3, 2, 1]
print(l[::2]) # 输出 [7, 9, 1, 7, 0]
print(l[::-1]) # 输出 [1, 0, 2, 7, 3, 1, 10, 9, 2, 7]
#常用方法
len(l)# 输出 3 列表的长度
# 加
l = [1, 10.31, 'Python']
l.append([4, 3])
print(l) # 输出 [1, 10.31, 'Python', [4, 3]]
l.extend([True, 'OK'])
print(l) # 输出 [1, 10.31, 'Python', [4, 3], True, 'OK']
l.insert(1, 'abc')
print(l) # 输出 [1, 'abc', 10.31, 'Python', [4, 3], True, 'OK']
# 减
l.remove('Python')
print(l) # 输出 1, 'abc', 10.31, [4, 3], True, 'OK']
p = l.pop(3)
print(p) # 输出 [4, 3]
print(l) # 输出 [1, 'abc', 10.31, True, 'OK']
# 排序
l = [7, 2, 5, 1, 3]
l.sort()
print(l) # 输出 [1, 2, 3, 5, 7]
l = [7, 2, 5, 1, 3]
l.sort(reverse=True)
print(l) # 输出 [7, 5, 3, 2, 1]
l = [7, 2, 5, 1, 3]
l.reverse()
print(l) # 输出 [3, 1, 5, 2, 7]
# 更新
l = [7, 2, 5, 1, 3]
l[2] = 100
print(l) # 输出 [7, 2, 100, 1, 3]
l[1:4] = [0,0,0]
print(l) # 输出 [7, 0, 0, 0, 3]
# 运算符
[1, 10.31, 'Python'] + ['Finance', None] + ['OK'] # 输出 [1, 10.31, 'Python', 'Finance', None, 'OK']
['OK']*3 # 输出 ['OK', 'OK', 'OK']
['Finance', 11] > ['Data', 11] # 输出 True
['Finance', 11] == ['Finance', 11] # 输出 True
1 in [1, 10.31, 'Python'] # 输出 True
['Finance', 11] is ['Finance', 11] # 输出 False
元组:
元组(tuple,Python 中对应类型为 tuple
)是Python中存储有序对象的不可变(immutable)容器,定义语法为t = (元素1, 元素2,…)(小括号可选,但建议保留;逗号是核心符号)。灵活定义方式包括:省略小括号(如t = 1, 2.5, ‘Python’)、通过tuple()转换其他可迭代对象(如tuple([1,2,3]))。注意:单元素元组需加逗号(如(‘OK’,)),否则会被识别为普通类型(如字符串)。
基础操作:索引(从0开始,支持负索引)和切片(语法t[start:stop],左闭右开)与列表/字符串规则完全一致(如t[0]取首元素,t[1:3]取中间片段)。
核心特性:
-
不可修改:不能重新赋值元素、增删元素或排序(操作如t[0]=100、t.append()会报错)。
-
例外情况:
• 元组变量可整体重新赋值(如t = (4,5,6),原元组被回收);• 若元组包含可变元素(如列表),其内部内容可修改(如t[1].append(3)),但元组结构(元素类型/数量)不变。
运算符支持:与列表类似,支持+(拼接)、*(复制拼接)、in/not in(成员判断)、is/is not(身份判断);比较运算符按元素顺序逐一比对。
特殊用途:解包(unpack)——将元组元素一次性赋值给多个变量(如a, b = (1, 2)),支持用_占位忽略无用值,或用*接收剩余/中间多余元素(如a, b, *c = (1,2,3,4)→c=[3,4])。
# 创建元组
t1 = (1,10.31, 'Python')
print(t1, type(t1) )
t2 = 1, 10.31, 'Python'
print(t2, type(t2))
t3 = tuple((1, 10.31, 'Python'))
print(t3, type(t3))
# 输出:(1, 10.31, 'Python') <class 'tuple'>
t = (1, 10.31, 'Python')
t.count('Python') # 1
t.index('Python') # 2
# 索引和切片
print(t[0], type(t[0])) # 1 <class 'int'>
print(t[1], type(t[1])) # 10.31 <class 'float'>
print(t[2], type(t[2])) # Python <class 'str'>
# 不可修改
t = (3, 2, 1)
t[0] = 10 # 报错
t.append(0)# 报错
t.remove(2)# 报错
# 打散赋值
a, b = 1, 2
print(a) # 1
print(b) # 2
a, _ = 1, 2
print(a) # 1
a, b, *c = 1, 2
print(a)
print(b)
print(c) # []
a, b, *c = 1, 2, 3, 4, 5
print(a)
print(b)
print(c)# [3, 4, 5]
a, b, *c, d = 1, 2, 3, 4, 5
print(a)
print(b)
print(c) # [3, 4]
print(d)
a, b, *_, d = 1, 2, 3, 4, 5
print(a)
print(b)
print(d) # 5
# 运算符
print(type(('OK'))) # <class 'str'>
print(type(('OK',))) # <class 'tuple'>
(1, 10.31, 'Python') + ('Finance', None) + ('OK',) # (1, 10.31, 'Python', 'Finance', None, 'OK')
('OK',)*3 # ('OK', 'OK', 'OK')
(0, 1, 2) < (5, 1, 2) # True
('Finance', 11) is ('Finance', 11) # True
字典:
字典(dictionary,Python 中对应类型为 dict
)是Python中存储无序键值对的容器,定义语法为d = {键1: 值1, 键2: 值2,...}
(大括号包裹键值对,逗号分隔,冒号关联键值)。核心特性是通过键(key)快速定位对应的值(value)(如字典的“词语-释义”关系)。
基础操作:
- 定义:键值对用
:
分隔,多个键值对用,
分隔,大括号{}
明确边界。示例:d = {'名字':'贵州茅台', '代号':'600519'}
。 - 内置方法:
keys()
:返回所有键的集合(类型dict_keys
,可转列表);values()
:返回所有值的集合(类型dict_values
);items()
:返回所有键值对的集合(类型dict_items
,每个元素为(键, 值)
元组)。
键的核心规则:
- 不可修改:仅支持不可变类型(如整数、字符串、元组等),不可变类型哈希值固定,能保证键唯一性;列表、字典等可变类型不可作为键(修改后哈希值变化)。
- 唯一性:同一字典中键不能重复,后定义的键值会覆盖前者(如
{'价格':1155, '价格':1200}
→最终d['价格']
为1200)。
判断数据类型可修改性的方法:
id(X)
对比:修改数据后,若id
变化则为不可变类型(如字符串、元组);若id
不变则为可变类型(如列表、字典)。hash(X)
函数:可哈希(不报错)说明是不可变类型(如hash('Python')
);不可哈希(报错)说明是可变类型(如hash([1,2])
会报TypeError
)。
字典与列表的异同:
特性 | 字典(Dict) | 列表(List) |
---|---|---|
存储形式 | 无序键值对(键: 值 ) |
有序元素(按索引排列) |
访问方式 | 通过键(d[键] ) |
通过整数索引(l[索引] ) |
核心优势 | 按唯一标识快速查找(效率高) | 按顺序存储/遍历(适合批量) |
可修改性 | 可修改(值/新增/删除键值对,键不可改) | 可直接增删改元素 |
适用场景 | 用户信息、配置项等需键关联的数据 | 日志、批量数据等顺序数据 |
常用操作:
- 添加:
d[新键] = 新值
(键不存在时新增); - 删除:
del d[键]
或d.pop(键)
(后者返回被删的值); - 更新:
d[已有键] = 新值
(覆盖原值); - 获取:
d[键]
(键不存在时报错)或d.get(键, 默认值)
(键不存在时返回默认值,避免报错)。
简言之,字典以键值映射为核心,适合高效检索;列表以顺序存储为核心,适合批量处理。两者均可动态操作数据,但适用场景不同。
# 创建字典
d = {
'名字' : '海底捞',
'代号' : '06862',
'行业' : '食品',
'价格' : 29.1,
'单位' : 'HKD'
}
print('Type:',type(d)) # Type: <class 'dict'>
print('Value:',d)
print(d.keys()) # dict_keys(['名字', '代号', '行业', '价格', '单位'])
print(d.values()) # dict_values(['海底捞', '06862', '食品', 29.1, 'HKD'])
print(d.items()) # dict_items([('名字', '海底捞'), ('代号', '06862'), ('行业', '食品'), ('价格', 29.1), ('单位', 'HKD')])
# 索引
d['价格'] = d['价格'] + 1
# 添加
d['国家'] = '中国'
# 获取
d.get('代号') # '06862'
# 更新
d.update( {'价格': 30.2, '董事长': '张勇'} )
# 删除
item = d.pop('董事长')
print(item) #输出 张勇
print(d) # {'名字': '海底捞', '代号': '06862', '行业': '食品', '价格': 30.2, '单位': 'HKD', '国家': '中国'}
d.clear()
print(d) # {}
集合:
集合(set,Python 中对应类型为 set
)是无序且唯一的容器,用s = {元素1, 元素2,...}
定义(空集合需用set()
,{}
是空字典)。支持将列表/字符串等可迭代对象转为集合(自动去重)。
核心特性:
- 无序:元素无固定位置,不支持索引;
- 唯一:自动去重;
- 快速判断:用
in
/not in
检查元素是否存在。
核心操作:
- 数学运算:并集(
|
/union
)、交集(&
/intersection
)、差集(/difference
)、对称差集(^
/symmetric_difference
); - 关系判断:互斥(
isdisjoint
)、包含(issubset
/issuperset
); - 增删改:
add()
添加、remove()
/discard()
删除、pop()
随机删、clear()
清空。
简言之,集合适合去重、快速查找及集合运算场景。
# 创建集合
A = set(['u', 'd', 'ud', 'du', 'd', 'du'])# 输出 {'d', 'du', 'u', 'ud'}
B = set(('d', 'dd', 'uu', 'u'))# 输出 {'d', 'dd', 'u', 'uu'}
C = {'d', 'dd', 'uu', 'u'}# 输出 {'d', 'dd', 'u', 'uu'}
D = set('finance') # 输出 {'a', 'c', 'e', 'f', 'i', 'n'}
# 集合的两个特点:无序性和唯一性
A[1] # 输出 报错 没法通过索引来访问集合中的元素。
'u' in A # 输出 True
'du' in B # 输出 False
# 集合运算
A = {1, 3, 5, 7, 9}
B = {2, 3, 5, 7}
# 并集
print(A.union(B))
print(A | B)
# 交集
print(A.intersection(B))
print(A & B)
# 补集
print(A.difference(B) )
print(A - B)
print(B.difference(A))
print(B - A)
# 对称差集
print(A.symmetric_difference(B) )
print(A ^ B)
print( B.symmetric_difference(A) )
print(B ^ A)
# 互斥
C = A - B
D = B - A
print(C.isdisjoint(D))
print(A.isdisjoint(B))
# 超集
print(A.issuperset(C))
print(A >= C )
print(A > C )
# 子集
print(D.issubset(B))
print(D <= B)
print(D < B)
# 更新集合
A = {1, 3, 5, 7, 9}
B = {2, 3, 5, 7}
A.update(B)
print(A) # 输出 {1, 2, 3, 5, 7, 9}
A = {1, 3, 5, 7, 9}
A.intersection_update(B)
print(A) # 输出 {3, 5, 7}
A = {1, 3, 5, 7, 9}
A.difference_update(B)
print(A) # 输出 {1, 9}
A = {1, 3, 5, 7, 9}
A.symmetric_difference_update(B)
print(A) # 输出 {1, 2, 9}
# 添加元素
A = {1, 3, 5, 7, 9}
A.add(11) # 输出 {1, 3, 5, 7, 9, 11}
# 删除元素
A.remove(1)# 输出 {3, 5, 7, 9, 11}
A.remove(2)# 输出 报错
A.discard(2) # 删除元素 x, 如果该元素不在集合里不会报错
p = A.pop()
print(p)# 输出 3
A.clear()# 输出 set()
Collections:
在讲解 collections
之前,我们先思考一个基础问题:为什么要设计 collections
模块?字符串、列表、元组、字典、集合这 “五大金刚” 难道不够用吗?事实上,collections
的出现,正是为了解决 “五大金刚” 在特定场景下的功能痛点 —— 已总结了这五种容器型数据的核心功能,可结合参考。
回顾这五种容器型数据的特征,我们能清晰理解 collections
各类数据结构的设计逻辑,读者可先思考以下对应关系:
- 元组可读性较差但不可修改,字典可读性强且支持修改,将两者特性结合,便有了命名元组(namedtuple);
- 操作字典时,若访问不存在的键会直接报错,为解决这一问题、让不存在的键返回默认值,便有了默认字典(defaultdict);
- 计数器(Counter) 是字典的子类,核心优势是能快速统计元素的出现次数,无需手动编写计数逻辑;
- 普通列表仅支持从尾部高效添加元素,为补充 “从头部快速添加 / 删除元素” 的需求,便有了双端队列(deque);
- 对多个字典同时进行操作时,常规方法效率较低,为简化多字典联合操作,便有了链式映射(ChainMap);
- Python 3.7 之前的普通字典无法记录元素的插入顺序,为满足 “记录元素放入顺序” 的需求,便有了有序字典(OrderedDict)(注:Python 3.7 后普通字典已支持有序,但
OrderedDict
仍有特殊场景优势)。
可见,带着 “解决原有容器痛点” 的思路学习 collections
,不仅目的性更强,也更容易记住每种数据结构的核心特征。关于 collections
的详细使用方法,此处不再展开,若有需求可参考 Python 官方文档或专业教程。
对我个人而言,目前掌握上述五种基础容器型数据,已能满足大部分常规开发需求。
(二)、流程控制
流程(flow)指多个业务步骤协同完成完整业务行为的过程。在代码中,除顺序执行外,常需灵活逻辑(如条件判断、重复执行),这依赖控制流程实现。
控制流程(control flow)的核心机制:
-
条件执行:用if语句(包括if、if…else、if…elif…else)按条件选择执行代码块(如判断分数是否及格);
-
重复执行:
• while循环:不定次数重复(满足条件持续执行);• for循环:定次数重复(按范围或可迭代对象遍历,如计算1到100总和);
异常处理:当代码运行出错(如类型不匹配、索引越界)时,通过识别错误类型并纠正/规避,重新获取程序控制权。
核心场景分为两类:
• 选择执行(条件语句):根据条件执行不同任务分支;
• 重复执行(循环语句):持续执行任务直至满足停止条件。
1、条件语句:
条件语句(conditional statement)通过判断布尔值(True/False)决定执行哪段代码,根据条件数量分为三种形式:
- 单支(if):仅当条件为 True 时执行代码块(条件为 False 则跳过)。
- 双支(if-else):条件为 True 时执行 if 块,为 False 时执行 else 块,覆盖二元决策(如“及格/不及格”)。
- 多支(if-elif-else):通过多个 elif 处理三种及以上并列条件(如成绩等级划分),若所有条件均不满足则执行可选的 else 块。
其他形式:
• 嵌套 if:在 if/elif/else 块内再嵌套条件判断,实现层级化逻辑(如先判断及格再细分良好)。
• 条件表达式(三元运算符):语法为 <expr1> if <cond_expr> else <expr2>,根据条件返回 expr1 或 expr2,可直接用于赋值等操作(如 result = “及格” if score >= 60 else “不及格”),但需注意优先级(复杂表达式建议用括号分组)。
简言之,条件语句通过灵活组合实现从简单分支到复杂层级决策的逻辑控制。
# --- 1. 单支 (if):仅当条件为 True 时执行 ---
# 场景:检查股票当前价格是否高于买入价,若高于则记录"盈利机会"
current_price = 105
buy_price = 100
if current_price > buy_price:
print(f"【单支判断】当前价({current_price})高于买入价({buy_price}),存在盈利机会!")
# --- 2. 双支 (if-else):二元决策(及格/不及格类比)---
# 场景:判断基金年化收益率是否达标(阈值6%),输出"达标"或"需优化"
annual_return = 0.05 # 5%
if annual_return >= 0.06:
print(f"【双支判断】年化收益率{annual_return:.1%} ≥ 6%,策略达标!")
else:
print(f"【双支判断】年化收益率{annual_return:.1%} < 6%,需优化策略!")
# --- 3. 多支 (if-elif-else):多等级划分(成绩等级类比)---
# 场景:根据股票波动率(年化标准差)划分风险等级
volatility = 0.25 # 25%年化波动率
if volatility < 0.1:
risk_level = "低风险"
elif 0.1 <= volatility < 0.2:
risk_level = "中低风险"
elif 0.2 <= volatility < 0.3:
risk_level = "中高风险"
else:
risk_level = "高风险"
print(f"【多支判断】波动率{volatility:.0%}对应风险等级:{risk_level}")
# --- 4. 嵌套 if:层级化逻辑(先判断盈利再细分超额收益)---
# 场景:先判断是否盈利(当前价>买入价),再判断是否为"超额收益"(盈利≥10%)
current_price = 115
buy_price = 100
if current_price > buy_price:
profit_ratio = (current_price - buy_price) / buy_price
if profit_ratio >= 0.1:
print(f"【嵌套判断】盈利且超额!收益率{profit_ratio:.1%} ≥ 10%(买入价{buy_price}→现价{current_price})")
else:
print(f"【嵌套判断】盈利但未超额(收益率{profit_ratio:.1%} < 10%)")
else:
print(f"【嵌套判断】当前价{current_price} ≤ 买入价{buy_price},未盈利")
# --- 5. 条件表达式(三元运算符):简洁赋值 ---
# 场景:根据持仓天数决定操作建议(简洁赋值给变量)
holding_days = 15
# 三元运算符:持仓≥30天建议"长期持有",否则建议"短期关注"
suggestion = "长期持有" if holding_days >= 30 else "短期关注"
print(f"【三元运算符】持仓{holding_days}天,建议:{suggestion}")
# 复杂场景(带括号分组):根据波动率和收益率综合判断
volatility = 0.18
return_ratio = 0.12
risk_adjusted_label = "优质标的" if (volatility < 0.2 and return_ratio > 0.1) else "需谨慎"
print(f"【三元运算符】波动率{volatility:.0%}+收益率{return_ratio:.0%} → 综合评估:{risk_adjusted_label}")
2、循环语句:
循环语句(loop statement)用于重复执行代码,分为两类:
-
while 循环(次数未知)
• 特点:只要条件表达式为 True 就持续执行循环体,条件变为 False 时终止。• 注意:需手动控制条件变量(如修改计数器),否则易导致无限循环。
• 变体:
◦ 无限循环(while True):需用 break 手动终止,常用于监听场景;
◦ 嵌套循环:在循环内再嵌套循环,实现多层遍历;
◦ 单行循环:循环体为单条语句时,可与 while 写在同一行(简化但仅限简单逻辑)。
-
for 循环(次数已知)
• 特点:遍历可迭代对象(如列表、字符串等)的每个元素,循环次数等于元素数量。• 常用辅助函数:
◦ enumerate():同时获取元素索引和值;
◦ zip():并行遍历多个可迭代对象,打包对应元素。
通用控制与流程
• break:立即终止整个循环,跳出后不执行 else 块。
• continue:跳过当前迭代剩余代码,直接进入下一轮循环。
• else:仅当循环未被 break 中断(正常结束)时执行,用于补充逻辑。
执行顺序:循环体按顺序执行 → 遇 break 则直接退出 → 遇 continue 则跳至下一轮 → 正常结束则执行 else。
# --- 1. while 循环(次数未知):监控价格直到达标 ---
# 场景:模拟监控股票价格,当价格超过目标价时买入(手动控制条件变量)
target_price = 105 # 目标买入价
current_price = 100 # 当前价格(模拟动态变化)
count = 0 # 记录监控次数
# 只要当前价未达标就持续监控(条件为True时循环)
while current_price < target_price:
count += 1
print(f"第{count}次监控:当前价{current_price} < 目标价{target_price},继续等待...")
current_price += 2 # 模拟价格每次+2(手动控制变量递增)
print(f"触发买入!当前价{current_price} ≥ 目标价{target_price}(监控了{count}次)")
# --- 2. for 循环(次数已知):遍历股票价格列表计算简单收益率 ---
# 场景:计算一支股票连续几天的简单收益率(每日价格变动比例)
prices = [100, 102, 105, 103, 107] # 按顺序排列的每日收盘价
returns = [] # 存储每日收益率
# 遍历价格列表(从第2天开始,计算相对于前一天的收益率)
for i in range(1, len(prices)):
ret = (prices[i] - prices[i-1]) / prices[i-1] # 收益率公式
returns.append(ret)
print(f"第{i}天收益率: {ret:.2%}") # 打印每日报酬率(如第2天:(102-100)/100=2%)
print("所有收益率:", [round(r, 4) for r in returns])
# --- 3. 常用辅助函数:enumerate() 获取索引+值 ---
# 场景:遍历多只股票的价格,同时显示序号和价格(比如持仓列表)
stock_prices = [150, 88, 210, 55] # 4只股票的当前价格
print("\n【enumerate示例】股票持仓详情:")
for idx, price in enumerate(stock_prices):
print(f"第{idx+1}只股票: 当前价{price}元") # idx从0开始,显示时+1更直观
# --- 4. 通用控制:break(提前终止)和 continue(跳过本次)---
# 场景:遍历价格列表,找到第一个收益率>10%的日期就停止(break),跳过价格为负的异常值(continue)
test_prices = [100, 105, -98, 120, 130] # 模拟含异常值的价格序列
print("\n【break/continue示例】寻找高收益日:")
for i in range(1, len(test_prices)):
if test_prices[i] < 0:
print(f"第{i}天价格异常({test_prices[i]}),跳过计算")
continue # 跳过当前循环,不计算收益率
ret = (test_prices[i] - test_prices[i-1]) / test_prices[i-1]
print(f"第{i}天收益率: {ret:.2%}")
if ret > 0.1: # 收益率超过10%
print(f"✓ 找到高收益日(第{i}天收益率{ret:.2%} > 10%),停止搜索!")
break # 终止整个循环
# --- 5. 单行循环(简化写法,仅演示)---
# 场景:简单打印3次"检查市场状态"(实际量化中较少用,仅作语法示例)
count = 0
while count < 3: print(f"检查市场状态(第{count+1}次)"); count += 1 # 单行循环(注意分号分隔)
循环与异常处理结合可提升鲁棒性(后续展开)。
3、错误类型:
从宏观层面来看,程序运行过程中出现的错误可分为三大类,其识别难度和表现形式各不相同:
- 语法错误(syntax error):最易识别
- 运行错误(runtime error):较易识别
- 逻辑错误(logical error):最难识别
错误类型 | 核心定义 | 触发阶段 | 典型示例 | 报错表现 |
---|---|---|---|---|
语法错误 | 未遵循 Python 语言的语法规则(如关键字拼写、符号缺失、缩进错误等) | 编译阶段 | 1. print 误写为 primt ;2. if a > 0 末尾缺失冒号;3. 字符串 'error 引号不闭合;4. 列表索引括号不匹配( print(l[1 );5. for 循环体未缩进 |
编译器直接报错,标注错误行号及原因(如 SyntaxError: invalid syntax ),程序无法启动运行 |
运行错误 | 语法无错、程序可启动,但运行中因数据异常或操作不可执行触发(又称“异常”) | 运行阶段 | 1. 分母为 0(10 / 0 ,触发 ZeroDivisionError );2. 整数加字符串( 10 + '20' ,触发 TypeError );3. 调用未定义函数( fun() ,触发 NameError );4. 读取不存在文件( open('test.txt') ,触发 FileNotFoundError );5. 列表索引越界( l = [1,2]; print(l[3]) ,触发 IndexError ) |
运行中突然报错,抛出具体异常类(如 ZeroDivisionError )及错误栈,程序中断执行 |
逻辑错误 | 语法无错、运行无报错,但输出结果与预期不符,源于代码逻辑设计问题 | 运行阶段(执行后) | 1. 变量名误用(total 误写为 totle ,且 totle 被其他地方定义);2. if 逻辑误写在块外(无条件执行);3. 运算符优先级混淆( 10 + 5 * 2 误算为 30);4. 布尔表达式错误( score > 60 遗漏 score == 60 及格场景);5. 循环少执行一次( range(1,4) 误替代 range(1,5) ) |
程序正常执行完毕,无任何报错提示,但结果不符合预期(如“计算总和应为 100,实际为 90”) |
完整的错误清单如下。下表呈现了 Python 中较为完整的错误层级结构,所有错误类型均是 BaseException
的子类;而我们通常所说的 “异常处理”,主要针对的是 Exception
及其子类(即普通运行错误)。接下来,我们一起看看常见的错误类型(下表中红色高亮的字段)。
4、异常处理:
异常处理用于预防和应对程序运行中的错误,防止程序因错误直接崩溃,核心针对运行错误(语法错误在运行前已被发现)。
关键操作:
-
主动预防/抛出异常:
• raise:手动触发异常(支持Python内置异常如ZeroDivisionError或自定义异常类)。• assert:声明关键条件必须成立(条件为False时抛出AssertionError,终止程序)。
-
被动捕捉/处理异常:
• try-except:将可能出错的代码放在try中,处理逻辑放在except中。推荐精准捕获指定异常类型(如except ZeroDivisionError),避免笼统的except:(会隐藏错误细节)。支持同时处理多个错误(分不同子句或合并类型)。• try-except-else:try无异常时执行else(补充正常流程逻辑)。
• try-except-else-finally:无论是否异常,finally中的代码必执行(常用于释放资源,如关闭文件、断开连接)。
异常处理本质:是程序的“异常流程控制”机制,与顺序、条件、循环等正常流程控制共同保障程序健壮性。
# --- 1. 主动预防/抛出异常:手动触发异常(raise) ---
# 场景:检查用户输入的投资金额是否合法(必须为正数),非法时主动抛出异常
def check_investment_amount(amount):
if amount <= 0:
# 手动触发内置异常(金额≤0时抛出ValueError)
raise ValueError(f"投资金额必须为正数!当前输入:{amount}")
print(f"✅ 投资金额验证通过:{amount}元")
# 测试合法金额(不触发异常)
try:
check_investment_amount(1000) # 正常输入
except ValueError as e:
print(f"主动异常捕获:{e}")
# 测试非法金额(触发异常)
try:
check_investment_amount(-500) # 非法输入(会触发raise)
except ValueError as e:
print(f"主动异常捕获:{e}") # 输出:投资金额必须为正数!当前输入:-500
# --- 2. 被动捕捉/处理异常:try-except 捕获运行错误 ---
# 场景:计算股票收益率时,可能因除数为0(如首日价格为0)导致ZeroDivisionError
prices = [0, 102, 105] # 模拟价格序列(第1天价格为0,会触发异常)
for i in range(1, len(prices)):
try:
# 计算第i天相对于第i-1天的收益率:(当前价-前一日价)/前一日价
ret = (prices[i] - prices[i-1]) / prices[i-1] # 若prices[i-1]=0则报错
print(f"第{i}天收益率: {ret:.2%}")
except ZeroDivisionError:
print(f"⚠️ 第{i}天计算失败:前一日价格({prices[i-1]})为0,无法计算收益率!")
# --- 3. 完整流程控制:try-except-else-finally(资源释放场景) ---
# 场景:模拟读取股票数据文件(可能不存在),计算平均价格,确保文件最终关闭
file_name = "stock_prices.txt" # 假设的文件名(实际可能不存在)
file = None # 初始化文件对象
try:
file = open(file_name, "r") # 尝试打开文件(可能触发FileNotFoundError)
prices = []
for line in file:
price = float(line.strip()) # 读取每行并转为浮点数(可能触发ValueError)
prices.append(price)
avg_price = sum(prices) / len(prices) # 计算平均价格
except FileNotFoundError:
print(f"❌ 文件 {file_name} 不存在,请检查路径!")
except ValueError:
print(f"❌ 文件包含非数字内容,无法转换为价格!")
else:
# 仅当try块无异常时执行(文件存在且数据有效)
print(f"✅ 文件读取成功!平均价格: {avg_price:.2f}元")
finally:
# 无论是否异常,最终执行(确保文件关闭,避免资源泄露)
if file:
file.close()
print("🔒 文件处理完成(已确保关闭)")
# --- 4. 精准捕获 vs 笼统捕获(推荐精准处理) ---
# 场景:演示避免用笼统的except:(会隐藏具体错误)
try:
# 模拟一个可能触发多种异常的操作(如除零或类型错误)
result = 10 / 0 # 触发ZeroDivisionError
except ZeroDivisionError:
print("🎯 精准捕获:除零错误(推荐明确处理此类异常)")
except Exception as e: # 笼统捕获(仅作对比,实际应避免)
print(f"⚠️ 笼统捕获到未知错误:{type(e).__name__}(可能隐藏细节)")
(三)、函数
到目前为止,我们编写的所有代码都以“一堆简单语句”的形式存在,其目的也只是“执行一次”。若需要多次运行某段代码,就需要使用“函数”——函数的核心价值在于“重复使用代码”和“增强代码可读性”。理想情况下,一个函数应只负责完成“一件事”,并将这件事做好。
函数的使用遵循“先定义、后调用”的原则:在定义函数时,参数被称为形参(形式参数),仅为“形式上的占位符”;在调用函数时,传入的参数被称为实参(实际参数),是“实际参与运算的值”。
Python 中函数的定义方式
在 Python 中,定义函数主要有两种方式:
- 使用关键词
def
定义普通函数(normal function)(有明确函数名,可多次调用); - 使用关键词
lambda
定义匿名函数(anonymous function)(无函数名,通常用于临时场景)。
函数的“一等公民”特性:函数与变量地位等同,可赋值给变量、存入容器、作为参数传递或在函数内返回,灵活应用于高阶场景。
函数的核心概念与分类
1、 函数的本质:从数学概念到 Python 实现
“函数(function)”最早源于数学领域,指“每个输入值对应唯一输出值的映射关系”。数学中的函数 y = f(x)
,与 Python 中的函数 fun(x)
逻辑相通,但 Python 函数更具通用性:
- 数学函数:通常支持“多对一”(多个输入对应一个输出),且输入、输出多为单个值;
- Python 函数:输入(参数)和输出(返回值)均可为多个,且支持更复杂的逻辑(如流程控制、异常处理)。
相比零散的语句,函数有三大核心优势:
• 复用性:定义一次即可多次调用;
• 模块化:拆分复杂代码为独立子任务,降低维护成本;
• 独立命名空间:函数内变量不与外部冲突。
2、 普通函数(def
定义)
在理解普通函数的各类参数前,需先明确形参与实参的核心区别:
- 形参(parameter):出现在函数定义语句中,是“形式上的参数名称”,用于指定函数可接收的参数类型和数量(如
def fun(x, y):
中的x
、y
); - 实参(argument):在调用函数时传入的值,是“实际参与运算的数据”(如
fun(10, 20)
中的10
、20
)。
简言之,形参定义了“函数能接收什么”,实参决定了“函数实际接收什么”。
3、 匿名函数(lambda
定义)
普通函数与匿名函数可类比为“性格外向与内向的兄弟”:
- 普通函数:用
def
关键词显性定义,有明确的函数名,适合多次调用、逻辑复杂的场景; - 匿名函数:用
lambda
关键词隐性定义,无函数名,语法简洁(仅一行),适合临时使用、逻辑简单的场景(如作为sorted()
、map()
等函数的参数)。
4、 高阶函数
Python 中的函数可分为低阶函数和高阶函数:
- 低阶函数:指不依赖其他函数作为参数或返回值的函数,可细分为普通函数和匿名函数;
- 高阶函数:指满足以下任一条件的函数:
- 接收其他函数作为参数(如
map(func, iterable)
,func
为传入的函数); - 返回一个函数作为结果(如嵌套函数中,外层函数返回内层函数)。
- 接收其他函数作为参数(如
关于高阶函数的具体用法,本文暂不展开讨论,感兴趣的读者可参考相关资料深入学习。
# 普通函数
def calculate_returns(prices):
simple_returns = []
for i in range(1, len(prices)):
# 简单收益率公式:(当前价格 - 前一日价格) / 前一日价格
ret = (prices[i] - prices[i-1]) / prices[i-1]
simple_returns.append(ret)
# 年化收益率(假设一年252个交易日)
if len(simple_returns) > 0:
avg_daily_return = sum(simple_returns) / len(simple_returns)
annualized_return = (1 + avg_daily_return) ** 252 - 1
else:
annualized_return = 0.0 # 无数据时返回0
return {
'simple_returns': simple_returns,
'annualized_return': annualized_return
}
sample_prices = [100, 102, 105, 103, 107]
result = calculate_returns(sample_prices)
print("每日简单收益率:", [round(r, 4) for r in result['simple_returns']])
print("年化收益率: {:.2%}".format(result['annualized_return']))
# 每日简单收益率: [0.02, 0.0294, -0.019, 0.0388]
# 年化收益率: 7435.90%
# 匿名函数
func = lambda x, y: x*y
func(2, 3) # 输出 6
func = lambda *args: sum(args)
func( 1, 2, 3, 4, 5 ) # 输出 15
从“函数与变量”到“对象与类”
变量和函数是 Python 中的两大核心知识点,但单独处理两者存在明显缺点:
- 若仅需存储数据:只需选择合适的数据结构(元素型或容器型)即可;
- 若仅需实现功能:只需编写对应逻辑的函数即可。
但这种“分开处理”的方式会导致变量与函数零散分布——存储变量时未考虑关联的函数,设计函数时未考虑依赖的变量。若需要将“变量(数据)”与“函数(操作)”绑定在一起,形成一个有机整体,就需要引入“对象(object)”和“类(class)”的概念。
(四)、面向对象编程
用函数编写程序的核心逻辑是 “输入数据、输出结果”,这种通过 “按步骤调用函数” 组织程序的方式,被称为面向过程编程(Procedure-Oriented Programming,简称 POP)。而组织程序的另一种核心方法,是将 “数据” 与 “操作数据的函数” 结合,并封装在 “对象” 中 —— 这就是面向对象编程(Object-Oriented Programming,简称 OOP)。
OOP 的核心:类与对象
类(class)和对象(object)是面向对象编程的两大核心概念,二者关系密不可分:
-
类(class):是对某一类事物的抽象描述(如 “人”“汽车”),定义了这类事物共有的 “数据(字段)” 和 “行为(方法)”;
-
对象(object):是类的具体实例(如 “张三” 是 “人” 的实例,“张三的车” 是 “汽车” 的实例),拥有类定义的所有属性,且有具体的数值和行为表现。
相比面向过程中 “零散的变量和函数”,对象的核心价值是 “整合”—— 它将数据和操作数据的逻辑打包在一起: -
在对象中,用于存储数据的 “变量” 被称为字段(fields);
-
在对象中,用于操作数据的 “函数” 被称为方法(methods)。
在 OOP 中,“字段” 和 “方法” 统称为类或对象的属性(attributes)。使用 “字段”“方法” 这类专门术语,能清晰区分 “独立的变量 / 函数” 与 “属于类或对象的变量 / 函数”,避免概念混淆。
OOP 的四大核心特征
面向对象编程有四大核心特征,它们共同支撑起“模块化、可复用、可扩展”的代码设计:
- 封装(Encapsulation):将相关数据和方法打包成类,隐藏内部细节,仅暴露统一接口(如手机通过按键/屏幕操作内部电路)。
- 继承(Inheritance):子类继承父类的字段和方法(复用逻辑),并可新增或重写方法(扩展功能,如“学生”继承“人”的基础行为并新增“学习”)。
- 多态(Polymorphism):同一父类方法在不同子类中有不同实现,父类引用指向子类对象时自动调用子类逻辑(如“计算薪资”在“开发者”“销售”子类中分别按工时/业绩计算)。
- 组合(Composition):一个类包含另一个类的对象作为组件(“has-a”关系,如“电脑”包含“CPU”“内存”)。
# --- 1. 类与对象(抽象与具体) ---
# 类:金融衍生品(抽象) -> 对象:沪深300指数期权(具体)
class FinancialDerivative:
"""金融衍生品类(抽象:定义标的资产和到期日,以及估值方法)"""
def __init__(self, underlying_asset, expiry_date):
self.underlying_asset = underlying_asset # 标的资产(如沪深300指数)
self.expiry_date = expiry_date # 到期日(如2024-12-31)
def calculate_valuation(self):
"""估值方法(具体逻辑由子类实现,如期权定价公式)"""
return f"计算 {self.underlying_asset} 在 {self.expiry_date} 的理论价值(需子类实现具体模型)"
# 对象:沪深300指数期权(类的实例)
option_hs300 = FinancialDerivative("沪深300指数", "2024-12-31")
print(option_hs300.calculate_valuation()) # 输出:计算 沪深300指数 在 2024-12-31 的理论价值(需子类实现具体模型)
# --- 2. 封装(打包量化数据与操作方法) ---
# 类:量化交易策略(封装策略参数和执行逻辑)
class TradingStrategy:
def __init__(self, strategy_name, initial_capital):
self.strategy_name = strategy_name # 策略名称(如"均线突破")
self.initial_capital = initial_capital # 初始资金
self.current_capital = initial_capital # 当前资金
def execute_trade(self, signal, price):
"""执行交易(根据信号买卖,更新资金)"""
if signal == "买入":
self.current_capital -= price # 简化逻辑:全仓买入
print(f"{self.strategy_name} 执行买入,价格 {price},剩余资金 {self.current_capital}")
elif signal == "卖出":
self.current_capital += price # 简化逻辑:全仓卖出
print(f"{self.strategy_name} 执行卖出,价格 {price},剩余资金 {self.current_capital}")
# 创建策略对象(实例化类)
strategy_ma = TradingStrategy("均线突破策略", 10000)
strategy_ma.execute_trade("买入", 50) # 模拟买入信号(价格50元)
strategy_ma.execute_trade("卖出", 55) # 模拟卖出信号(价格55元)
# --- 3. 继承(复用基础策略逻辑并扩展) ---
# 子类:动量策略(继承基础交易策略,新增动量因子逻辑)
class MomentumStrategy(TradingStrategy):
def __init__(self, strategy_name, initial_capital, momentum_threshold):
super().__init__(strategy_name, initial_capital)
self.momentum_threshold = momentum_threshold # 动量阈值(如过去20日涨幅>10%)
def execute_trade(self, signal, price, momentum_value):
"""重写交易逻辑:仅当动量超过阈值时执行"""
if momentum_value >= self.momentum_threshold:
if signal == "买入":
self.current_capital -= price # 简化逻辑:全仓买入
print(f"{self.strategy_name} 执行买入,价格 {price},剩余资金 {self.current_capital}(动量 {momentum_value:.1%})")
elif signal == "卖出":
self.current_capital += price # 简化逻辑:全仓卖出
print(f"{self.strategy_name} 执行卖出,价格 {price},剩余资金 {self.current_capital}(动量 {momentum_value:.1%})")
else:
print(f"动量不足({momentum_value:.1%} < {self.momentum_threshold:.1%}),不执行交易")
# 创建动量策略对象(继承自基础策略)
momentum_strategy = MomentumStrategy("动量突破策略", 10000, 0.1) # 动量阈值10%
momentum_strategy.execute_trade("买入", 50, 0.15) # 动量15% > 10%,执行买入
momentum_strategy.execute_trade("买入", 50, 0.08) # 动量8% < 10%,不执行
# --- 4. 多态(同一方法不同量化策略实现) ---
# 子类:均值回归策略、趋势跟踪策略(均继承交易策略,不同买卖逻辑)
class MeanReversionStrategy(TradingStrategy):
def execute_trade(self, signal, price, deviation):
"""均值回归逻辑:价格偏离均值过多时反向操作"""
if signal == "买入" and deviation > 0: # 价格低于均值(负偏离)
self.current_capital -= price
print(f"均值回归策略 买入(偏离 {deviation}),剩余资金 {self.current_capital}")
elif signal == "卖出" and deviation < 0: # 价格高于均值(正偏离)
self.current_capital += price
print(f"均值回归策略 卖出(偏离 {deviation}),剩余资金 {self.current_capital}")
class TrendFollowingStrategy(TradingStrategy):
def execute_trade(self, signal, price, trend_strength):
"""趋势跟踪逻辑:趋势强度高时顺势操作"""
if signal == "买入" and trend_strength > 0.5:
self.current_capital -= price
print(f"趋势跟踪策略 买入(趋势强度 {trend_strength}),剩余资金 {self.current_capital}")
elif signal == "卖出" and trend_strength < 0.5:
self.current_capital += price
print(f"趋势跟踪策略 卖出(趋势强度 {trend_strength}),剩余资金 {self.current_capital}")
# 创建不同策略对象
mean_reversion = MeanReversionStrategy("均值回归策略", 10000)
trend_following = TrendFollowingStrategy("趋势跟踪策略", 10000)
# 多态:统一调用不同策略的execute_trade方法(自动执行各自逻辑)
# 注意:为了简化,这里直接调用各策略的execute_trade方法,而不是通过父类列表
mean_reversion.execute_trade("买入", 50, 0.1) # 均值回归:价格偏离均值0.1(买入)
trend_following.execute_trade("买入", 50, 0.6) # 趋势跟踪:趋势强度0.6(买入)
# --- 5. 组合(策略包含多个标的资产) ---
# 类:组合策略(包含多个子策略对象作为组件)
class PortfolioStrategy:
def __init__(self, name, sub_strategies):
self.name = name # 组合策略名称
self.sub_strategies = sub_strategies # 子策略列表(组合关系:has-a)
def execute_all(self):
"""执行所有子策略的交易逻辑"""
print(f"执行组合策略 '{self.name}' 下的所有子策略:")
for strat in self.sub_strategies:
if isinstance(strat, MeanReversionStrategy):
strat.execute_trade("买入", 10, 0.1) # 模拟统一信号(简化逻辑)
elif isinstance(strat, TrendFollowingStrategy):
strat.execute_trade("买入", 10, 0.6) # 模拟统一信号(简化逻辑)
elif isinstance(strat, MomentumStrategy):
strat.execute_trade("买入", 10, 0.15) # 模拟统一信号(简化逻辑)
# 创建子策略对象(均值回归、趋势跟踪和动量策略)
sub_strategy1 = MeanReversionStrategy("子策略-均值回归", 5000)
sub_strategy2 = TrendFollowingStrategy("子策略-趋势跟踪", 5000)
sub_strategy3 = MomentumStrategy("子策略-动量突破", 5000, 0.1)
# 创建组合策略对象(包含多个子策略)
portfolio = PortfolioStrategy("多策略组合", [sub_strategy1, sub_strategy2, sub_strategy3])
portfolio.execute_all() # 执行组合内所有策略
简单来说,类就像是产品的设计图纸,对象就是根据这个图纸生产出来的具体产品。要做出某个产品,必须先有它的设计图纸。
在学习面向对象编程(OOP)之前,得先换个思路——别把整数、字符串、列表这些只当成普通的"变量",而要把它们当作一个个独立的"对象"。普通的变量通常就只是存个数值,但对象不一样,它不光有用来描述状态的"属性"(也就是各种字段),还有能实现各种功能的"方法"。
四、总结
到这里,我们已经聊完了Python里最重要的几个话题:数据结构、流程控制、函数用法,还有类与对象这些基础内容。
上面只是简单介绍了Python的基础知识,如果想学得更详细,可以到B站或者其他教学平台找更深入的教程。还是那句话,学习的时候一定要边学边动手实践,这样进步才快。其实也没必要把所有基础都学透,只要把上面这些核心内容搞明白了,就可以直接去学量化相关的进阶编程了。接下来,我会继续分享Python编程的进阶内容,讲讲Python语法在量化投资里的具体应用。