文科生の量化闯关记:代码篇 - Python编程基础
  我是宽客 2天前 54 0

一、引言

文科生学量化需要学编程吗?

image.png

上次我们已经学习了 Python 安装与配置的相关内容。这次将运用已学的编程知识,简要介绍 Python 的 4 个基础模块:数据结构、流程控制、函数用法、类与对象。在开启编程学习之旅前,我们需要明确为什么要学编程,以及该怎么学。

二、编程

为什么学习编程

image.png

我们为什么需要编程?文科生学量化真的需要编程吗?个人学习量化需具备编程、数理、金融、交易 4 项技能,其中数理知识门槛相当高,但好在身处 AI 时代 —— 尽管编程有难度,却能借助大模型逐行解释难懂代码、解决报错、阐释复杂概念,借此攻克代码关后,数理(高数、统计、计量)门槛会大大降低,因为大部分复杂数学公式和算法只需理解原理,实际计算可交给编程;而且量化分析本质是 “用数据和模型解决金融问题”,编程是实现这一过程的 “桥梁”,没有编程能力就无法处理海量数据、回测策略、落地实盘,更难在量化竞争中构建优势,所以编程能力是自学量化的核心。

如今在 AI 时代,编程也不再只是程序员的专属技能,而是每个人都应掌握的基本素养。

什么是编程

接下来我会介绍编程的定义与学习前提:**计算机发明后,为指挥它解决问题,“编程”应运而生。它就像人类与计算机的“翻译官”,能将人的意图转化为计算机可执行的指令。**编程的核心目的本质是 “指挥计算机按人类意愿行动、解决特定问题”,具体体现为 沟通(人与计算机的交互)、控制(掌控计算机行为)、自动化(替代人工完成重复任务)。理解这一点要 聚焦于问题解决,而非技术细节,明确编程是工具而非目的,多思考如何用它高效解决实际问题。

而编程本身,是以特定编程语言为媒介,通过向计算机下达清晰准确的步骤指令实现目标的过程。从本质看,它既是 给计算机下指令(像详细告知朋友从家去地铁站的步骤),也是 人与计算机的沟通(以编程语言为桥梁,让计算机理解人的想法),更能帮我们解决问题(不仅写代码,还能锻炼逻辑思维、用创意方法解决问题)。

编程语言

前面我们提到,编程就是给计算机下达指令的过程。那么,计算机该如何听懂我们的指令呢?这就引出了一个关键概念 —— 编程语言。编程语言和人类语言一样,种类繁多,常见的有 Python、Java、C++、JavaScript 等等。

image.png

为什么会有这么多编程语言?因为每种语言都有其特定的应用场景与优缺点。但对于我们这些量化入门者来说,建议直接选择 Python。无需在语言选择上纠结,重要的是迈出学习的第一步。

Jupyter Notebook

image.png

编程小白入门建议优先用 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 了。

image.png

打开 Notebook 后,我们可以直接在单元格中编写代码,之后按 Ctrl+Enter 键,就能运行选中的单元格了。

image.png

Python 逻辑

其实 Python,或者说任何一门编程语言,核心目的都是 “给计算机‘讲故事’”—— 也就是传递我们的指令。而它的基础逻辑,无外乎 “按顺序执行、按条件判断、按需求重复” 这三类。

人们通过按顺序(in sequence)组织句子根据条件(by conditions)调整叙事适当重复(with repetitions)关键内容这三种方式来编织生动的故事。类比到 Python 编程中,编写程序同样需要掌握三类基础逻辑:按顺序编写语句(流程控制的基础)、通过条件判断灵活调整执行路径(如 if语句)、利用循环结构高效处理重复任务(如 for/while循环)。

image.png

学习语言需先掌握词汇与语法这两大基石,进而通过组合词汇形成句子,最终以句子为单元编织出完整的故事(或思想表达)。

(1)、词汇

1. 关键字(也称“保留字”)

