Skip to main content

练习

折纸问题

一张厚度为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 官方推荐的单例实现方式,简单且天然线程安全

my_singleton.py - 模块单例
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格式图片为例

OffsetOffset10SizeHex valueValueDescription
BMP Header
0h0242 4D"BM"ID field(42h, 4Dh)
2h2446 00 00 0070 bytes(54+16)BMP 文件的大小(54 字节标头+ 16 字节数据)
6h6200 00Unused特定应用
8h8200 00Unused特定应用
Ah10436 00 00 0054 bytes(14+40)可以找到像素阵列(位图数据)的偏移量
DIB Header-Device Independent Bitmaps-设备无关位图
Eh14428 00 00 0040 bytesDIB 头中的字节数(从此时开始)
12h18402 00 00 002 pixels(left to right order)位图的宽度(以像素为单位)
16h22402 00 00 002 pixels(bottom to top order)位图的高度(以像素为单位)。从下到上的像素顺序为正。
1Ah26201 001 plane使用的颜色平面数量
1Ch28218 0024 bits每个像素的位数
1Eh30400 00 00 000BI_RGB,未使用像素阵列压缩
22h34410 00 00 0016 bytes原始位图数据的大小(包括填充)
26h38413 0B 00 002835 pixels/metre horizontal图像的打印分辨率,
2Ah42413 0B 00 002835 pixels/metre vertical72 DPI × 39.3701 inches per metre yields 2834.6472
2Eh46400 00 00 000 colors调色板中的颜色数量
32h50400 00 00 000 important colors0 表示所有颜色都很重要
Start of pixel array(bitmap data)
36h54300 00 FF0 0 255Red, Pixel(x=0, y=1)
39h573FF FF FF255 255 255White, Pixel(x=1, y=1)
3Ch60200 000 0Padding for 4 byte alignment(could be a value other than zero)
3Eh623FF 00 00255 0 0Blue, Pixel(x=0, y=0)
41h65300 FF 000 255 0Green, Pixel(x=1, y=0)
44h68200 000 0Padding 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,说明你已经走了很远的路了。