日志分析太头疼?从数据容器到函数封装,这篇Python实战教程帮你自动搞定!

摘要
平时写Python代码的时候,咱们经常会碰到这么几个问题:存一堆数据不知道用什么类型合适、读写文件老是搞混模式、重复的代码写了一遍又一遍。这篇文章就专门唠唠这三个基础但超实用的知识点——数据容器(列表、元组、字典)、文件操作,还有函数。不管你是刚学Python,还是平时写脚本总踩坑,看完这篇都能直接用上。
引言
现在不管是做数据分析、写自动化脚本,还是搞后端开发,Python都是绕不开的工具。就拿日常的小需求来说:统计Excel里的销售数据、批量处理日志文件、自动生成报表……这些都离不开数据容器来存数据、文件操作来读写内容,还有函数来封装重复逻辑。今天咱们不搞虚的,直接对着实际场景讲,每个知识点都配能跑的代码,看完就能上手。
数据容器:一次存多个数据的好帮手
列表:最常用的“收纳盒”
为啥要用列表?
你想啊,如果要存一个班级30个学生的名字,总不能定义30个变量吧?name1、name2……写到第10个就该烦了。这时候列表就派上用场了,它能一次装一堆数据,而且数据类型还能不一样——比如你可以把字符串、数字甚至另一个列表都塞进去。
列表怎么用?
定义列表特简单,用中括号[]把元素括起来,元素之间用逗号隔开就行:
# 存水果的列表
fruits = ['apple', 'banana', 'orange']
print(fruits) # 直接打印整个列表:['apple', 'banana', 'orange']
print(type(fruits)) # 类型是list:<class 'list'>
# 啥类型都能存
mixed_list = [1, '小明', True, 3.14]
print(mixed_list) # [1, '小明', True, 3.14]
常用操作:查、增、删、改、排序
查元素:和字符串一样,用索引(下标)拿元素。从左到右数从0开始,从右到左数从-1开始。
fruits = ['apple', 'banana', 'orange']
print(fruits[0]) # 第一个元素:apple
print(fruits[-1]) # 最后一个元素:orange
# 切片:拿一部分元素
print(fruits[:2]) # 前两个:['apple', 'banana']
print(fruits[::-1]) # 反转列表:['orange', 'banana', 'apple']
# 判断元素在不在列表里(这个超常用!)
ips = ['192.168.1.1', '10.0.0.1']
if '192.168.1.1' in ips:
print('允许访问')
else:
print('拒绝访问') # 输出:允许访问
增元素:往列表里加东西,常用append(加在最后)、insert(插在指定位置)、extend(合并两个列表)。
fruits = ['apple', 'banana']
# 加在最后
fruits.append('orange')
print(fruits) # ['apple', 'banana', 'orange']
# 插在索引1的位置(原来的元素往后挪)
fruits.insert(1, 'pear')
print(fruits) # ['apple', 'pear', 'banana', 'orange']
# 合并两个列表
more_fruits = ['grape', 'mango']
fruits.extend(more_fruits)
# 也可以写成 fruits += more_fruits
print(fruits) # ['apple', 'pear', 'banana', 'orange', 'grape', 'mango']
删元素:用remove(删指定元素)、clear(清空列表)。
fruits = ['apple', 'banana', 'orange']
# 删'banana',记得先判断在不在,不然会报错
if 'banana' in fruits:
fruits.remove('banana')
print(fruits) # ['apple', 'orange']
# 清空列表
fruits.clear()
print(fruits) # []
改元素:直接通过索引赋值就行。
fruits = ['apple', 'banana', 'orange']
fruits[0] = '苹果' # 把第一个元素改成中文
print(fruits) # ['苹果', 'banana', 'orange']
排序:用sort(),默认升序,加reverse=True就是降序。
scores = [85, 92, 78, 90]
scores.sort() # 升序:[78, 85, 90, 92]
print(scores)
scores.sort(reverse=True) # 降序:[92, 90, 85, 78]
print(scores)
列表嵌套:处理分层数据
比如要存三个班级的学生名单,每个班级的名单是一个列表,那就可以用列表嵌套:
classes = [
['张三', '李四'], # 一班
['王五', '赵六'], # 二班
['田七', '钱八'] # 三班
]
# 拿二班的王五:先拿第二个班级(索引1),再拿第一个学生(索引0)
print(classes[1][0]) # 王五
# 遍历所有学生
for class_students in classes:
for student in class_students:
print(student)
实际应用场景
场景1:学生成绩管理
比如要存一个班级的数学成绩,算平均分、最高分:
math_scores = [88, 92, 76, 95, 89]
# 平均分
avg_score = sum(math_scores) / len(math_scores)
print(f'平均分:{avg_score:.1f}') # 平均分:88.0
# 最高分
max_score = max(math_scores)
print(f'最高分:{max_score}') # 最高分:95
场景2:待办事项清单
做一个简单的待办列表,支持添加、完成(删除)任务:
todo_list = []
# 添加任务
todo_list.append('写周报')
todo_list.append('买牛奶')
todo_list.append('交电费')
print('当前待办:', todo_list) # 当前待办: ['写周报', '买牛奶', '交电费']
# 完成任务(删除第一个任务)
finished_task = todo_list.pop(0) # pop(0)删除并返回第一个元素
print(f'已完成:{finished_task}') # 已完成:写周报
print('剩余待办:', todo_list) # 剩余待办: ['买牛奶', '交电费']
元组:不能改的“只读列表”
为啥要用元组?
有时候咱们存的数据不希望被修改,比如一年有12个月、一周有7天,这种固定的数据用列表的话,万一不小心改了就麻烦了。这时候就用元组——它和列表很像,但里面的元素一旦定义就不能改。
元组怎么用?
用圆括号()定义,元素之间用逗号隔开。注意:如果只有一个元素,后面必须加逗号,不然会被当成普通数据。
# 多个元素的元组
months = (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12)
print(months) # (1, 2, ..., 12)
print(type(months)) # <class 'tuple'>
# 单个元素的元组(逗号不能少!)
single_tuple = (1,)
print(type(single_tuple)) # <class 'tuple'>
# 不加逗号就不是元组了
not_tuple = (1)
print(type(not_tuple)) # <class 'int'>
常用操作
元组因为不能改,所以操作比列表少,主要是查:
months = (1, 2, 3, 4, 5)
# 查元素(和列表一样)
print(months[0]) # 1
print(months[1:3]) # (2, 3)
# 长度
print(len(months)) # 5
# 判断元素在不在
if 3 in months:
print('3在元组里') # 输出:3在元组里
实际应用场景
场景1:固定配置项
比如数据库连接的信息,IP、端口、数据库名这些都是固定的,用元组存更安全:
db_config = ('localhost', 3306, 'test_db', 'root', '123456')
host, port, db_name, user, password = db_config # 解包,方便取值
print(f'连接数据库:{host}:{port}/{db_name}') # 连接数据库:localhost:3306/test_db
场景2:函数返回多个值
Python函数返回多个值时,其实返回的就是元组,咱们平时没注意而已:
def get_user_info():
name = '小明'
age = 18
city = '北京'
return name, age, city # 这里返回的其实是元组 ('小明', 18, '北京')
# 接收返回值(解包)
user_name, user_age, user_city = get_user_info()
print(f'{user_name},{user_age}岁,住在{user_city}') # 小明,18岁,住在北京