与人类语言不同,Python 的关键字(keyword)数量相当少。之所以称这些词汇为 “关键字”,是因为它们在 Python 语言体系中具有特定的语法意义,不能被随意用作变量名、函数名等标识符。

image.png

2. 变量名

与人类语言的表达逻辑相似,在编写 Python 代码时,我们可以自主创建一类具有指代意义的词汇 —— 即变量名(variable name)。变量的命名自由度较高,核心规则是不能与 Python 保留字重名,同时需符合标识符的基础规范(如不能以数字开头、不能包含特殊符号等)。不过,在掌握保留字知识和变量命名规则后,我们还需要通过 “正确的语法”,将这些 “词汇”(关键字、变量名等)组织成可执行的代码逻辑(类似 “句子” 的功能)。

image.png

(2)语法:

语法(syntax)指规范一门语言中句子结构、字词顺序的一整套规则与流程。汉语和英语有各自的语法,Python 作为编程语言也不例外。以一个最常见的语法规则为例:字符串必须用引号包裹,若未按此规则编写,程序运行时就会报错。

image.png

定义字符串时,使用单引号或双引号均可,但前后引号必须匹配。例如上图中最后一个示例缺少后引号,运行程序时就会触发语法错误。Python 程序报错时,会自动输出错误信息,帮助我们定位问题。

(3)、句子和故事:

1、句子

在 Python 中,“句子” 被称为语句(statement),它可以是一次变量命名、一次数值计算,或是一条执行特定操作的指令。就像一段故事由多个句子串联而成,一段程序(program) 也由多条语句组成 —— 从这个角度来说,编写程序就如同 “给计算机讲一个有逻辑的故事”。

Python 中的语句主要分为两类:

  • 简单语句:通常仅需一两行代码即可完成,例如 a = 10(变量赋值)、print("hello")(打印内容);
  • 复合语句:一般包含多行代码,且带有嵌套结构,例如 if...else(条件判断)、for 循环(重复执行)等。

2、故事

我们讲故事时,常用三种核心逻辑:按顺序叙述、按条件分支叙述(比如 “如果… 就…,否则…”)、按重复情节叙述(比如 “每天都做…”)。这三种逻辑对应到程序中,就是 “流程控制” 的核心思想 —— 后续介绍 “流程控制” 模块时,会对此展开更详细的说明。

接下来,我将从数据结构、流程控制、函数用法、面向对象(类与对象) 四个核心模块,系统介绍 Python 的基础知识。

(一)、数据结构

image.png

学习任何编程语言,首先都应该接触其数据结构。在 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 # 运算符:+,*

变量:
从规则上来说,变量可以使用任意名称,但需满足两个核心条件:

  1. 名称不能是 Python 的关键字(如 ifforlambda 等);
  2. 名称不能以数字开头(如 1stock2_bonds 等均为不合法变量名)。

除了避免使用上述 “不合法” 的变量名,建议使用可读性强的变量名(例如用 user_age 表示 “用户年龄”,而非 x 或 a1),方便后续代码的阅读与维护。

Python VS C语言

这里简单说明:在 Python 中创建变量时,不需要像 C 语言那样在变量前指定变量类型,两种语言创建变量的语法存在明显区别。通过对比可见,Python 定义变量时无需声明数据类型,因此 Python 属于动态类型(dynamic typed)语言

image.png

看到这里,你可能会觉得 Python 不够严谨,甚至产生 “定义变量怎么都不用带变量类型呢” 这类疑问。其实原因很简单:Python 中的变量本质上只是一个 “名字”。就像 x,它仅仅是一个变量名,作用是 “指向” 一个引用对象 PyObject;而 PyObject 本质是计算机分配的一块内存,包含类型、大小、引用计数等属性。

总而言之,对初学者而言,Python 会更简单 —— 无需考虑复杂的变量类型声明,直接给变量赋值即可开始使用。

