1 常用基本数据类型
1.1 引言:Python数据类型的重要性
在金融和商业数据分析中,选择正确的数据类型是构建可靠程序的基础。Python作为一门强类型动态语言,提供了丰富的基本数据类型,每种类型都有其特定的应用场景和操作规则。本章将深入介绍Python中最常用的基本数据类型,包括数值型、字符串型和布尔型,并通过金融实例演示其实际应用。
1.2 本章学习目标
通过本章学习,你将能够:
- 说出
int、float、str、bool四种基本数据类型的特点,并根据金融场景(金额、证券代码、日期、交易信号)为数据选择合适的类型 - 使用索引与切片(
s[0]、s[2:5]、s[-3:])提取字符串信息,并用+、upper()、"{:.2f}".format()完成字符串拼接与格式化输出 - 使用
int()、float()、str()、bool()进行显式类型转换,并解释强类型语言为何不允许'10.55' + 100这样的隐式运算 - 运用比较运算与逻辑运算符(
and、or、not)表达交易条件,并说出真值测试中哪些值被视为 False - 解释 IEEE 754 浮点误差的来源,并在金额比较中使用
math.isclose()或decimal.Decimal规避精度陷阱
1.3 数值类型(Numeric Types)
Python中的数值类型主要包括两大类:整型(int)和浮点型(float)。在Python 3中,整型的理论精度只受限于计算机内存,而浮点型则遵循IEEE 754标准的双精度浮点数表示。
1.3.1 任务一计算普通年金终值
普通年金(Ordinary Annuity)是金融学中的一个核心概念,指在每期期末进行等额收付的系列款项。其终值(Future Value)计算公式为:
\[ FV = A \times \frac{(1+i)^n - 1}{i} \]
其中:
- \(FV\): 年金终值
- \(A\): 每期年金金额
- \(i\): 每期利率
- \(n\): 期数
数学推导: 该公式的推导基于等比数列求和。设每期末支付金额为A,利率为i,期数为n,则第n期末的终值为各期支付及其利息的累加:
\[ FV = A(1+i)^{n-1} + A(1+i)^{n-2} + \cdots + A(1+i) + A \]
这是一个首项为A,公比为(1+i),项数为n的等比数列。根据等比数列求和公式:
\[ S_n = \frac{a_1(1-q^n)}{1-q} \]
代入得:
\[ FV = \frac{A[(1+i)^n - 1]}{(1+i) - 1} = A \times \frac{(1+i)^n - 1}{i} \]
以下代码展示了如何使用Python计算普通年金终值:
任务要求:给定每期年金金额 A=10000、年利率 i=0.05、投资期限 n=10,按普通年金终值公式 \(F=A\times\frac{(1+i)^n-1}{i}\) 计算终值,并用 "{:.2f}".format(F) 保留两位小数输出。请将代码原样输入教学平台(注释除外),判定以平台为准。
# ⚠️ 平台原始代码 - 请原样输入至教学平台(注释除外),平台才会判定答案正确
#任务一:完成计算普通年金终值的小程序
#计算普通年金终值
A=10000
i=0.05 # 设置年利率为5%
n=10 # 设置投资期限为10年
F=A*((1+i)**n-1)/i # 根据普通年金终值公式计算终值F
print("普通年金终值是{:.2f}".format(F)) # 格式化输出普通年金终值(保留两位小数)预期输出(本机 Python 实际运行结果,具体以平台运行结果为准):
普通年金终值是125778.93
代码解析:
变量赋值: Python使用
=进行变量赋值,这是引用绑定而非拷贝。数值类型是不可变对象(Immutable),每次运算都会创建新对象。幂运算:
**是Python的幂运算符,** n表示n次方。这在金融复利计算中非常常用。运算符优先级: 表达式
A * ((1 + i) ** n - 1) / i中,括号具有最高优先级,确保先计算(1 + i),再进行幂运算,然后减1,最后进行乘除运算。字符串格式化:
"{:.2f}".format(F)使用Python的format方法进行格式化输出,.2f表示保留两位小数。
实际应用场景:
- 养老金规划: 计算每月定投养老金在退休时的总额
- 教育金储备: 计算为子女每月储蓄教育基金的未来价值
- 贷款偿债基金: 企业为偿还到期债务而建立的偿债基金
1.4 字符串类型(String Types)
字符串是Python中用于表示文本数据的数据类型。在金融应用中,字符串常用于处理证券代码、公司名称、交易日期等信息。Python 3中的字符串采用Unicode编码,可以处理多语言文本。
1.4.1 任务二字符串的基本操作
以下代码演示了字符串的创建、索引、切片、连接和大小写转换等基本操作:
任务要求:给定字符串 s='ilovepython' 与 a='helloworld',依次输出 5 行内容:完整字符串 s、第 1 个字符 s[0]、切片 s[2:5]、s 与 a 的拼接结果、s 转大写后的结果。判定以平台为准。
# ⚠️ 平台原始代码 - 请原样输入至教学平台(注释除外),平台才会判定答案正确
#任务二:给定字符串按要求输出
s = 'ilovepython'
a = 'helloworld' # 定义第二个字符串变量
print(s) # 输出完整字符串s
print(s[0]) # 输出字符串s的第一个字符(索引为0)
print(s[2:5]) # 输出字符串s的切片(索引2到4)
print(s+a) # 输出两个字符串拼接后的结果
print(s.upper()) # 输出字符串s转换为大写后的结果预期输出(本机 Python 实际运行结果,具体以平台运行结果为准):
ilovepython
i
ove
ilovepythonhelloworld
ILOVEPYTHON
代码深度解析:
字符串的不可变性: 与数值类型一样,Python字符串也是不可变对象。所有看似”修改”字符串的操作(如
upper())实际上都是创建了新的字符串对象。索引机制:
- 正向索引: 0, 1, 2, … (从左到右)
- 负向索引: -1, -2, -3, … (从右到左)
- 例如:
s[-1]会得到最后一个字符’n’
切片的高级用法
s[:3]省略start,默认从0开始,得到’ilo’s[3:]省略end,默认到字符串末尾,得到’vepython’s[::2]步长为2,得到’ioeyhn’s[::-1]步长为-1,实现字符串反转,得到’nohtypevoli’
字符串编码与Unicode: Python 3采用Unicode编码,每个字符占用1-4个字节。这对处理中文金融数据非常重要:
# ==================== 中文字符串示例 ====================
chinese = '沪深300指数' # 创建中文字符串
# len()返回字符串的字符数(不是字节数)
print(len(chinese)) # 输出: 7 (7个字符)
# 索引访问单个字符
print(chinese[0]) # 输出: 沪 (第一个字符)
# 切片获取子字符串
print(chinese[:2]) # 输出: 沪深 (前两个字符)- 字符串格式化方法的演进:
- 旧式格式化:
"%s %.2f" % ("金额", 123.456) - format方法:
"{0} {1:.2f}".format("金额", 123.456) - f-string(Python 3.6+):
f"金额 {123.456:.2f}"(推荐)
- 旧式格式化:
金融应用实例:
# =============================================================================
# 金融应用:证券代码与公司名称处理
# =============================================================================
# ==================== 证券代码标准化 ====================
# 场景:将股票代码与交易所代码合并,生成完整的证券代码
stock_code = '000001' # 股票代码(数字部分)
exchange = 'SZ' # 交易所代码(SZ=深圳,SH=上海)
# 使用+运算符连接字符串
# 生成完整代码:000001.SZ(平安银行)
full_code = stock_code + '.' + exchange
print(full_code) # 输出: 000001.SZ
# ==================== 公司名称格式化 ====================
# 场景:将小写的公司名称转换为标准格式(首字母大写)
company = 'china construction bank' # 小写的公司名称
# title()方法将每个单词的首字母大写
# 输出: China Construction Bank
print(company.title())
# ==================== 提取市场信息 ====================
# 场景:从完整证券代码中提取交易所信息
code_with_exchange = '600000.SH' # 完整代码(浦发银行)
# split('.')方法按点号分割字符串
# 返回一个列表:['600000', 'SH']
# [1]表示取列表的第二个元素(索引为1)
market = code_with_exchange.split('.')[1]
print(market) # 输出: SH (上海证券交易所)1.5 类型转换(Type Conversion)
在实际编程中,经常需要在不同数据类型之间进行转换。Python提供了内置函数来实现类型转换:
表:常用类型转换函数
| 函数 | 功能 | 示例 | 金融应用场景 |
|---|---|---|---|
int() |
转换为整型 | int('123') → 123 |
将字符串形式的交易量转换为整数 |
float() |
转换为浮点型 | float('3.14') → 3.14 |
将字符串形式的价格转换为浮点数 |
str() |
转换为字符串 | str(100) → ‘100’ |
将数值转换为字符串用于输出 |
bool() |
转换为布尔型 | bool(1) → True |
判断交易状态 |
补充说明:类型系统的理论背景
上表中的转换函数之所以必须显式写出,根源在于Python的类型系统设计。从编程语言理论的角度来看,Python的类型系统属于动态类型系统(Dynamic Typing)与强类型系统(Strong Typing)的结合。这意味着:
- 动态类型: 变量在运行时自动绑定类型,无需显式声明
- 强类型: 不同类型之间不会进行隐式转换,需要显式转换
这种设计在金融数据处理中尤为重要,因为它可以避免因类型转换导致的精度丢失和计算错误。例如,在处理金额时,Python不会自动将浮点数转换为整数,从而保证了计算精度。
易混淆概念辨析:隐式转换与显式转换
在弱类型语言(如JavaScript)中,不同类型之间可以自动进行隐式转换,例如'1' + 2会得到'12'。但在Python这种强类型语言中,这会抛出TypeError异常。Python要求程序员显式指定类型转换,这虽然增加了编码负担,但大大降低了程序出错的概率——这在金融系统中至关重要,因为一个隐式转换错误可能导致巨大的资金损失。
# =============================================================================
# 题目:类型转换的正确与错误方式
# =============================================================================
# ==================== 正确的显式转换 ====================
# 场景:计算交易金额
price_str = '10.55' # 字符串形式的价格
price_num = float(price_str) # 显式转换为浮点数
quantity = 100 # 数量(整数)
amount = price_num * quantity # 计算金额
print(f"交易金额: {amount:.2f}") # 输出: 交易金额: 1055.00
# ==================== 错误的隐式转换演示 ====================
# 在Python中,不同类型不能直接运算
try:
# 尝试将字符串与整数直接相加
# 这会抛出TypeError异常
result = '10.55' + 100
except TypeError as e:
# 捕获异常并打印错误信息
print(f"错误: {e}")
# 输出: can only concatenate str (not "int") to str
# 含义:只能将字符串与字符串连接,不能与整数连接1.6 布尔类型(Boolean Types)
布尔类型只有两个值:True和False。在金融决策系统中,布尔类型广泛用于条件判断、风险评估和信号生成。
# =============================================================================
# 题目:布尔类型在金融交易中的应用
# =============================================================================
# ==================== 布尔值的创建 ====================
# 布尔值用于表示逻辑真或假
is_trading_day = True # 是否为交易日
is_holiday = False # 是否为节假日
# ==================== 比较运算符返回布尔值 ====================
# 比较运算符:>, <, >=, <=, ==, !=
# 它们总是返回布尔值(True或False)
price = 105.5 # 当前价格
reference_price = 100.0 # 参考价格(如移动平均线)
# 判断价格是否高于参考价
is_above_ma = price > reference_price
print(is_above_ma) # 输出: True(判断当前价格是否站上均线参考价)
# ==================== 逻辑运算符 ====================
# 逻辑运算符:and(与), or(或), not(非)
# 用于组合多个布尔条件
price_above_ma = price > 100 # 价格高于100
volume_above_avg = True # 成交量高于平均值(假设已计算)
# and运算:两个条件都为True时,结果才为True
buy_signal = price_above_ma and volume_above_avg
print(buy_signal) # 输出: True
# ==================== 实际应用:交易信号生成 ====================
# 场景:根据价格判断是否平仓
current_price = 102.5 # 当前价格
stop_loss = 98.0 # 止损价
take_profit = 108.0 # 止盈价
# or运算:任一条件为True时,结果为True
# 生成平仓信号:价格触及止损线或止盈线时平仓
should_close = current_price <= stop_loss or current_price >= take_profit
print(f"是否平仓: {should_close}") # 输出: 是否平仓: False补充说明:真值测试(Truth Value Testing)
在Python中,所有对象都可以进行布尔值测试。以下是 falsy(被视为False)的值:
None和False- 数值0 (
0,0.0,0j) - 空序列 (
'',(),[],{}) - 用户自定义类的
__bool__()或__len__()方法返回0或False
其他所有值都被视为truthy。这在金融数据处理中非常有用:
# =============================================================================
# 题目:真值测试在数据验证中的应用
# =============================================================================
# ==================== 检查数据是否存在 ====================
# 场景:验证交易数据是否为空
trading_data = [] # 空列表(无数据)
# if语句自动进行布尔测试
# 空列表[]被视为False,非空列表被视为True
if trading_data: # 等价于 if len(trading_data) > 0
print("数据存在,开始分析")
else:
print("数据为空,跳过分析") # 会执行这行
# ==================== 检查价格是否有效 ====================
# 场景:验证价格数据是否有效
price = 0 # 价格为0(无效)
# 数值0被视为False,非零数值被视为True
if price: # 等价于 if price != 0
print("有效价格")
else:
print("无效价格,需检查") # 会执行这行1.7 数值精度与金融计算
在金融计算中,数值精度是一个关键问题。Python的浮点数遵循IEEE 754标准,存在精度限制。这对金融计算可能产生影响。
补充说明:浮点数精度的数学本质
IEEE 754双精度浮点数使用64位存储:
- 符号位: 1位
- 指数位: 11位
- 尾数位: 52位
这提供了约15-17位的有效十进制数字精度,但也引入了舍入误差。
# =============================================================================
# 题目:浮点数精度问题及解决方案
# =============================================================================
# ==================== 浮点数精度问题演示 ====================
# 场景:简单的加法运算
result1 = 0.1 + 0.2
print(result1) # 输出: 0.30000000000000004
# 问题:不是精确的0.3,而是有一个非常小的误差
# 原因:0.1和0.2在二进制中无法精确表示
# ==================== 比较运算的陷阱 ====================
# 场景:比较浮点数是否相等
print(0.1 + 0.2 == 0.3) # 输出: False
# 问题:由于精度误差,直接用==比较会得到错误结果
# ==================== 正确的比较方法 ====================
# 使用math.isclose()进行近似比较
import math
# math.isclose(a, b)判断a和b是否近似相等
print(math.isclose(0.1 + 0.2, 0.3)) # 输出: True
# ==================== 金融计算的解决方案 ====================
# 使用Decimal模块进行精确的十进制运算
from decimal import Decimal, getcontext
# 设置计算精度(有效数字位数)
getcontext().prec = 6 # 设置为6位有效数字
# 使用Decimal创建精确的小数
price1 = Decimal('0.1') # 注意:参数必须是字符串
price2 = Decimal('0.2')
result2 = price1 + price2
print(result2) # 输出: 0.3 (精确值)
print(result2 == Decimal('0.3')) # 输出: True最佳实践:
- 货币计算: 使用
decimal.Decimal模块确保精度 - 大额计算: 使用整数(以分为单位)而非浮点数
- 科学计算: 使用
numpy.float64提高精度 - 比较运算: 使用
math.isclose()而非==
1.8 本章小结
要点:
- 数值分整型与浮点型:整型精度不受限,浮点型遵循 IEEE 754,货币计算需警惕舍入误差
- 字符串不可变,索引从 0 开始,切片含头不含尾(
s[2:5]只取索引 2、3、4) - 类型转换必须显式:
float('10.55')可行,而'10.55' + 100抛TypeError - 布尔值由比较运算产生,用
and/or/not组合条件;0、''、空列表均为假值 - 浮点数比较用
math.isclose(),精确金额运算用decimal.Decimal
易错点:
- 切片右端点取不到:
s[2:5]得到 3 个字符而非 4 个 0.1 + 0.2 == 0.3返回False,不要用==直接比较浮点数- 字符串与数值直接相加会报错,须先
str()转换或改用 f-string "{:.2f}".format(F)只影响显示格式,不改变变量本身的值
1.9 动手与思考
以下练习每题附参考答案(默认折叠)。请先独立完成并写下你的判断,再点开对照,最后上机验证。
输出预测:不运行代码,先写出下面代码的输出结果,再上机检验你的判断。
s = 'ilovepython' print(s[2:5]) print(s[-3:]) print(len(s)) print(s.upper()[:4])参考答案(先写下你的预测再点开)
解题思路:先逐行推演,再对照实际输出。字符串
s = 'ilovepython'共 11 个字符,索引 0 到 10 依次为i(0)、l(1)、o(2)、v(3)、e(4)、p(5)、y(6)、t(7)、h(8)、o(9)、n(10)。第 1 个print执行s[2:5],切片含头不含尾,只取索引 2、3、4 的字符,得到ove;第 2 个执行s[-3:],负索引从末尾计数,-3对应索引 8,取索引 8、9、10 直到末尾,得到hon;第 3 个执行len(s),返回字符个数11;第 4 个执行s.upper()[:4],先整体转大写得到'ILOVEPYTHON',再取前 4 个字符(方法与切片可以链式连用),得到ILOV。# 验证脚本:逐行输出四个表达式的求值结果 s = 'ilovepython' # 定义待处理的字符串 print(s[2:5]) # 切片取索引2、3、4的字符(含头不含尾) print(s[-3:]) # 负索引从倒数第三个取到末尾 print(len(s)) # 输出字符串的字符个数 print(s.upper()[:4]) # 先转大写再取前四个字符预期输出(本机 peter 环境实际运行结果,具体以平台运行结果为准):
ove hon 11 ILOV回扣本章:对应本章小结“易错点”第 1 条——切片
s[2:5]只取索引 2、3、4 三个字符,右端点取不到。概念辨析:
print('1' + '2')与print(1 + 2)的结果分别是什么?为什么float('3.5')能得到 3.5,而int('3.5')会报错?bool(0)、bool('')、bool('0')分别返回什么?参考答案(点开前请先独立完成)
解题思路:逐点作答。第一,
'1' + '2'是两个字符串相加,+对字符串执行拼接,输出12;1 + 2是两个整数相加,输出3。同样的+号,运算含义由操作数类型决定,这正是 Python 强类型的体现。第二,float('3.5')把整段字符串解析成一个浮点数值,3.5 本身是合法的浮点字面值,因此得到 3.5;而int('3.5')要求字符串整体是一个整数字面值,解析器遇到小数点.就无法继续,抛出ValueError(若想先把 3.5 转成整数,应写int(float('3.5')),结果为 3,即先转浮点再截断取整)。第三,真值测试看的是“值本身是否为空或为零”:bool(0)数值为零返回False;bool('')空字符串返回False;bool('0')是长度为 1 的非空字符串,返回True——字符串只看是否为空,不看内容像不像零。# 验证脚本:逐个输出题中表达式的结果 print('1' + '2') # 字符串拼接,输出12 print(1 + 2) # 整数相加,输出3 print(float('3.5')) # 字符串解析为浮点数 print(bool(0), bool(''), bool('0')) # 三个值的真值判定 try: int('3.5') # 尝试把带小数点的字符串直接转为整数 except ValueError as error_msg: print('int("3.5") 报错:', error_msg) # 捕获并显示解析错误信息预期输出(本机 peter 环境实际运行结果,具体以平台运行结果为准):
12 3 3.5 False False True int("3.5") 报错: invalid literal for int() with base 10: '3.5'回扣本章:对应本章小结“要点”第 3 条(类型转换必须显式)与第 4 条(
0、''均为假值),以及“易错点”第 3 条(字符串与数值直接相加会报错)。变式任务(平台任务一同型改造):某投资者每季度末定投 A=2000 元,季度利率 i=0.02,共投资 n=20 个季度。仿照平台任务一编写程序,输出普通年金终值(保留两位小数)。进一步思考:若改为每期期初投入(先付年金),终值应如何在普通年金公式基础上调整?
参考答案(点开前请先独立完成)
解题思路:普通年金(期末投入)终值公式为 \(F=A\times\frac{(1+i)^n-1}{i}\),把 A=2000、i=0.02、n=20 代入即可。先付年金(期初投入)的每一笔投入都比期末投入多计一期利息,即每期终值整体乘以 \((1+i)\),故先付年金终值 = 普通年金终值 \(\times(1+i)\),这只需在原公式外再乘一个 \((1+i)\) 因子。
# 变式程序:计算季度定投的普通年金终值与先付年金终值 A = 2000 # 每期定投金额(元) i = 0.02 # 季度利率(2%) n = 20 # 投资期数(20个季度) F = A * ((1 + i) ** n - 1) / i # 普通年金终值:每期期末投入 F_due = F * (1 + i) # 先付年金终值:每期期初投入,整体多计一期利息 print('普通年金终值是{:.2f}'.format(F)) # 保留两位小数输出普通年金终值 print('先付年金终值是{:.2f}'.format(F_due)) # 保留两位小数输出先付年金终值预期输出(本机 peter 环境实际运行结果,具体以平台运行结果为准):
普通年金终值是48594.74 先付年金终值是49566.63注意:以上为本题变式的独立代码;列表 1.1 对应平台任务的原始代码块仍须原样输入教学平台,不要用本变式替换。
回扣本章:对应本章小结“要点”第 1 条(整型精度不受限、浮点遵循 IEEE 754,货币计算警惕舍入误差)与“易错点”第 4 条(
"{:.2f}".format(F)只影响显示格式,不改变变量本身的值)。动手验证:先
import math与from decimal import Decimal,再分别运行print(0.1 + 0.2 == 0.3)、print(math.isclose(0.1 + 0.2, 0.3))与print(Decimal('0.1') + Decimal('0.2')),解释三者输出的差异,并说明金额计算应采用哪种方案。参考答案(点开前请先独立完成)
解题思路:三者输出的差异源于十进制小数在二进制浮点数中的表示误差。第一,
0.1与0.2在 IEEE 754 双精度下都无法精确表示,相加结果约为 0.30000000000000004,与字面值 0.3 并不逐位相等,因此==返回False。第二,math.isclose()在默认相对容差(约 \(10^{-9}\))内比较两个数,误差远小于容差,返回True,它是浮点数比较的正确姿势。第三,Decimal按十进制精确存储与运算,Decimal('0.1') + Decimal('0.2')严格等于 0.3,输出0.3。金额计算建议:对精度有硬性要求的货币运算(清算、对账、税费)用decimal.Decimal;一般的浮点比较用math.isclose(),避免用==直接比较。# 验证脚本:对比 ==、math.isclose 与 Decimal 三种处理方式 import math # 导入数学模块,提供isclose近似比较 from decimal import Decimal # 导入十进制精确运算模块 print(0.1 + 0.2 == 0.3) # 直接用==比较,受浮点误差影响 print(math.isclose(0.1 + 0.2, 0.3)) # 容差内近似比较,返回True print(Decimal('0.1') + Decimal('0.2')) # 十进制精确加法,结果恰为0.3预期输出(本机 peter 环境实际运行结果,具体以平台运行结果为准):
False True 0.3回扣本章:对应本章小结“要点”第 5 条与“易错点”第 2 条——
0.1 + 0.2 == 0.3返回False,浮点数比较应使用math.isclose(),精确金额运算使用decimal.Decimal。