字典:按“名字”找数据的神器
为啥要用字典?
如果用列表存一个人的信息:person = ['小明', 18, '北京'],那你拿年龄的时候得记着索引是1,拿城市得记着索引是2,太麻烦了。而且如果信息多了,根本记不住哪个索引对应啥。这时候字典就香了——它是“键值对”的形式,每个数据都有个“名字”(键),找的时候直接按名字拿,不用记索引。
字典怎么用?
用大括号{}定义,里面是键: 值的形式,键值对之间用逗号隔开。键必须是唯一的,而且通常用字符串(也可以用数字)。
# 存个人信息
person = {
'name': '小明',
'age': 18,
'city': '北京',
'is_student': True
}
print(person) # {'name': '小明', 'age': 18, 'city': '北京', 'is_student': True}
print(type(person)) # <class 'dict'>
常用操作:增、删、改、查
查数据:两种方式,一种是字典[键](键不存在会报错),另一种是get(键, 默认值)(键不存在返回默认值,推荐用这个)。
person = {'name': '小明', 'age': 18, 'city': '北京'}
# 方式1:字典[键]
print(person['name']) # 小明
# print(person['gender']) # 键不存在,直接报错 KeyError: 'gender'
# 方式2:get(键, 默认值),键不存在返回默认值(默认是None)
print(person.get('age')) # 18
print(person.get('gender', '未知')) # 键不存在,返回'未知' → 未知
增/改数据:直接用字典[键] = 值,键存在就改,不存在就加。
person = {'name': '小明', 'age': 18}
# 改年龄
person['age'] = 19
print(person) # {'name': '小明', 'age': 19}
# 加性别
person['gender'] = '男'
print(person) # {'name': '小明', 'age': 19, 'gender': '男'}
删数据:用del 字典[键],同样建议先判断键是否存在。
person = {'name': '小明', 'age': 19, 'gender': '男'}
key = 'gender'
if key in person:
del person[key]
print(person) # {'name': '小明', 'age': 19}
遍历字典:常用items()同时拿到键和值。
person = {'name': '小明', 'age': 18, 'city': '北京'}
for key, value in person.items():
print(f'{key}: {value}')
# 输出:
# name: 小明
# age: 18
# city: 北京
实际应用场景
场景1:JSON数据处理
现在很多API返回的数据都是JSON格式,其实就是Python的字典。比如调用天气API返回的JSON:
weather_data = {
'city': '上海',
'temperature': 25,
'weather': '晴',
'wind': '东南风3级',
'aqi': 45
}
# 提取需要的信息
print(f"{weather_data['city']}今天{weather_data['weather']},气温{weather_data['temperature']}℃")
# 输出:上海今天晴,气温25℃
场景2:统计单词出现次数
比如统计一段文本里每个单词出现了多少次:
text = "apple banana orange apple banana apple"
words = text.split() # 按空格分割成列表:['apple', 'banana', 'orange', 'apple', 'banana', 'apple']
word_count = {} # 用字典存统计结果
for word in words:
# 如果单词已经在字典里,次数+1;否则初始化为1
word_count[word] = word_count.get(word, 0) + 1
print(word_count) # {'apple': 3, 'banana': 2, 'orange': 1}
文件操作:读写磁盘上的数据
为啥要学文件操作?
程序运行时产生的数据都存在内存里,一旦程序关了,数据就没了。如果想把数据保存下来(比如日志、配置文件、爬取的网页内容),就得写到文件里。反过来,想用之前保存的数据(比如历史订单、用户信息),就得从文件里读出来。
操作系统:对应表 (编码表 a --> 97 --> 01100001 2进制) --> ascii码表
文件操作的基本流程
就三步:打开文件 → 读/写文件 → 关闭文件。重点是打开文件的模式,别搞混了。
打开文件的模式
用open()函数打开文件,常用的模式:
r:只读(默认),文件不存在会报错。w:写入,会清空原文件内容,文件不存在会新建。a:追加,不会清空原内容,在文件末尾加新内容,文件不存在会新建。rb/wb:二进制模式,用来读图片、视频、音频等(别指定encoding)。
路径问题:分绝对路径和相对路径。绝对路径是从盘符开始的完整路径(比如C:/project/data/a.txt),缺点是路径长、换环境容易错;相对路径是相对于当前Python文件的路径,./表示当前目录(可以省略),../表示上一级目录,推荐用相对路径。
基础读写示例
写文件
# 用w模式写文件(会清空原内容)
# 注意:指定encoding='utf-8',不然中文可能乱码
f = open('./data/hello.txt', 'w', encoding='utf-8')
f.write('人生苦短,我学Python!\n')
f.write('Python真香!\n')
f.close() # 写完一定要关文件,不然数据可能没保存进去
# 用a模式追加内容
f = open('./data/hello.txt', 'a', encoding='utf-8')
f.write('追加一行内容~\n')
f.close()
读文件
# 一次性读全部内容
f = open('./data/hello.txt', 'r', encoding='utf-8')
content = f.read()
print(content)
f.close()
# 一行一行读(适合大文件,省内存)
f = open('./data/hello.txt', 'r', encoding='utf-8')
while True:
line = f.readline()
if not line: # 读到空行就退出循环
break
print(line.strip()) # strip()去掉换行符和前后空格
f.close()
推荐用法:with open()
上面那种写法有个问题:如果读写文件时出错了,f.close()可能执行不到,导致文件没关。所以推荐用with open(),它会自动帮我们关文件,不用手动调用close()。
# 写文件
with open('./data/hello.txt', 'w', encoding='utf-8') as f:
f.write('用with open写文件,不用手动关!\n')
# 读文件
with open('./data/hello.txt', 'r', encoding='utf-8') as f:
content = f.read()
print(content)
# 同时打开两个文件(比如复制文件)
with open('./data/hello.txt', 'r', encoding='utf-8') as f1, \
open('./data/hello_copy.txt', 'w', encoding='utf-8') as f2:
f2.write(f1.read()) # 把f1的内容写到f2里
实际应用场景
场景1:日志备份(过滤测试数据)
比如有个账单日志invoice.log,里面有很多记录,我们只需要保留remarks是“正式”的记录,备份到新文件:
# 源文件路径和备份文件路径
src_file = './data/invoice.log'
bak_file = './data/invoice_bak.log'
with open(src_file, 'r', encoding='utf-8') as f1, \
open(bak_file, 'w', encoding='utf-8') as f2:
# 先写表头(第一行)
header = f1.readline()
f2.write(header)
# 逐行处理剩下的内容
while True:
line = f1.readline()
if not line:
break
# 判断是否是“正式”记录(strip()去掉换行符)
if line.strip().endswith('正式'):
f2.write(line)
print('备份完成!')
场景2:Nginx访问日志IP统计
有个Nginx访问日志access.log,每一行的开头是IP地址,现在要统计访问次数最多的前5个IP:
log_file = './data/access.log'
ip_count = {} # 用字典存IP和访问次数的对应关系
with open(log_file, 'r', encoding='utf-8') as f:
while True:
line = f.readline()
if not line:
break
# 按空格分割,第一个元素是IP
ip = line.split()[0]
# IP已存在就+1,不存在就初始化为1
ip_count[ip] = ip_count.get(ip, 0) + 1
# 把字典转成列表,方便排序(列表元素是( IP, 次数 )的元组)
ip_list = list(ip_count.items())
# 按次数降序排序(key=lambda x: x[1]表示按元组的第二个元素排序)
ip_list.sort(key=lambda x: x[1], reverse=True)
# 取前5个
print('访问最多的前5个IP:')
for ip, count in ip_list[:5]:
print(f'IP: {ip}, 访问次数: {count}')
函数:把重复代码打包复用
啥是函数?
你想啊,如果你要算两个数的和,每次都写a + b,写了十几次之后发现要改逻辑(比如加完再乘2),那你就得改十几次,太麻烦了。函数就是把这段重复的代码“打包”,起个名字,以后想用的时候直接喊这个名字就行,改的时候也只需要改函数里的代码。
打个比方:煮泡面有一套固定流程(烧水→放面→煮3分钟→加调料→盛出),你不会每次煮面都重新想一遍流程,而是把这套流程记下来,这就是“函数”。以后想吃面,直接执行这个“煮泡面函数”就行。
函数的基本语法
定义函数用def关键字,格式:
def 函数名(参数1, 参数2, ...):
函数体(要执行的代码)
return 返回值 # 可选,不写默认返回None
调用函数就是函数名(实际参数)。
最简单的函数(无参数、无返回值)
# 定义一个打招呼的函数
def say_hello():
print('你好呀!')
# 调用函数(想打几次招呼就调用几次)
say_hello() # 输出:你好呀!
say_hello() # 输出:你好呀!
带参数的函数(形参和实参)
定义函数时的参数是“形参”(占位用的),调用时传的是“实参”(实际的数据):
# 定义一个加法函数,a和b是形参
def add(a, b):
result = a + b
return result # 返回计算结果
# 调用函数,1和2是实参
sum_result = add(1, 2)
print(sum_result) # 3
# 也可以直接传变量
x = 10
y = 20
print(add(x, y)) # 30
带默认值的参数
如果某个参数大部分情况都用同一个值,可以给形参设默认值,调用时可以不用传:
# 定义一个打印信息的函数,gender默认是'未知'
def print_info(name, age, gender='未知'):
print(f'姓名:{name},年龄:{age},性别:{gender}')
# 传两个参数,gender用默认值
print_info('小明', 18) # 姓名:小明,年龄:18,性别:未知
# 传三个参数,覆盖默认值
print_info('小红', 17, '女') # 姓名:小红,年龄:17,性别:女
可变参数(*args和**kwargs)
如果不确定要传多少个参数,可以用可变参数:
*args:接收任意个位置参数,存成元组。**kwargs:接收任意个关键字参数,存成字典。
# *args示例:计算任意多个数的和
def sum_all(*args):
total = 0
for num in args:
total += num
return total
print(sum_all(1, 2)) # 3
print(sum_all(1, 2, 3, 4, 5)) # 15
# **kwargs示例:打印任意多个键值对
def print_kwargs(**kwargs):
for key, value in kwargs.items():
print(f'{key}: {value}')
print_kwargs(name='小明', age=18, city='北京')
# 输出:
# name: 小明
# age: 18
# city: 北京
实际应用场景
场景1:封装日志写入函数
平时写脚本经常要写日志,每次都写with open(...) as f太麻烦,封装成一个函数:
def write_log(log_content, log_file='./data/app.log'):
"""
写日志的函数
:param log_content: 日志内容
:param log_file: 日志文件路径,默认是./data/app.log
"""
with open(log_file, 'a', encoding='utf-8') as f:
f.write(f'{log_content}\n')
# 调用函数写日志
write_log('程序启动成功')
write_log('用户登录:小明')
write_log('数据加载完成')
场景2:封装文件备份函数
前面写过日志备份的代码,把它封装成函数,以后备份其他文件也能用:
def backup_file(src_path, bak_path, filter_func=None):
"""
文件备份函数
:param src_path: 源文件路径
:param bak_path: 备份文件路径
:param filter_func: 过滤函数,接收一行内容,返回True(保留)或False(丢弃)
"""
with open(src_path, 'r', encoding='utf-8') as f1, \
open(bak_path, 'w', encoding='utf-8') as f2:
# 如果有过滤函数,就逐行过滤;否则直接复制全部内容
if filter_func:
for line in f1:
if filter_func(line):
f2.write(line)
else:
f2.write(f1.read())
# 示例1:备份invoice.log,只保留“正式”记录
def is_official(line):
return line.strip().endswith('正式')
backup_file('./data/invoice.log', './data/invoice_bak.log', filter_func=is_official)
# 示例2:直接复制文件(不过滤)
backup_file('./data/hello.txt', './data/hello_copy.txt')
QA环节
Q1:列表和元组到底该用哪个?
A:看数据要不要改。如果数据是固定的(比如月份、配置项),用元组,更安全,也不怕误改;如果数据需要增删改(比如待办列表、成绩),用列表。另外,元组占用的内存比列表小一点,大量固定数据时用元组更省资源。
Q2:字典的键能用列表吗?
A:不行。字典的键必须是“可哈希”的(简单说就是不可变的),列表是可变的,所以不能作为键。字符串、数字、元组(元组里的元素也得是不可变的)都可以作为键。比如{(1,2): 3}是可以的,但{[1,2]: 3}会报错。
Q3:文件操作为啥推荐用with open()?
A:因为with语句会自动管理文件的关闭,就算读写文件时出错了,也会确保文件被正确关闭。如果用手动open()+close(),万一中间代码报错,close()没执行,文件就会一直被占用,可能导致数据丢失或者其他问题。
Q4:函数一定要有return吗?
A:不一定。如果函数只是执行操作(比如打印内容、写日志),不需要返回结果,就可以不写return,默认返回None。比如前面的say_hello()函数和write_log()函数都不需要return。
总结
今天咱们唠了Python里三个最基础也最常用的知识点:
- 数据容器:列表适合存需要修改的多条数据,元组适合存固定的数据,字典适合按“名字”找数据的场景,选对了容器能少写很多代码。
- 文件操作:记住“打开→读写→关闭”的流程,优先用
with open(),指定encoding='utf-8'避免中文乱码,实际场景里日志处理、数据备份都离不开它。 - 函数:把重复代码封装成函数,提高复用性,改起来也方便,参数和返回值根据需求设计,复杂的逻辑拆成多个小函数更易维护。
这些知识点看着简单,但组合起来能做很多事——比如写个自动化脚本,从CSV文件读数据,用字典统计数据,再把结果写到新的文件里,全程不用手动操作。多练多用在项目里,慢慢就熟了。下次碰到具体问题,翻翻这篇文章,说不定就能找到思路~
更多推荐
所有评论(0)