运算符:

运算符(operator)用于操作和比较数据,具体可分为七类,分别是算术运算符、按位运算符、比较运算符、赋值运算符、逻辑运算符、身份运算符和成员运算符。

image.png

# 算术运算符 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 本质上和我们学数学时常用的 “运算优先级规则” 是一致的。

image.png

下面将分别介绍元素型(element type)数据和容器型(container type)数据。

2、元素型数据

整数 :

整数(integer,Python 中对应类型为 int)是最简单的数据类型之一,它与浮点数(float)的核心区别在于:整数的小数点后无有效数值(如 5-10),而浮点数的小数点后带有有效数值(如 5.0-10.5)。

需要特别说明的是:在 Python 中 “万物皆对象”,整数也不例外。只要是对象,就会包含相应的字段(fields) 和方法(methods),这两者统称为对象的属性(attributes)

image.png

image.png

还有,任何对象都有内存地址,用 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 = TrueF = False。除了直接赋值 True 或 False,还可通过 bool(X) 函数创建布尔变量,其中 X 既可以是元素型数据(如整数、浮点数),也可以是容器型数据(如列表、字符串)。

需要注意的是,若将布尔变量用于数字运算,True 会对应数值 1False 会对应数值 0。因此 T + 2 的结果为 3F + 10.31 的结果为 10.31(原句中 T + 2 结果表述有误,此处修正)。

此外,使用比较操作符(==!=><>=<=)对变量或常量进行比较时,会返回布尔值。后续将介绍的 “条件判断”,正是通过 “变量或常量之间的比较” 来构建,再根据比较结果为 True 或 False,决定是否执行对应代码块。

