练习
折纸问题
一张厚度为N层的纸,每次对折都会使厚度变为原来的2倍,问第多少次对折后,其高度超过珠穆朗玛峰的高度(8848.86米)
题解
def fold_paper(thickness, height):
count = 0
while thickness < height:
thickness *= 2
count += 1
return count
print(fold_paper(0.0001, 8848.86))
打印时间
描述
请从00:00依次打印出一天的时间 示例:
- 23 : 52
- 23 : 53
- 23 : 54
题解
for 时钟 in range(24):
for 分钟 in range(60):
print(时钟, ':', 分钟)
九九乘法表
描述
要求使用循环代码打印一个九九乘法表出来.如下
1*1=1
1*2=2 2*2=4
1*3=3 2*3=6 3*3=9
1*4=4 2*4=8 3*4=12 4*4=16
1*5=5 2*5=10 3*5=15 4*5=20 5*5=25
1*6=6 2*6=12 3*6=18 4*6=24 5*6=30 6*6=36
1*7=7 2*7=14 3*7=21 4*7=28 5*7=35 6*7=42 7*7=49
1*8=8 2*8=16 3*8=24 4*8=32 5*8=40 6*8=48 7*8=56 8*8=64
1*9=9 2*9=18 3*9=27 4*9=36 5*9=45 6*9=54 7*9=63 8*9=72 9*9=81
题解
#方法一
for i in range(1,10):
print()
for j in range(1,i+1):
print('%d*%d=%d' % (j,i,i*j),end=' ')
#方法二
i=1
while i<10: #控制行,1到9
j=1
while j <= i: #控制每行显示的数量,1到9
print("%d*%d=%d"%(j,i,i*j),end=' ') #输出
j+=1 #每行显示的数量加1
print("\n") #每一行结束换行
i+=1 #行数加1
复利的力量
小明从2026年1月1日每日定投100元,年化收益率为10%,考虑到交易日,问小明在第几年可以实现100万的目标?
题解
# 小明从2026年1月1 日每日定投300元,年化收益率为10%,考虑到交易日(周一到周五),问小明在第几年可以实现100万的目标?
from datetime import datetime, timedelta
import calendar
# 计算参数
daily_investment = 100 # 每日定投金额(元)
annual_return_rate = 0.10 # 年化收益率 10%
target_amount = 1000000 # 目标金额 100万元
# 起始日期:2026年1月1日
start_date = datetime(2026, 1, 1)
current_date = start_date
# 计算每日收益率(假设每年约250个交易日)
# 先估算,后面会根据实际交易日数调整
estimated_trading_days = 250
daily_return_rate = (1 + annual_return_rate) ** (1 / estimated_trading_days) - 1
print("=" * 60)
print("定投计算(实际交易日:周一到周五)")
print("=" * 60)
print(f"起始日期: {start_date.strftime('%Y年%m月%d日')}")
print(f"每日收益率: {daily_return_rate * 100:.6f}%")
print(f"每日定投: {daily_investment} 元")
print(f"目标金额: {target_amount:,} 元")
print("-" * 60)
# 模拟每日定投
total_amount = 0 # 当前总金额
trading_day_count = 0 # 交易日计数
current_year = start_date.year
trading_days_this_year = 0 # 当前年度的交易日数
last_year_end_amount = 0 # 上一年结束时的金额
last_year_trading_days = 0 # 上一年的交易日数
while total_amount < target_amount:
# 检查是否是工作日(周一到周五,weekday()返回0-6,0是周一,6是周日)
weekday = current_date.weekday() # 0=周一, 1=周二, ..., 4=周五, 5=周六, 6=周日
# 检查是否跨年(在处理交易日之前)
if current_date.year > current_year:
year_num = current_year - start_date.year + 1
print(f"第 {year_num} 年({current_year} 年)结束时,总金额: {last_year_end_amount:,.2f} 元, 交易日数: {last_year_trading_days} 天")
current_year = current_date.year
trading_days_this_year = 0
last_year_end_amount = total_amount
last_year_trading_days = 0
if weekday < 5: # 周一到周五(0-4)是交易日
trading_day_count += 1
trading_days_this_year += 1
last_year_trading_days = trading_days_this_year
# 先计算昨日收益(复利)
total_amount = total_amount * (1 + daily_return_rate)
# 然后投入今日的定投金额
total_amount += daily_investment
last_year_end_amount = total_amount
# 检查是否达到目标(在交易日达到目标)
if total_amount >= target_amount:
break
# 移动到下一天
current_date += timedelta(days=1)
# 打印最后一年(如果还没打印)
if trading_days_this_year > 0:
year_num = current_year - start_date.year + 1
print(f"第 {year_num} 年({current_year} 年)结束时,总金额: {last_year_end_amount:,.2f} 元,交易日数: {trading_days_this_year} 天")
# 计算达到目标的实际日期
# 由于在交易日达到目标后立即break,current_date就是达到目标的日期
actual_date = current_date
year_num = actual_date.year - start_date.year + 1
print("-" * 60)
print("\n最终结果:")
print("=" * 60)
print(f"达到目标日期: {actual_date.strftime('%Y年%m月%d日')} ({calendar.day_name[actual_date.weekday()]})")
print(f"达到目标时间: 第 {year_num} 年({actual_date.year} 年)的第 {trading_days_this_year} 个交易日")
print(f"总金额: {total_amount:,.2f} 元")
print(f"累计交易日数: {trading_day_count} 天")
print(f"累计投资本金: {trading_day_count * daily_investment:,.2f} 元")
print(f"累计收益: {total_amount - trading_day_count * daily_investment:,.2f} 元")
print(f"收益率: {(total_amount - trading_day_count * daily_investment) / (trading_day_count * daily_investment) * 100:.2f}%")
print(f"实际经过天数: {(actual_date - start_date).days + 1} 天")
print("=" * 60)
字典排序
描述
将字典数组按字典的某个key排序
题解
# 方法1:
sorted(d.cop(),key = lambda i:i[k])
# 方法2:
heappush(h,(i[k],i)) for i in d
单例模式
单例模式目的:让类创建的对象,在系统中只有 唯一的一个实例
特点:每一次执行 类名() 返回的对象,内存地址是相同的
方法一:使用 __new__ 方法
基本实现
class MusicPlayer(object):
# 记录第一个被创建对象的引用
instance = None
def __new__(cls, *args, **kwargs):
# 1. 判断类属性是否为空对象
if cls.instance is None:
# 调用父类方法为第一个对象分配空间
cls.instance = super().__new__(cls)
# 2. 返回类属性保存的对象引用
return cls.instance
# 测试单例效果
player1 = MusicPlayer()
player2 = MusicPlayer()
print(f"player1: {player1}")
print(f"player2: {player2}")
print(f"是否为同一对象: {player1 is player2}") # True
优化:只初始化一次
上述实现中,虽然返回的是同一个对象,但 __init__ 方法会被多次调用
class MusicPlayer(object):
# 记录第一个被创建对象的引用
instance = None
# 记录初始化执行状态
init_flag = False
def __new__(cls, *args, **kwargs):
if cls.instance is None:
cls.instance = super().__new__(cls)
return cls.instance
def __init__(self):
# 1. 判断是否执行过初始化动作
if MusicPlayer.init_flag:
return
# 2. 如果没有执行过就执行初始化动作
print("播放器初始化")
# 3. 修改初始化状态
MusicPlayer.init_flag = True
# 测试
player1 = MusicPlayer() # 输出: 播放器初始化
player2 = MusicPlayer() # 不会再次初始化
方法二:使用装饰器
def singleton(cls):
"""单例装饰器"""
instances = {}
def get_instance(*args, **kwargs):
if cls not in instances:
instances[cls] = cls(*args, **kwargs)
return instances[cls]
return get_instance
@singleton
class MySingleton:
def __init__(self, param):
self.param = param
print(f"初始化参数: {param}")
# 使用示例
if __name__ == '__main__':
a = MySingleton(10) # 输出: 初始化参数: 10
b = MySingleton(20) # 不会再次初始化
print(f"a.param: {a.param}") # 10
print(f"b.param: {b.param}") # 10
print(f"是否为同一对象: {a is b}") # True
- 代码简洁优雅
- 可重用性强
- 不需要修改原类的内部结构
方法三:使用类方法
class Singleton(object):
def __init__(self, name):
self.name = name
@classmethod
def instance(cls, *args, **kwargs):
if not hasattr(cls, "_instance"):
cls._instance = cls(*args, **kwargs)
return cls._instance
# 使用示例
single_1 = Singleton.instance('第1次创建')
single_2 = Singleton.instance('第2次创建')
print(f"single_1.name: {single_1.name}") # 第1次创建
print(f"single_2.name: {single_2.name}") # 第1次创建
print(f"是否为同一对象: {single_1 is single_2}") # True
线程安全版本
上述实现在多线程环境下不安全,需要加锁保护
from threading import RLock
class Singleton(object):
_lock = RLock() # 可重入锁
def __init__(self, name):
self.name = name
@classmethod
def instance(cls, *args, **kwargs):
# 使用锁确保线程安全
with cls._lock:
if not hasattr(cls, "_instance"):
cls._instance = cls(*args, **kwargs)
return cls._instance
方法四:使用元类
class SingletonType(type):
def __call__(cls, *args, **kwargs):
# 创建 cls 的对象时调用
if not hasattr(cls, "_instance"):
# 创建 cls 的对象
cls._instance = super(SingletonType, cls).__call__(*args, **kwargs)
return cls._instance
class Singleton(metaclass=SingletonType):
def __init__(self, name):
self.name = name
# 使用示例
single_1 = Singleton('第1次创建')
single_2 = Singleton('第2次创建')
print(f"single_1.name: {single_1.name}") # 第1次创建
print(f"single_2.name: {single_2.name}") # 第1次创建
print(f"是否为同一对象: {single_1 is single_2}") # True
Singleton是元类SingletonType的实例Singleton('参数')实际上是调用元类的__call__方法- 使用
super()避免递归调用
方法五:使用模块
这是 Python 官方推荐的单例实现方式,简单且天然线程安全
class Singleton:
def __init__(self, name):
self.name = name
def do_something(self):
print(f"{self.name} 正在工作...")
# 创建单例实例
singleton = Singleton('模块单例')
# file1.py
from my_singleton import singleton
print(f"file1 中的 singleton: {singleton}")
# file2.py
from my_singleton import singleton
print(f"file2 中的 singleton: {singleton}")
# 测试文件
import file1
import file2
print(f"是否为同一对象: {file1.singleton is file2.singleton}") # True
进度条打印
描述
字符串具有更丰富的格式化方法,结合转义字符、暂停模块,可以实现进度条打印。
效果如下:
从0%到100%,每0.5秒打印一次,每次在同一行打印
[# ] 0%
[##########] 100%
题解
# 模拟进度条
import time
for i in range(1,10):
print(f"[{'#'*i:<10}]",f"{i*10}%",'\r',end="",flush=True)
time.sleep(0.5)
后缀表达式
描述
后缀表达式,又称逆波兰式,指的是不包含括号,运算符放在两个运算对象的后面,所有的计算按运算符出现的顺序,严格从左向右进行(不再考虑运算符的优先规则)。
例如:后缀表达式为“2 3 + 4 × 5 -”计算过程如下: (1)从左至右扫描,将 2 和 3 压入堆栈; (2)遇到 + 运算符,因此弹出 3 和 2( 3 为栈顶元素,2 为次顶元素,注意与前缀表达式做比较),计算出 3+2 的值,得 5,再将 5 入栈; (3)将 4 入栈; (4)接下来是 × 运算符,因此弹出 4 和 5,计算出 4 × 5 = 20, 将 20 入栈; (5)将 5 入栈; (6)最后是-运算符,计算出 20-5 的值,即 15,由此得出最终结果。
示例
listx = [15, 7, 1, 1, "+", "-", "/", 3, "*", 2, 1, 1, "+", "+", "-"]
题解
# 方法1-python人思维
while len(listx) > 1:
print(listx)
for i in range(len(listx)):
if str(listx[i]) in '+-*/':
if listx[i] == '+':
new = listx[i-2] + listx[i-1]
if listx[i] == '-':
new = listx[i-2] - listx[i-1]
if listx[i] == '*':
new = listx[i-2] * listx[i-1]
if listx[i] == '/':
new = listx[i-2] / listx[i-1]
del listx[i]
del listx[i-1]
listx[i-2] = new
break
print(listx)
# 方法2-利用pop 和 append 仿c语言栈操作
listy = []
for i in listx:
if str(i) not in "+-*/":
listy.append(i) # 入栈
else:
if i == "+":
new = listy.pop() + listy.pop() # 出栈
if i == "-":
new = listy.pop() - listy.pop()
if i == "*":
new = listy.pop() * listy.pop()
if i == "/":
new = listy.pop() / listy.pop()
listy.append(new)
print(listy)
电影演员
描述
小明拿到了一个电影+演员的数据名单,他想设计一个程序,要求: 1.输入演员名 2.如果演员出演了电影,则打印他+他出演的全部电影。程序结束 3.如果演员没有出演电影,则打印查无此人。程序继续
电影 = [
'妖猫传',['黄轩','染谷将太'],
'无问西东',['章子怡','王力宏','祖峰'],
'超时空同居',['雷佳音','佟丽娅','黄轩']]
题解
电影 = [
'妖猫传',['黄轩','染谷将太'],
'无问西东',['章子怡','王力宏','祖峰'],
'超时空同居',['雷佳音','佟丽娅','黄轩']]
# 如果查到了:打印出演员+【所有的】电影,循环结束
# 如果没查到,就 循环继续,并且打印【查无此人】
找到了吗 = 0
while True:
name = input('你要找的演员')
for i in 电影:
if name not in i :
a = i #暂存---for 是逐一提取数据,并赋值
else:
print(name,'出演了',a)
找到了吗 += 1
if 找到了吗 != 0 : # 不等于 0 就代表它找到了
break
print('【查无此人】') # 1号位
规整的打印考场号
描述
学校有440人参加考试,1号考场有80个座位,要求座位号为0101--0180 后面每个考场40个座位: 2号考场考试号要求为0201--0240 3号考场考试号要求为0301--0340 后续考场以此类推,请你打印出来这些考场号吧
题解
for i in range(1,440):
if i <= 80 :
print('01{:0>2d}'.format(i))
elif i <= 440:
if i%40 == 0:
print('{:0>2d}{:0>2d}'.format(i//40-1,40))
else:
print('{:0>2d}{:0>2d}'.format(i//40,i%40))
读取BMP文件
描述
不使用第三方模块的前提下,完成对24位bmp图像的图像数据分析与像素读取。 程序设计需要体现面向对象编程的特点,以创建类的形式编写。
参考资料:
以一张2*2的24位图的bmp格式图片为例
| Offset | Offset10 | Size | Hex value | Value | Description |
|---|---|---|---|---|---|
| BMP Header | |||||
| 0h | 0 | 2 | 42 4D | "BM" | ID field(42h, 4Dh) |
| 2h | 2 | 4 | 46 00 00 00 | 70 bytes(54+16) | BMP 文件的大小(54 字节标头+ 16 字节数据) |
| 6h | 6 | 2 | 00 00 | Unused | 特定应用 |
| 8h | 8 | 2 | 00 00 | Unused | 特定应用 |
| Ah | 10 | 4 | 36 00 00 00 | 54 bytes(14+40) | 可以找到像素阵列(位图数据)的偏移量 |
| DIB Header-Device Independent Bitmaps-设备无关位图 | |||||
| Eh | 14 | 4 | 28 00 00 00 | 40 bytes | DIB 头中的字节数(从此时开始) |
| 12h | 18 | 4 | 02 00 00 00 | 2 pixels(left to right order) | 位图的宽度(以像素为单位) |
| 16h | 22 | 4 | 02 00 00 00 | 2 pixels(bottom to top order) | 位图的高度(以像素为单位)。从下到上的像素顺序为正。 |
| 1Ah | 26 | 2 | 01 00 | 1 plane | 使用的颜色平面数量 |
| 1Ch | 28 | 2 | 18 00 | 24 bits | 每个像素的位数 |
| 1Eh | 30 | 4 | 00 00 00 00 | 0 | BI_RGB,未使用像素阵列压缩 |
| 22h | 34 | 4 | 10 00 00 00 | 16 bytes | 原始位图数据的大小(包括填充) |
| 26h | 38 | 4 | 13 0B 00 00 | 2835 pixels/metre horizontal | 图像的打印分辨率, |
| 2Ah | 42 | 4 | 13 0B 00 00 | 2835 pixels/metre vertical | 72 DPI × 39.3701 inches per metre yields 2834.6472 |
| 2Eh | 46 | 4 | 00 00 00 00 | 0 colors | 调色板中的颜色数量 |
| 32h | 50 | 4 | 00 00 00 00 | 0 important colors | 0 表示所有颜色都很重要 |
| Start of pixel array(bitmap data) | |||||
| 36h | 54 | 3 | 00 00 FF | 0 0 255 | Red, Pixel(x=0, y=1) |
| 39h | 57 | 3 | FF FF FF | 255 255 255 | White, Pixel(x=1, y=1) |
| 3Ch | 60 | 2 | 00 00 | 0 0 | Padding for 4 byte alignment(could be a value other than zero) |
| 3Eh | 62 | 3 | FF 00 00 | 255 0 0 | Blue, Pixel(x=0, y=0) |
| 41h | 65 | 3 | 00 FF 00 | 0 255 0 | Green, Pixel(x=1, y=0) |
| 44h | 68 | 2 | 00 00 | 0 0 | Padding for 4 byte alignment(could be a value other than zero) |
bit(位)比特是计算机运算的基础,属于二进制的范畴
byte字节是内存的基本单位
8 bit = 1 byte
# 参考知识
data = b'\xff' # b代表这是一个二进制数据,\x代表这是一个十六进制的数据
bin_data = bin(int.from_bytes(data))[2:] # -> 11111111
int(bin_data, 2) # -> 255
# 打开文件作为可编辑对象
with open("r.bmp", "rb") as f:
d = f.read()
data = bytearray(d)
# 试着把54到246的数据都改成0x00,即黑色。这样整张图片都变成黑色了
for i in range(54, 246):
data[i]= 0x00
# 保存文件
with open("black.bmp", "wb") as f:
f.write(data)
题解
class Readbmp:
"""
read bmp files
图片的格式说明:https://en.wikipedia.org/wiki/BMP_file_format#Example_1
"""
def __init__(self, pic_path) -> None:
self.pic_path = pic_path
self.read_color()
def read_color(self):
if self.pic_path.endswith(".bmp"):
self.read_bmp()
else:
print("不支持的格式")
def read_bmp(self):
bin_datas = []
"""read file data to bin"""
with open(self.pic_path, "rb") as f:
while True:
if len(bin_datas) == f.tell():
data = f.read(1)
bindata = bin(int.from_bytes(data))[2:]
if len(bindata) < 8:
bindata = (8 - len(bindata)) * "0" + bindata
bin_datas.append(bindata)
else:
bin_datas = bin_datas[:-1]
break
self.bin_pic_head = bin_datas[0:2] # ID field
self.bin_pic_size = bin_datas[2:6] # Size of the BMP file 文件大小
self.bin_pic_exe = bin_datas[6:10] # 特定应用,默认为0
self.bin_pic_address = bin_datas[10:14] # 图片信息开始地址
self.bin_pic_dib = bin_datas[14:18] # DIB 头中的字节数
self.bin_pic_w = bin_datas[18:22] # 图片像素宽度
self.bin_pic_h = bin_datas[22:26] # 图片像素高度
self.bin_pic_color_num = bin_datas[26:28] # 使用颜色平面数
self.bin_pic_color_long = bin_datas[28:30] # 每个像素位数
self.bin_pic_bi = bin_datas[30:34] # BI_RGB
self.bin_pic_big = bin_datas[34:38] # 原始图像数据大小
self.bin_pic_printpix = bin_datas[38:42] # 打印分辨率
self.bin_pic_dpi = bin_datas[42:46] # DPI
self.bin_pic_color_num = bin_datas[46:50] # 调色板中颜色数量
self.bin_pic_color_important = bin_datas[50:54] # 重要颜色数量
self.bin_pic_data = bin_datas[54:] # 图片数据
self.bin_to_pic()
# 将二进制数据转化成十进制数据
def bin_to_dec(self, bin_datas):
bin_data = ""
for i in reversed(bin_datas):
bin_data += i
return int(bin_data, 2)
# 将列表转为3个一组的二维列表
def change_data(self, data):
data_2d = []
x = []
for i in data:
x.append(int(i, 2))
if len(x) == 3:
data_2d.append(tuple(x))
x = []
return data_2d
# 处理图片数据
def bin_to_pic(self):
self.pic_head = chr(int(self.bin_pic_head[0], 2)) + chr(
int(self.bin_pic_head[1], 2)
)
self.pic_size = self.bin_to_dec(self.bin_pic_size)
self.pic_exe = self.bin_to_dec(self.bin_pic_exe)
self.pic_address = self.bin_to_dec(self.bin_pic_address)
self.pic_dib = self.bin_to_dec(self.bin_pic_dib)
self.pic_w = self.bin_to_dec(self.bin_pic_w)
self.pic_h = self.bin_to_dec(self.bin_pic_h)
self.pic_color_num = self.bin_to_dec(self.bin_pic_color_num)
self.pic_color_long = self.bin_to_dec(self.bin_pic_color_long)
self.pic_bi = self.bin_to_dec(self.bin_pic_bi)
self.pic_big = self.bin_to_dec(self.bin_pic_big)
self.pic_printpix = self.bin_to_dec(self.bin_pic_printpix)
self.pic_dpi = self.bin_to_dec(self.bin_pic_dpi)
self.pic_color_num = self.bin_to_dec(self.bin_pic_color_num)
self.pic_color_important = self.bin_to_dec(self.bin_pic_color_important)
self.pic_data = self.change_data(self.bin_pic_data)
# 打印图片信息
def show(self):
print(
"""
文件ID {}
图像大小(Byte) {}
特定应用 {}
图片信息开始地址 {}
DIB 头中的字节数 {}
图片像素宽度 {}
图片像素高度 {}
使用颜色平面数 {}
每个像素位数 {}
BI_RGB {}
原始图像数据大小(Byte) {}
打印分辨率 {}
DPI {}
调色板中颜色数量 {}
重要颜色数量 {}
图片数据 {} .... {}
""".format(
self.pic_head,
self.pic_size,
self.pic_exe,
self.pic_address,
self.pic_dib,
self.pic_w,
self.pic_h,
self.pic_color_num,
self.pic_color_long,
self.pic_bi,
self.pic_big,
self.pic_printpix,
self.pic_dpi,
self.pic_color_num,
self.pic_color_important,
self.pic_data[:5],
self.pic_data[-5:],
)
)
# 判断颜色
def color(self, color):
b, g, r = color[0], color[1], color[2]
if r == 0 and g == 0 and b == 0:
return "黑色"
elif r == 0 and g == 0 and b == 255:
return "蓝色"
elif r == 0 and g == 255 and b == 0:
return "绿色"
elif r == 255 and g == 0 and b == 0:
return "红色"
elif r == 255 and g == 255 and b == 255:
return "白色"
else:
return "其他颜色"
# 统计颜色
def count_color(self):
color_dict = {}
for i in self.pic_data:
if i in color_dict:
color_dict[i] += 1
else:
color_dict[i] = 1
return color_dict
# 判断颜色的比例
def color_percent(self):
color_dict = self.count_color()
color_percent_dict = {}
for i in color_dict:
color_percent_dict[self.color(i)] = int(
color_dict[i] / len(self.pic_data) * 100
)
for i in color_percent_dict:
print("{} 占比百分之 {}".format(i, color_percent_dict[i]))
p = Readbmp("r.bmp") # 另存为新文件
p.color_percent()
# 红色 占比百分之 100
"""
r.bmp是8*8的位图,其中有一个点是红色,其他都是黑色
"""
# 打开文件作为可编辑对象
with open("r.bmp", "rb") as f:
d = f.read()
data = bytearray(d)
# 试着把54到246的数据都改成0x00,即黑色。这样整张图片都变成黑色了(也可以只更改某个数据端)
for i in range(54, 246):
data[i]= 0x00
# 保存文件
with open("rn.bmp", "wb") as f:
f.write(data)
p = Readbmp("rn.bmp")
p.show()
p.color_percent()
# 黑色 占比百分之 100
Python cookbook
更多练习,请访问Python cookbook
如何成为某个领域的专家?选一个你愿意啃数年的主线:比如 Python 后端 / 数据分析 / AI 应用 / 工具链。
持续做 真正有交付结果的东西:开源项目、被人在用的小工具、线上服务、课程/文档。
如果有一天,你遇到了一些Python的BUG,可以去Python的官方仓库为它们提交PR,说明你已经走了很远的路了。