布尔值也可通过逻辑操作符(andornot)进行运算:多个简单条件配合逻辑操作符,可组合成复合条件(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),它仅有一个取值:NoneNone 是不可修改的关键字,本质是一个特殊对象,需特别强调: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)是若干字符组成的序列,本质上可看作字符的容器。创建字符串(包括单行和多行字符串)有以下五种常用方法:

  1. 单引号 's = 'Python'
  2. 双引号 "s = "Python"
  3. 内置函数 str():例如 s = str(123)(将数字转为字符串)
  4. 三单引号 ''':用于创建多行字符串,例如s = '''第一行内容 第二行内容'''
  5. 三双引号 """:功能与三单引号一致,例如s = """第一行内容 第二行内容"""

字符串通过索引获取单个字符(从0开始计数,支持负索引反向计数),通过切片获取多个连续字符(语法s[start:stop],左闭右开,含起始索引字符、不含结束索引字符)。

此外,字符串常用操作分为两大类:

  1. 内容处理(分合查替):如分割、拼接、查找、替换;
  2. 格式调整(格式化):如大小写转换、删除/补充字符、格式化输出等。

具体方法可通过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)查看全部方法):

  1. append(单个元素)(尾部添加)、extend(可迭代对象)(尾部批量添加)、insert(索引, 元素)(中间插入)。
  2. remove(元素值)(删首个匹配值)、pop(索引)(删指定索引元素,可选默认末尾)。
  3. sort()(原地升序/降序排序)、reverse()(原地反转索引顺序)。
  4. :按索引(如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]取中间片段)。

核心特性:

  1. 不可修改:不能重新赋值元素、增删元素或排序(操作如t[0]=100、t.append()会报错)。

  2. 例外情况:
    • 元组变量可整体重新赋值(如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,每个元素为(键, 值)元组)。

键的核心规则

  1. 不可修改:仅支持不可变类型(如整数、字符串、元组等),不可变类型哈希值固定,能保证键唯一性;列表、字典等可变类型不可作为键(修改后哈希值变化)。
  2. 唯一性:同一字典中键不能重复,后定义的键值会覆盖前者(如{'价格':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 的出现,正是为了解决 “五大金刚” 在特定场景下的功能痛点 —— 已总结了这五种容器型数据的核心功能,可结合参考。

image.png

回顾这五种容器型数据的特征,我们能清晰理解 collections 各类数据结构的设计逻辑,读者可先思考以下对应关系:

  • 元组可读性较差但不可修改,字典可读性强且支持修改,将两者特性结合,便有了命名元组(namedtuple)
  • 操作字典时,若访问不存在的键会直接报错,为解决这一问题、让不存在的键返回默认值,便有了默认字典(defaultdict)
  • 计数器(Counter) 是字典的子类,核心优势是能快速统计元素的出现次数,无需手动编写计数逻辑;
  • 普通列表仅支持从尾部高效添加元素,为补充 “从头部快速添加 / 删除元素” 的需求,便有了双端队列(deque)
  • 对多个字典同时进行操作时,常规方法效率较低,为简化多字典联合操作,便有了链式映射(ChainMap)
  • Python 3.7 之前的普通字典无法记录元素的插入顺序,为满足 “记录元素放入顺序” 的需求,便有了有序字典(OrderedDict)(注:Python 3.7 后普通字典已支持有序,但 OrderedDict 仍有特殊场景优势)。

可见,带着 “解决原有容器痛点” 的思路学习 collections,不仅目的性更强,也更容易记住每种数据结构的核心特征。关于 collections 的详细使用方法,此处不再展开,若有需求可参考 Python 官方文档或专业教程。

对我个人而言,目前掌握上述五种基础容器型数据,已能满足大部分常规开发需求。

(二)、流程控制

image.png

流程(flow)指多个业务步骤协同完成完整业务行为的过程。在代码中,除顺序执行外,常需灵活逻辑(如条件判断、重复执行),这依赖控制流程实现。

控制流程(control flow)的核心机制:

  1. 条件执行:用if语句(包括if、if…else、if…elif…else)按条件选择执行代码块(如判断分数是否及格);

  2. 重复执行:
    • while循环:不定次数重复(满足条件持续执行);

    • for循环:定次数重复(按范围或可迭代对象遍历,如计算1到100总和);

异常处理:当代码运行出错(如类型不匹配、索引越界)时,通过识别错误类型并纠正/规避,重新获取程序控制权。

核心场景分为两类:
• 选择执行(条件语句):根据条件执行不同任务分支;

• 重复执行(循环语句):持续执行任务直至满足停止条件。

image.png

1、条件语句:
条件语句(conditional statement)通过判断布尔值(True/False)决定执行哪段代码,根据条件数量分为三种形式:

  1. 单支(if):仅当条件为 True 时执行代码块(条件为 False 则跳过)。
  2. 双支(if-else):条件为 True 时执行 if 块,为 False 时执行 else 块,覆盖二元决策(如“及格/不及格”)。
  3. 多支(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)用于重复执行代码,分为两类:

  1. while 循环(次数未知)
    • 特点:只要条件表达式为 True 就持续执行循环体,条件变为 False 时终止。

    • 注意:需手动控制条件变量(如修改计数器),否则易导致无限循环。

    • 变体:

    ◦ 无限循环(while True):需用 break 手动终止,常用于监听场景;

    ◦ 嵌套循环:在循环内再嵌套循环,实现多层遍历;

    ◦ 单行循环:循环体为单条语句时,可与 while 写在同一行(简化但仅限简单逻辑)。

  2. 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、错误类型:

从宏观层面来看,程序运行过程中出现的错误可分为三大类,其识别难度和表现形式各不相同:

  1. 语法错误(syntax error):最易识别
  2. 运行错误(runtime error):较易识别
  3. 逻辑错误(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 及其子类(即普通运行错误)。接下来,我们一起看看常见的错误类型(下表中红色高亮的字段)。

image.png

4、异常处理:

image.png

异常处理用于预防和应对程序运行中的错误,防止程序因错误直接崩溃,核心针对运行错误(语法错误在运行前已被发现)。

关键操作:

  1. 主动预防/抛出异常:
    • raise:手动触发异常(支持Python内置异常如ZeroDivisionError或自定义异常类)。

    • assert:声明关键条件必须成立(条件为False时抛出AssertionError,终止程序)。

  2. 被动捕捉/处理异常:
    • 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 中,定义函数主要有两种方式:

  1. 使用关键词 def 定义普通函数(normal function)(有明确函数名,可多次调用);
  2. 使用关键词 lambda 定义匿名函数(anonymous function)(无函数名,通常用于临时场景)。

函数的“一等公民”特性​​:函数与变量地位等同,可​​赋值给变量​​、​​存入容器​​、​​作为参数传递​​或​​在函数内返回​​,灵活应用于高阶场景。

函数的核心概念与分类
1、 函数的本质:从数学概念到 Python 实现
“函数(function)”最早源于数学领域,指“每个输入值对应唯一输出值的映射关系”。数学中的函数 y = f(x),与 Python 中的函数 fun(x) 逻辑相通,但 Python 函数更具通用性:

image.png

  • 数学函数:通常支持“多对一”(多个输入对应一个输出),且输入、输出多为单个值;
  • Python 函数:输入(参数)和输出(返回值)均可为多个,且支持更复杂的逻辑(如流程控制、异常处理)。

相比零散的语句,函数有三大核心优势:
• 复用性:定义一次即可多次调用;
• 模块化:拆分复杂代码为独立子任务,降低维护成本;
• 独立命名空间:函数内变量不与外部冲突。

image.png

2、 普通函数(def 定义)
在理解普通函数的各类参数前,需先明确形参实参的核心区别:

  • 形参(parameter):出现在函数定义语句中,是“形式上的参数名称”,用于指定函数可接收的参数类型和数量(如 def fun(x, y): 中的 xy);
  • 实参(argument):在调用函数时传入的值,是“实际参与运算的数据”(如 fun(10, 20) 中的 1020)。

简言之,形参定义了“函数能接收什么”,实参决定了“函数实际接收什么”。

3、 匿名函数(lambda 定义)
普通函数与匿名函数可类比为“性格外向与内向的兄弟”:

  • 普通函数:用 def 关键词显性定义,有明确的函数名,适合多次调用、逻辑复杂的场景;
  • 匿名函数:用 lambda 关键词隐性定义,无函数名,语法简洁(仅一行),适合临时使用、逻辑简单的场景(如作为 sorted()map() 等函数的参数)。

4、 高阶函数
Python 中的函数可分为低阶函数高阶函数

  • 低阶函数:指不依赖其他函数作为参数或返回值的函数,可细分为普通函数和匿名函数;
  • 高阶函数:指满足以下任一条件的函数:
    1. 接收其他函数作为参数(如 map(func, iterable)func 为传入的函数);
    2. 返回一个函数作为结果(如嵌套函数中,外层函数返回内层函数)。

关于高阶函数的具体用法,本文暂不展开讨论,感兴趣的读者可参考相关资料深入学习。

# 普通函数 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 的四大核心特征
面向对象编程有四大核心特征,它们共同支撑起“模块化、可复用、可扩展”的代码设计:

  1. 封装(Encapsulation):将相关数据和方法打包成类,隐藏内部细节,仅暴露统一接口(如手机通过按键/屏幕操作内部电路)。
  2. 继承(Inheritance):子类继承父类的字段和方法(复用逻辑),并可新增或重写方法(扩展功能,如“学生”继承“人”的基础行为并新增“学习”)。
  3. 多态(Polymorphism):同一父类方法在不同子类中有不同实现,父类引用指向子类对象时自动调用子类逻辑(如“计算薪资”在“开发者”“销售”子类中分别按工时/业绩计算)。
  4. 组合(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语法在量化投资里的具体应用。

最后一次编辑于 12小时前 1

暂无评论