Nature's Beauty
Explore the wonders of the world
参考资料
logger 包
Python 的 logging 包是一个强大的日志记录系统,是 Python 标准库的一部分。它提供了灵活的日志记录功能,可以帮助开发者跟踪程序运行状态、调试问题以及记录重要事件。
1. 基本概念
日志级别
logging 定义了 5 个标准日志级别(按严重性递增):
级别
数值
使用场景
DEBUG
10
详细信息,用于调试
INFO
20
确认程序按预期运行
WARNING
30
表示意外情况或未来可能出现的问题
ERROR
40
严重问题,程序无法执行某些功能
CRITICAL
50
严重错误,程序可能无法继续运行
主要组件
Logger :记录日志的接口,应用程序通过它记录日志
Handler :决定日志记录的输出位置(控制台、文件等)
Filter :提供更细粒度的控制,决定哪些日志记录会被输出
Formatter :指定日志记录的最终输出格式
2. 基本使用
简单示例
1
2
3
4
5
6
7
8
9
10
11
import logging
# 配置基本日志记录
logging . basicConfig ( level = logging . INFO )
# 记录不同级别的日志
logging . debug ( '这是一条调试信息' ) # 不会显示
logging . info ( '这是一条普通信息' )
logging . warning ( '这是一条警告信息' )
logging . error ( '这是一条错误信息' )
logging . critical ( '这是一条严重错误信息' )
输出格式定制
1
2
3
4
5
6
7
8
9
import logging
logging . basicConfig (
level = logging . INFO ,
format = ' %(asctime)s - %(name)s - %(levelname)s - %(message)s ' ,
datefmt = '%Y-%m- %d %H:%M:%S'
)
logging . info ( '这是一条格式化的日志信息' )
3. 高级用法
使用 Logger 对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import logging
# 创建 logger 对象
logger = logging . getLogger ( 'my_app' )
logger . setLevel ( logging . DEBUG )
# 创建控制台 handler 并设置级别
ch = logging . StreamHandler ()
ch . setLevel ( logging . DEBUG )
# 创建 formatter
formatter = logging . Formatter ( ' %(asctime)s - %(name)s - %(levelname)s - %(message)s ' )
# 将 formatter 添加到 handler
ch . setFormatter ( formatter )
# 将 handler 添加到 logger
logger . addHandler ( ch )
# 记录日志
logger . debug ( '调试信息' )
logger . info ( '普通信息' )
文件日志记录
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import logging
logger = logging . getLogger ( 'file_logger' )
logger . setLevel ( logging . INFO )
# 创建文件 handler
fh = logging . FileHandler ( 'app.log' )
fh . setLevel ( logging . INFO )
formatter = logging . Formatter ( ' %(asctime)s - %(levelname)s - %(message)s ' )
fh . setFormatter ( formatter )
logger . addHandler ( fh )
logger . info ( '这条信息会被记录到文件中' )
日志轮转
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from logging.handlers import RotatingFileHandler
logger = logging . getLogger ( 'rotating_logger' )
logger . setLevel ( logging . INFO )
# 每个文件最大 1MB,保留 3 个备份文件
handler = RotatingFileHandler ( 'app.log' , maxBytes = 1024 * 1024 , backupCount = 3 )
handler . setLevel ( logging . INFO )
formatter = logging . Formatter ( ' %(asctime)s - %(levelname)s - %(message)s ' )
handler . setFormatter ( formatter )
logger . addHandler ( handler )
for i in range ( 10000 ):
logger . info ( f '这是第 { i } 条日志信息' )
定时轮转日志
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from logging.handlers import TimedRotatingFileHandler
logger = logging . getLogger ( 'timed_rotating_logger' )
logger . setLevel ( logging . INFO )
# 每天午夜轮转日志,保留 7 天备份
handler = TimedRotatingFileHandler ( 'app.log' , when = 'midnight' , backupCount = 7 )
handler . setLevel ( logging . INFO )
formatter = logging . Formatter ( ' %(asctime)s - %(levelname)s - %(message)s ' )
handler . setFormatter ( formatter )
logger . addHandler ( handler )
logger . info ( '这条日志会根据时间轮转' )
4. 最佳实践
模块化日志记录 :每个模块使用自己的 logger,命名通常为 __name__
1
logger = logging . getLogger ( __name__ )
合理设置日志级别 :生产环境通常使用 INFO 或 WARNING,开发环境使用 DEBUG
避免在热路径中记录 DEBUG 日志 :先检查是否启用该级别
1
2
if logger . isEnabledFor ( logging . DEBUG ):
logger . debug ( 'Message with %s ' , expensive_arg ())
使用结构化日志 :考虑使用 json 或自定义格式以便于日志分析
异常记录 :使用 logger.exception() 或 logger.error(exc_info=True) 记录异常堆栈
1
2
3
4
try :
1 / 0
except ZeroDivisionError :
logger . exception ( "发生了除以零的错误" )
5. 配置方式
字典配置(Python 3.2+)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import logging.config
config = {
'version' : 1 ,
'formatters' : {
'detailed' : {
'format' : ' %(asctime)s %(name)-15s %(levelname)-8s %(processName)-10s %(message)s '
}
},
'handlers' : {
'console' : {
'class' : 'logging.StreamHandler' ,
'level' : 'INFO' ,
},
'file' : {
'class' : 'logging.FileHandler' ,
'filename' : 'app.log' ,
'mode' : 'w' ,
'formatter' : 'detailed' ,
},
},
'root' : {
'level' : 'DEBUG' ,
'handlers' : [ 'console' , 'file' ]
},
}
logging . config . dictConfig ( config )
文件配置
可以保存在单独的配置文件中(如 logging.conf):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
[loggers]
keys = root
[handlers]
keys = consoleHandler,fileHandler
[formatters]
keys = simpleFormatter
[logger root]
level = DEBUG
handlers = consoleHandler,fileHandler
[handler consoleHandler]
class = StreamHandler
level = INFO
formatter = simpleFormatter
args = (sys.stdout,)
[handler fileHandler]
class = FileHandler
level = DEBUG
formatter = simpleFormatter
args = ('app.log', 'a')
[formatter simpleFormatter]
format = %(asctime)s - %(name)s - %(levelname)s - %(message)s
datefmt = %Y-%m-%d %H:%M:%S
然后加载配置:
1
2
3
import logging.config
logging . config . fileConfig ( 'logging.conf' )
6. 高级特性
日志过滤器
1
2
3
4
5
6
7
8
9
10
class MyFilter ( logging . Filter ):
def filter ( self , record ):
# 只允许包含特定文字的日志通过
return 'important' in record . getMessage ()
logger = logging . getLogger ( 'filtered_logger' )
logger . addFilter ( MyFilter ())
logger . info ( '这是一条普通信息' ) # 不会被记录
logger . info ( '这是一条重要信息 important' ) # 会被记录
上下文信息
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import logging
from logging import LoggerAdapter
logger = logging . getLogger ( __name__ )
logger . addHandler ( logging . StreamHandler ())
logger . setLevel ( logging . INFO )
class ContextFilter ( logging . Filter ):
def filter ( self , record ):
record . user = 'John' # 添加上下文信息
return True
logger . addFilter ( ContextFilter ())
formatter = logging . Formatter ( ' %(asctime)s - %(user)s - %(levelname)s - %(message)s ' )
for handler in logger . handlers :
handler . setFormatter ( formatter )
logger . info ( '系统消息' )
# 或者使用 LoggerAdapter
extra = { 'user' : 'Alice' }
logger = LoggerAdapter ( logger , extra )
logger . info ( '系统消息' )
多进程日志记录
在多进程环境中,使用 QueueHandler 和 QueueListener:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
import logging
import logging.handlers
import multiprocessing
def listener_process ( queue ):
root = logging . getLogger ()
h = logging . FileHandler ( 'multi_process.log' )
f = logging . Formatter ( ' %(asctime)s %(processName)-10s %(name)s %(levelname)-8s %(message)s ' )
h . setFormatter ( f )
root . addHandler ( h )
while True :
try :
record = queue . get ()
if record is None :
break
logger = logging . getLogger ( record . name )
logger . handle ( record )
except Exception :
import sys , traceback
print ( 'Error in listener:' , file = sys . stderr )
traceback . print_exc ( file = sys . stderr )
def worker_process ( queue ):
queue_handler = logging . handlers . QueueHandler ( queue )
root = logging . getLogger ()
root . addHandler ( queue_handler )
root . setLevel ( logging . DEBUG )
logging . info ( '来自工作进程的消息' )
if __name__ == '__main__' :
queue = multiprocessing . Queue ()
listener = multiprocessing . Process ( target = listener_process , args = ( queue ,))
listener . start ()
workers = []
for i in range ( 2 ):
worker = multiprocessing . Process ( target = worker_process , args = ( queue ,))
workers . append ( worker )
worker . start ()
for worker in workers :
worker . join ()
queue . put ( None )
listener . join ()
7. 总结
Python 的 logging 模块提供了强大而灵活的日志记录功能:
支持多级别日志记录
可配置的输出目标和格式
支持模块化日志记录
提供日志轮转功能
支持多进程环境
可通过多种方式配置
合理使用 logging 模块可以大大提高应用程序的可维护性和可调试性,是 Python 开发中不可或缺的工具。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
"""
日志处理
"""
import logging
import os
from logging.handlers import RotatingFileHandler
import colorlog # 导入 colorlog
def setup_logging ( log_file = None , log_level = logging . INFO , max_bytes = 1 * 1024 * 1024 , backup_count = 5 ):
"""
配置日志记录,包括日志轮换和控制台输出,支持彩色日志
:param log_file: 日志文件路径,如果为None则使用默认路径
:param log_level: 日志等级
:param max_bytes: 单个日志文件的最大字节数,默认为1MB
:param backup_count: 保留的日志文件备份数
"""
if log_file is None :
log_file = os . getenv ( 'LOG_FILE_PATH' , 'docs/log/test.log' )
# 确保日志文件夹存在
log_dir = os . path . dirname ( log_file )
if not os . path . exists ( log_dir ):
os . makedirs ( log_dir )
# 创建根记录器
logger = logging . getLogger ()
logger . setLevel ( log_level )
# 清除已有的处理器,避免重复添加
if logger . hasHandlers ():
logger . handlers . clear ()
# 创建文件处理器,支持日志轮换
file_handler = RotatingFileHandler ( log_file , mode = 'w' , maxBytes = max_bytes , backupCount = backup_count )
file_handler . setLevel ( log_level )
# 创建控制台处理器,日志输出到控制台
console_handler = logging . StreamHandler ()
console_handler . setLevel ( log_level )
# 定义日志格式
log_format = ' %(asctime)s - %(levelname)-8s - %(message)-60s - [ %(filename)-15s : %(lineno)-4d ]'
color_format = " %(log_color)s " + log_format
# 设置文件处理器的格式
file_formatter = logging . Formatter ( log_format )
file_handler . setFormatter ( file_formatter )
# 设置控制台处理器的彩色格式
color_formatter = colorlog . ColoredFormatter (
color_format ,
log_colors = {
'DEBUG' : 'cyan' ,
'INFO' : 'green' ,
'WARNING' : 'yellow' ,
'ERROR' : 'red' ,
'CRITICAL' : 'bold_red' ,
}
)
console_handler . setFormatter ( color_formatter )
# 将处理器添加到记录器中
logger . addHandler ( file_handler )
logger . addHandler ( console_handler )
# 添加日志分隔符
logger . info ( "=" * 60 )
logger . info ( "新日志开始" . center ( 60 , ' ' ))
logger . info ( "=" * 60 )
def log_data ( data ):
"""
记录数据的日志
"""
logging . info ( f "数据记录: { data } " )
# 通用异常处理装饰器
def log_exceptions ( func ):
def wrapper ( * args , ** kwargs ):
try :
return func ( * args , ** kwargs )
except Exception as e :
logging . error ( f " { func . __name__ } 执行时发生错误" , exc_info = True )
raise e
return wrapper
# 示例:调用日志设置
if __name__ == "__main__" :
setup_logging ( log_file = '/Users/zhangxianping/Desktop/pythonProject/SiCore_ChipTest/docs/log//test.log' , log_level = logging . DEBUG )
log_data ( "这是一个测试数据记录" )
logging . debug ( "这是一条调试信息" )
logging . warning ( "这是一条警告信息" )
logging . error ( "这是一条错误信息" )
logging . critical ( "这是一条严重错误信息" )
获取系统监控信息
⭕ 关于 CPU
1
2
3
4
5
6
7
8
# 核心数
psutil . cpu_count ( logical = False )
# 参数 logical=False 只会返回物理内核
# 使用率
psutil . cpu_percent ( interval = 0.5 )
# 若加上参数 percpu=True 则会返回一个列表,包含各个核心的使用率
⭕ 关于内存
1
2
3
4
# 内存信息,包括:总容量、可用空间及占比等
psutil . virtual_memory ()
# 可以通过点号访问具体的属性,例如 psutil.virtual_memory().total
⭕ 关于磁盘
1
2
3
4
5
6
# 磁盘分区信息,包括:总容量、可用空间及占比等
psutil . disk_partitions ()
# 指定目录的磁盘信息
psuntil . disk_usage ( '/' )
⭕ 关于网络
1
2
# 可以获取到收、发数据包的数量
。。。。。。
⭕ 关于用户
1
2
# 活动用户信息
psutil . users ()
⭕ 保存到日志
示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import psutil
import datetime
import unicodedata
def align_string ( text , width , alignmethod ):
# 处理中英文混合字符串的对齐问题
text_width = sum ( 2 if unicodedata . east_asian_width ( c ) in ( 'F' , 'W' ) else 1 for c in text )
if alignmethod == 'ljust' :
tmp = text . ljust ( width - text_width )
elif alignmethod == 'rjust' :
tmp = text . rjust ( width - text_width )
elif alignmethod == 'center' :
tmp = text . center ( width - text_width )
return tmp
cpu_per = psutil . cpu_percent ( interval = 0.5 )
memory_info = psutil . virtual_memory ()
disk_info = psutil . disk_usage ( "/" )
net_info = psutil . net_io_counters ()
current_time = datetime . datetime . now () . strftime ( " %F %T" )
log_template = f """
| { align_string ( '监控时间' , 19 , 'center' ) } | { align_string ( 'CPU使用率' , 19 , 'center' ) } | { align_string ( '内存使用率' , 19 , 'center' ) } | { align_string ( '硬盘使用率' , 19 , 'center' ) } | { align_string ( '网络收发量' , 28 , 'center' ) } |
| { align_string ( '' , 19 , 'ljust' ) } | { align_string ( f '(共 { psutil . cpu_count ( logical = False ) : .2f } 核CPU)' , 19 , 'center' ) } | { align_string ( f '(总计 { memory_info . total / 1024 / 1024 / 1024 : .2f } G内存)' , 19 , 'center' ) } | { align_string ( f '(总计 { disk_info . total / 1024 / 1024 / 1024 : .2f } G硬盘)' , 19 , 'center' ) } | { align_string ( '' , 28 , 'ljust' ) } |
| { '-' * 19 } | { '-' * 19 } | { '-' * 19 } | { '-' * 19 } | { '-' * 28 } |
| { align_string ( current_time , 19 , 'ljust' ) } | { align_string ( f ' { cpu_per } %' , 19 , 'ljust' ) } | { align_string ( f ' { memory_info . percent } %' , 19 , 'ljust' ) } | { align_string ( f ' { disk_info . percent } %' , 19 , 'ljust' ) } | { align_string ( f '收: { net_info . bytes_recv } /发: { net_info . bytes_sent } ' , 28 , 'ljust' ) } |
| { '-' * 19 } | { '-' * 19 } | { '-' * 19 } | { '-' * 19 } | { '-' * 28 } |
"""
print ( log_template )
with open ( 'log.txt' , 'a' ) as f :
f . write ( log_template + ' \n\n ' )
若需要持续写入日志文件,可以利用 while 循环
每隔一段时间去重新计算需要的指标即可
发送邮件
1
2
# 导入模块
import yagmail
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 先创建对象
ya_obj = yagmail . SMTP (
user = "发件人邮箱" ,
password = "授权码(非密码,在邮箱设置)" ,
host = "网易邮箱服务器smtp.163.com"
)
# 配置邮件内容
content = '。。。'
# 发送邮件
ya_obj . send ( '收件人邮箱' , '邮件主题' , content )
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
from bs4 import BeautifulSoup
import smtplib
from email.mime.text import MIMEText
msg_from = '870407139@qq.com' #发送方邮箱
passwd = '...' #填入发送方邮箱的授权码
receivers = [ '870407139@qq.com,503734057@qq.com' ] #收件人邮箱
subject = "北大研招网更新连接" #主题
content = "..." #正文
msg = MIMEText ( content )
msg [ 'Subject' ] = subject
msg [ 'From' ] = msg_from
msg [ 'To' ] = ',' . join ( receivers )
try :
s = smtplib . SMTP_SSL ( "smtp.qq.com" , 465 ) #邮件服务器及端口号
s . login ( msg_from , passwd )
s . sendmail ( msg_from , msg [ 'To' ] . split ( ',' ), msg . as_string ())
print ( "发送成功" )
except :
print ( "发送失败" )
finally :
s . quit ()
⭕ 实际应用
可以结合上面的监控信息
判断当 CPU 使用率超过 80%,内存使用率超过 90% 的时候,发送邮件给指定收件人
1
2
if cpu_per > 80 or memory_info . percent > 90 :
# 发送邮件。。。
re 模块
⭕ match、search 方法
1
2
3
# 从字符串的【开头位置】匹配
# 可以用于【验证】输入的【内容是否满足某种格式】
re . match ( "正则表达式" , "要验证/检测的字符串" )
1
2
# 从需要检测的字符串中【搜索】满足正则的内容
re . search ( "正则表达式" , "要搜索的字符串" )
以上两个方法,如果匹配成功,返回 match object 对象
如果匹配失败,返回 None
可以通过 .group() 获取结果
⭕ sub 方法
对 search 到的字符(串)进行 替换
1
2
3
4
5
6
7
8
9
10
11
import re
def add ( temp ):
strNum = temp . group ()
num = int ( strNum ) + 1
return str ( num )
# 通过传入 add 函数,对 search 到的内容进行更加【多样化的替换】
ret = re . sub ( r "\d+" , add , "hello python = 997" )
print ( ret )
⭕ split 方法
根据正则表达式对字符串进行 拆分
1
2
3
4
# 根据冒号或者空格拆分
result = re . split ( ":| " , "info:hello@163.com zhangsan lisi" )
print ( result )
# 相比于 str.split() 更加灵活
⭕ 贪婪与非贪婪
正则表达式默认为 贪婪模式
满足表达式的情况下,尽可能多获取内容
要转换成 非贪婪模式 ,在 +*{} 等符号的后面添加 ? 符号即可
1
2
3
result = re . match ( "aaa(\d+?)" , "aaa123456" )
# 匹配到 1 就停止了
print ( result . group ())
⭕ 其他
字符串前 r 的作用:让正则中的 \ 不再是转义 的特殊含义
1
2
3
4
5
# result = re.match("<([a-zA-Z0-9]+)><([a-zA-Z0-9]+)>.*</\\2></\\1>", "<html><h1>asdbj</h1></html>")
# 等价写法
result = re . match ( r "<([a-zA-Z0-9]+)><([a-zA-Z0-9]+)>.*</\2></\1>" , "<html><h1>asdbj</h1></html>" )
print ( result . group ())
SQL 语句
⭕ pymysql 的使用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import pymysql
# 建立连接对象
conn = pymysql . connect (
host = 'localhost' ,
port = 3306 ,
user = 'root' , password = '123456'
)
# 创建游标对象
cur = conn . cursor ()
# 使用游标对象执行 SQL 语句
sql = """
select * from students
"""
results = cur . execute ( sql )
# 提交
conn . commit ()
# 仅从查询的数据中取出一条数据
oneres = cur . fetchone ()
# fetchall 返回嵌套元组,每个元组是一条数据
res = cur . fetchall ()
# 关闭游标对象
cur . close ()
# 关闭连接对象
conn . close ()
⭕ pymongo 的使用
1
2
3
4
5
6
7
8
9
10
11
12
13
import os
from pymongo import MongoClient
# 配置 mongodb 数据库
host = os . environ . get ( 'MONGODB_HOST' , '127.0.0.1' ) # 本地数据库
port = os . environ . get ( 'MONGODB_PORT' , '27017' ) # 数据库端口
mongo_url = 'mongodb:// {} : {} ' . format ( host , port )
mongo_db = os . environ . get ( 'MONGODB_DATABASE' , '...' )
client = MongoClient ( mongo_url )
db = client [ mongo_db ]
#db['...'].create_index('id', unique=True)
⭕ SQL 注入
SQL 注入是一种常见的安全漏洞
主要发生在使用动态构建 SQL 查询语句时,未对用户输入进行 充分验证和过滤
导致恶意用户可以通过输入 特定的 SQL 语句 来执行非法操作
1
2
3
4
5
6
7
8
9
10
11
12
13
sql = f """
select * from goods where name = { input_name } order by id desc
"""
# 当 input_name = ' or 1 or ' 时,执行
# select * from goods where name = '' or 1 or '' order by id desc
# 会导致注入问题
# 防止注入的写法
sql = """
select * from goods where name = %s order by id desc
"""
result = cur . execute ( sql , [ input_name , ])
一些对策:
1、不要直接将用户输入的数据拼接为 SQL 查询语句
对语句进行 适当的转义或参数化处理
2、将 SQL 查询语句和参数分开处理
确保参数值不会被当作 SQL 语句的一部分执行
3、使用 ORM(Object-Relational Mapping)框架
帮助开发者避免直接操作 SQL 语句,通过对象来操作数据库
4、在数据库连接时,使用具有 最小权限的用户
避免数据库用户具有过高的权限,以减少潜在的风险
下一步学习方向:
Web 开发(Flask/Django)
数据分析(Pandas)
自动化办公(openpyxl)
推荐学习资源
DrissionPage
运行模式
特性
匿名模式
非匿名模式
数据存储
关闭浏览器后自动删除历史、cookie、缓存等
正常保存历史、cookie、缓存等
会话隔离
独立且全新的会话
复用本地已有的用户数据和会话
适用场景
干净的独立环境,多账号隔离
持久化登录状态
设置匿名模式
1
2
co = ChromiumOptions ()
co . incognito ()
特性
无头模式
非无头模式
显示
无图形界面,后台静默运行
显示浏览器窗口,可见页面操作过程
消耗
资源占用低
渲染界面,占用较高
设置无头模式
1
2
3
co = ChromiumOptions ()
co . headless ()
co . set_argument ( '--disable-blink-features=AutomationControlled' )
特性
沙盒模式
非沙盒模式
定义
浏览器运行在受限的环境中,无系统资源访问权限
浏览器直接以当前用户权限运行,无隔离机制
安全性
高
低
消耗
较高,需维护沙盒环境
较低
设置无沙盒模式
1
2
3
co = ChromiumOptions ()
co . set_argument ( '--no-sandbox' )
co . set_argument ( '--disable-dev-shm-usage' )
Selenium
参考资料
简介
Selenium 支持的语言包括 C#,Java,Perl,PHP,Python 和 Ruby
它是自动化测试套件,也可以用于爬虫
Selenium 测试脚本可以使用任何支持的编程语言进行编码,并且可以直接在大多数现代 Web 浏览器中运行
安装
pip 下载 selenium
并根据自己的浏览器及版本,安装相应的驱动
使用
1
2
3
4
5
option = webdriver . ChromeOptions ()
# 防止自动关闭浏览器
option . add_experimental_option ( 'detach' , True )
driver = webdriver . Chrome ( chrome_options = option )
定位元素
进入浏览器的开发者工具
1、根据 id 定位
<input id='search-input' type='text' value='' placeholder='请输入'>
1
driver . find_element_by_id ( 'search-input' )
由于 id 的唯一性,我们可以直接通过 id 定位
2、根据 name 定位
<meta name='keywords' content='github,coding'>
1
driver . find_element_by_name ( 'keywords' )
3、根据 class 定位
<div class='toolbar-container'>
1
driver . find_element_by_class_name ( 'toolbar-container' )
4、根据 tag 定位
5、根据 xpath 定位
在 XML 文档中定位元素的语言
1
2
3
4
5
6
7
# 绝对路径,根据层级关系定位
driver . find_element_by_xpath ( '/html/body/div/div/input[1]' )
# 根据元素属性定位
driver . find_element_by_xpath ( "//*[@id='search-input']" )
# 也可以结合上述二者
6、根据 css 定位
定位较为灵活,并且速度较快
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 选择 class 为指定值的元素
driver . find_element_by_css_selector ( '.toolbar-container' )
# 选择 id 为指定值的元素
driver . find_element_by_css_selector ( '#search-input' )
# 选择所有 input 元素
driver . find_element_by_css_selector ( 'input' )
# 选择父元素为 div 的所有 input 元素
driver . find_element_by_css_selector ( 'div>input' )
# div 标签与 input 标签在同级,且 input 在 div 之后
driver . find_element_by_css_selector ( 'div+input' )
# 选择 type 属性值为 text 所有的元素
driver . find_element_by_css_selector ( "[type='text']" )
7、定位文本链接
<div class='text-box'>hello world</div>
1
2
3
4
driver . find_element_by_link_text ( 'hello world' )
# partial 只需要部分文本即可定位
driver . find_element_by_partial_link_text ( 'hello' )
定位一组元素
将上述的 element 改为 elements 即可
然后通过遍历返回的元素列表,进行相应的处理
控制
浏览器控制
可以修改窗口大小
全屏显示
刷新
前进、后退
窗口切换
切换到出现的弹窗(点击某个按钮后出现)
上传下载文件
还可以通过执行 js 语句
1
2
3
4
5
6
# 打开新的标签页
js = "window.open('https://baidu.com')"
driver . execute_script ( js )
# 滑动滚动条
js = "window.scrollTo(0, 500)"
可以模拟输入指定内容
清除文本内容
判断元素是否可见
获取标签属性值
获取元素尺寸、文本
鼠标控制
可以实现
鼠标左、右键单击
双击
拖动
悬停
键盘控制
常用的按键都用相应的关键词映射
元素等待
很多页面都使用 ajax 技术,页面的元素 不是同时被加载出来的
为了防止定位这些尚在加载的元素导致报错,可以设置元素等待来增加脚本的稳定性
显式等待
设置一个超时时间,每隔一段时间就检测一下元素是否存在
注意,没找到之前或者没超时之前,都不会执行后续的内容
类似于阻塞了
1
2
3
4
5
6
7
8
9
10
11
12
from selenium import webdriver
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.by import By
driver = webdriver . Chrome ()
element = WebDriverWait ( driver , 5 , 0.5 ) \
. until (
EC . presence_of_element_located (( By . ID , 'kw' )),
message = '超时了'
)
隐式等待
与显式等待类似,但是超时抛出的异常不同
1
2
3
# 设置隐式等待 5 秒
driver . implicitly_wait ( 5 )
# 一般配合 try except 捕获超时异常
cookies 操作
常使用 selenium + requests 实现 cookie 持久化
即先用 selenium 模拟登陆获取 cookie ,再通过 requests 携带 cookie 进行请求
可以添加、获取、删除 cookie 等操作
其他
有些网站的 li 标签是懒加载,只有当用户滑动至最后标签时,才会加载后边的数据
一种思路是通过循环来实现重复滚动
每一次循环都进行元素的查找和 js 滑动滚动条脚本的执行
关闭全部页面并退出 quit
关闭当前页面 close
截图 get_screenshot_as_png
获取页面的 html 源码 page_source
实战
一般验证码需要通过第三方平台实现破解
哔哩哔哩
先打开登录界面
定位用户名和密码对应的标签
输入相关数据
点击登录
之后会弹出文字验证码
获取验证码图片的方式有两种
1、截图并裁剪
2、通过 xpath 等方式获取图片的 url 地址
图片保存在本地后,将图片传给第三方平台破解
可能还需要将图片缩小,然后将返回的坐标再按比例放大
最后按序点击即可
PySide
参考资料
简介
Qt 库是 C++ 语言开发的,里面有非常强大的图形界面开发库
PySide2、PyQt5 可以让我们通过 Python 语言使用 Qt
PyQT5 和 PySide2 大部分的接口都是类似的
二者之间的互相兼容,通常只要把导入的名字从 PyQt5 换成 PySide2 就行
PyQT 是由 Riverbank Computing Ltd 开发维护的
采用了 GPL 加商业许可 两种许可证模式
由 GPL 的传染性,这就意味着如果你的代码使用了 PyQT,那么就必须开源代码
而如果不想开源你的代码,你就得 购买商业许可证
这导致 PyQT 商业不友好
于是 Nokia(QT 的开发者)打算和 Riverbank Computing 谈判,劝说 PyQT 采用 LGPL(相对来说是商业友好的)
但是 Riverbank 不同意该建议(因为损失了利益)
Nokia 便决定开发自己的版本。这就是后来的 PySide
安装
pip install pyside2 -i https://mirrors.aliyun.com/pypi/simple
示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
import sys
from PySide2.QtWidgets import QApplication , QMainWindow , QLabel , QPushButton
def click ():
print ( "Button is clicked!" )
# 每个 GUI 都必须包含一个 Qapplication
# argv 表示获取命令行参数,如果不用获取,则可以使用 [] 代替
app = QApplication ( sys . argv )
# QMainWindow 类似一个容器(窗口)
# 用来包含按钮、文本、输入框等 widgets
win = QMainWindow ()
# 定义窗口的尺寸,四个参数分别对应坐标 x、y 和宽高
win . setGeometry ( 400 , 400 , 400 , 300 )
win . setWindowTitle ( "PySide2 Tutorial" )
# 设置文本内容
label = QLabel ( win )
label . resize ( 200 , 100 )
label . setText ( "Hi this is PySide2" )
label . move ( 100 , 100 )
# 按钮和事件
button = QPushButton ( win )
button . resize ( 200 , 100 )
button . setText ( "Hi! Click Me" )
button . move ( 100 , 100 )
# 将按钮和函数 click 进行绑定
button . clicked . connect ( click )
# 显示窗口
win . show ()
# 设置窗口一直运行,直到使用关闭按钮进行关闭
sys . exit ( app . exec_ ())
常用控件
函数
用途
QLabel
用于显示文本或图像,没有提供用户交互功能
QComboBox
组合框(也就是下拉框),可以弹出可选项目列表。组合框可以是可编辑的
QCheckBox
复选框
QRadioButton
单选按钮,isChecked() 方法返回值 True 表示选中,False 表示未选中
QLineEdit
单行文本框控件,可以输入单行字符串,无法换行输入。多行可用 QTextEdit
QPushButton
切换按钮,通过点击可以在 按下和未按下 两种状态之间切换
QTableWidget
显示数据表格
QSlider
滚动条,具有简单句柄的小部件,可以来回拉动
QProgressBar
进度条,在处理冗长任务时直观地让用户知道任务正在进行中
QCalendarWidget
日历小部件
实战
制作简易的天气查询软件
使用 Qt Designer 设计一个界面
Qt Designer 的路径 D:\python\Lib\site-packages\qt5_applications\Qt\bin\designer.exe
用到的控件有:按钮、组合框、文本标签、下拉选择框、文本编辑框
同时定义了两个按钮 queryBtn 及 clearBtn,分别用来查询及清空天气数据
按钮绑定槽函数:
1、在 Qt Designer 右下角选择 信号/槽编辑器 ,点击 + 号新增
2、分别选择 queryBtn 及 clearBtn,选择 信号 clicked(),接收者 Dialog 及槽 accept()
【这里的槽 accept() 需要在 ui 转换成的 py 文件中进行修改,改为对应的点击函数】
最后保存为 Weather.ui 文件
PySide 支持直接使用 .ui 文件
1
2
3
4
5
6
7
8
import sys
from PyQt5 import QtWidgets , uic
app = QtWidgets . QApplication ( sys . argv )
window = uic . loadUi ( "mainwindow.ui" )
window . show ()
app . exec ()
也可以将 ui 文件转换为 py 文件的命令
pyuic5 -o weather.py .\weather.ui
对话框类 Dialog
在 MainDialog 中调用界面类 Ui_Dialog
然后在其中中添加查询天气的业务逻辑代码,这样就做到了 界面显示和业务逻辑的分离
新增 demo.py 文件,在 MainDialog 类中定义了两个 槽函数 queryWeather() 和 clearText()
与 ui 文件中定义的两个按钮(queryBtn 和 clearBtn)的触发 clicked 信号 进行绑定
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
import sys
# weather.ui 文件转换为的 py 文件,其中包含 Ui_Dialog 类
import Weather
from PySide2.QtWidgets import QApplication , QDialog
import requests
class MainDialog ( QDialog ):
def __init__ ( self , parent = None ):
super ( QDialog , self ) . __init__ ( parent )
# 设置 UI
self . ui = Weather . Ui_Dialog ()
self . ui . setupUi ( self )
# 实现业务逻辑
def queryWeather ( self ):
cityName = self . ui . comboBox . currentText ()
cityCode = self . getCode ( cityName )
r = requests . get (
"https://restapi.amap.com/v3/weather/weatherInfo?key=f4fd5b287b6d7d51a3c60fee24e42002&city= {} " . format ( cityCode ))
if r . status_code == 200 :
data = r . json ()[ 'lives' ][ 0 ]
weatherMsg = '城市: {} \n 天气: {} \n 温度: {} \n 风向: {} \n 风力: {} \n 湿度: {} \n 发布时间: {} \n ' . format (
data [ 'city' ],
data [ 'weather' ],
data [ 'temperature' ],
data [ 'winddirection' ],
data [ 'windpower' ],
data [ 'humidity' ],
data [ 'reporttime' ],
)
else :
weatherMsg = '天气查询失败,请稍后再试!'
self . ui . textEdit . setText ( weatherMsg )
def getCode ( self , cityName ):
cityDict = { "北京" : "110000" ,
"苏州" : "320500" ,
"上海" : "310000" }
return cityDict . get ( cityName , '101010100' )
def clearText ( self ):
self . ui . textEdit . clear ()
if __name__ == '__main__' :
myapp = QApplication ( sys . argv )
myDlg = MainDialog ()
myDlg . show ()
sys . exit ( myapp . exec_ ())
将代码打包成 exe 文件
常用的工具有:PyInstaller,py2exe,cx_Freeze,bbfreze,py2app 等
fbs
要求 Python 版本小于等于 3.6
先创建一个虚拟环境,再安装 pip install fbs PySide2==5.12.0
创建新的 fbs 项目 fbs startproject
启动界面进行测试 fbs run
打包为 APP fbs freeze
创建安装程序 fbs installer
注意的是在 Windows 上面我们需要安装 NSIS
需要把 NSIS 的根目录添加到 system PATH 中
物料表格匹配
首先通过 Qt Designer 设计好界面
将 ui 文件转换成 py 文件
然后编写主要文件:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 通过全局变量设置样式
# 三对双引号内书写 css
startbutton_qss = """
QPushButton{
background:orange;
color:white;
font-size:18px;
border-radius: 24px;
font-family: 微软雅黑;
}
QPushButton:pressed{
background:black;
}
"""
主要分为两个窗口,一个用于文件转换,另一个用于物料匹配
这两个类都继承于 QtWidgets.QDialog
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
- `class FileConversionWindow()`
- __init__
初始化一些控件,绑定好点击事件和信号,设置好样式
- callback
用于进度条参数的回传
- callback_done
进度条结束的信号
- messageDialog1
通知对话框 `QtWidgets.QMessageBox` 实现通知信息
- select_oldBomfile
文件选择对话框 `QtWidgets.QFileDialog.getOpenFileName`
- select_totalBomfile
- run_convert
主要的文件处理逻辑,最后将前面定义的信号 emit 出来
- `class BomCompareWindow()`
- __init__
- select_file
- select_file_new
- show_result
- show_log
- run_compare
- `class Ui_MainWindow(object)`
通过 QtCore.pysideSignal 设置信号
1
2
3
4
5
6
7
8
9
10
11
12
# 创建 app
app = QtWidgets . QApplication ( sys . argv )
# 设置主窗口
MainWindow = QtWidgets . QMainWindow ()
# 初始化 UI
ui = Ui_MainWindow ()
# 将 UI 设置到主窗口上
ui . setupUi ( MainWindow )
# 展示主窗口
MainWindow . show ()
# 当 app 执行结束动作时,系统关闭 app
sys . exit ( app . exec_ ())
turtle
海龟绘图
实现在平铺的纸上画线
1
2
3
4
5
6
7
from turtle import *
# 注意这么导入的对象集很大,可能发生名称冲突
# 推荐如下导入方式
import turtle
# width() 就要变成 turtle.width()
⭕ 移动和绘制
标准模式下,默认是面向东,此时是 0°
逆时针旋转 90° 即北
(注:logo 模式下,默认是 0° 面向北)
作用
写法
抬笔
turtle.penup() 或 turtle.up() 或 turtle.pu()
下笔
turtle.pendown() 或 turtle.down() 或 turtle.pd()
当前画笔是否落下
turtle.isdown() (落下则返回 true,否则返回 false)
前进 n 步
turtle.forward(n) 或 turtle.fd(n) (同理有 turtle.backward(n) 或 .bk(n))
向左旋转 120°
turtle.left(m) 或 turtle.lt(m) (同理有 turtle.right(m) 或 .rt(m))
获取海龟的 xy 坐标
turtle.position() 或 turtle.pos() (仅获取 x 坐标 turtle.xcor() 仅获取 y 坐标 turtle.ycor())
设置海龟的坐标,不改变方向,画笔落下就会画线
turtle.goto(x, y) 或 turtle.setpos(x, y) (单独设置横纵坐标 turtle.setx(x)、turtle.sety(y)turtle.teleport(x, y, fill_gap=False) 有类似作用,但是不会画线)
设置海龟的朝向
turtle.setheading(90) 朝向北
获取海龟的朝向
turtle.heading()
绘制半径为 120 个单位的半圆(圆心在海龟的左侧)
turtle.circle(120, 180)
绘制直径为 20,颜色为蓝色的圆点
turtle.dot(20, 'blue') (若未指定直径,则选取 max(pensize+4, 2*pensize))
在当前位置印一个海龟形状
stamp_id = turtle.stamp() 清除印记 turtle.clearstamp(stamp_id)
撤销动作
turtle.undo() 可以配合循环进行多次的撤销
获取夹角
turtle.towards(x, y) 当 x 为海龟对象时,y=none
获取距离
turtle.distance(x, y)
设置角度的度量单位为弧度
turtle.radians()
设置画笔线条颜色和填充颜色
turtle.color('blue', 'green') 支持 rgb 元组
单独控制线条颜色
turtle.pencolor('blue')
单独控制填充色
turtle.fillcolor('green')
控制填充的打开和关闭(二者需要同时出现)
turtle.begin_fill()turtle.end_fill()
返回填充状态,布尔值
turtle.filling()
控制线宽或者线条粗细
turtle.width(3) 或 turtle.pensize(3) (若不指定参数,则返回当前的 size)
设置海龟移动的速度
turtle.speed('normal') 1~10 为最慢到快,0 为最快
获取当前画笔的配置,返回值是字典
turtle.pen() 也可以向其传入参数和值进行设置,一般是一个字典
当海龟消失在屏幕中时,需要将其送回起点
turtle.home()
清空窗口
turtle.clearscreen()
判断海龟何时回到初始点
abs(pos()) < 1
防止脚本结束时,海龟窗口被自动关闭
turtle.mainloop() 窗口就会等待被主动终止
清除绘制的内容,海龟回到原点所有设置变为默认值
turtle.reset()
删除指定海龟的绘图,但是海龟不动
turtle.clear()
使海龟不可见
turtle.hideturtle() 或 turtle.ht()
使海龟可见
turtle.showturtle() 或 turtle.st()
返回海龟是否可见,布尔类型
turtle.isvisible()
书写内容
turtle.write("Home = ", move=True, align="center") 若 move 为真值,画笔会移至文本的右下角
创建并返回海龟的克隆体(具有相同的位置、朝向和海龟属性)
turtle.clone()
获取海龟对象自身
turtle.getturtle() 或 turtle.getpen()
获取海龟绘图场所的 TurtleScreen 类对象
turtle.getscreen()
设置海归外观
turtle.shape(name='turtle') 此外还有 “arrow”, “circle”, “square”, “triangle”, “classic” 等外观
为鼠标点击海龟事件绑定函数
turtle.onclick(myfunc, btn=1, add=None)btn=1 表示绑定鼠标左键,当 myfunc 为 none 时,意味着 移除现有绑定
为鼠标【释放】点击海龟事件绑定函数
turtle.onrelease(myfunc, btn=1, add=None)
⭕ 面向对象
1
2
3
4
5
from turtle import Turtle
t1 = Turtle ()
t2 = Turtle ()
# 通过这种方式,允许屏幕同时存在多只海龟
1
2
3
# 自定义屏幕
t1 . screen . title ( 'turtle demo' )
t1 . screen . bgcolor ( 'orange' )
⭕ 搭配循环绘制各种几何图案
1
2
3
4
5
for steps in range ( 100 ):
for c in ( 'blue' , 'red' , 'green' ):
color ( c )
forward ( steps )
right ( 30 )
NumPy
⭕ 常用语句
🔘 numpy 数组的大小形状
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
a = np . arange ( 12 ) . reshape ( 3 , 4 )
a . ndim
# 返回维度数量
a . shape
# 返回二维数组,表示各个维度的大小
a . dtype . name
# 数据类型
a . size
# 数组的长宽相乘
type ( a )
# 获取数据类型,numpy.ndarray
🔘 修改形状、类型
1
2
3
4
5
6
7
8
9
10
a . resize (( 2 , 6 )) # 不支持 -1
a . reshape (( 2 , - 1 ))
# 注意这里的 -1
# 不用去管几列,只要专注于自己想要将原矩阵修改为几行即可
# 将数组展平
a . flat # 返回值是迭代器,可以通过list关键字转换成列表
list ( a . flat )
a . ravel () # 直接返回 numpy 数组对象
1
2
3
np . array ([[ 1 , 2 ], [ 3 , 4 ]], dtype = complex )
# 这里在创建的时候指定了数据形式为【复数】
# 还可以指定 dtype=np.float32 等等
🔘 创建区间数据
1
2
3
4
5
6
7
8
9
np . arange ( 10 , 30 , 5 )
# 从10开始取值,步长为5,直到30但不包括30
np . arange ( 0 , 2 , 0.3 )
# [0,2),步长为 0.3
# 在区间 [0,2] 上生成【平均间隔】的 5 个数
np . linspace ( 0 , 2 , 5 )
# array([0. , 0.5, 1. , 1.5, 2. ])
🔘 矩阵乘法
1
2
3
4
5
6
7
8
A * B
# elementwise product,相应位置的数字相乘
A . T @ B
# matrix product,矩阵相乘
A . T . dot ( B ) # 同上
np . dot ( A . T , B ) # 同上
🔘 其他
索引:
1
2
3
4
5
6
7
8
9
c = np . array ([[[ 0 , 1 , 2 ],
[ 10 , 12 , 13 ]],
[[ 100 , 101 , 102 ],
[ 110 , 112 , 113 ]]])
c [ 1 , ... ] # 等同于 c[1, :, :] or c[1]
c [ ... , 2 ] # 等同于 c[:, :, 2]
数组的随机乱序排列:
1
2
3
nums = [ 1 , 2 , 3 , 4 , 5 , 6 , 7 ]
a = np . random . RandomState ( 0 )
a . permutation ( nums )
Pandas
Pandas 建立在 NumPy 基础上
NumPy 最适合处理 同构 数值数组数据
而 Pandas 是为处理 表格 或 异构数据 而设计的
Pandas 的三个基本数据结构
数据结构
说明
Series
带索引的一维数组,也是特殊的字典
DataFrame
带行列索引的二维数组,也是特殊的字典
Index
不可变数组或有序集合
⭕ Series 对象
创建一个 Series 对象:
1
2
3
4
5
6
7
8
data = pd . Series ([ 0.2 , 0.5 , 0.7 , 1 ])
print ( data )
print ( '----------------' )
print ( data . index ) #查看 Serise 对象的索引
print ( '----------------' )
print ( data . values ) #查看 Serise 对象的值
NumPy 数组
Series 对象
通过【隐式定义的整数索引】获取数组
用【显式定义的索引】与数值关联 索引不限于整数,可以是任意类型 例如字符串,也可以不连续且无顺序
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
arr = [ 0.2 , 0.5 , 0.7 , 1 ]
ind1 = [ 'a' , 'c' , 'b' , 'd' ]
ind2 = [ 2 , 5 , 3 , 7 ]
d1 = pd . Series ( arr )
# 默认索引是从 0 开始依次标号
print ( 'd1的索引是:' , d1 . index )
# 将【字符(串)】作为索引
d2 = pd . Series ( arr , index = ind1 )
print ( 'd2的索引是:' , d2 . index )
# 将【无序整数】作为索引
d3 = pd . Series ( arr , index = ind2 )
print ( 'd3的索引是:' , d3 . index )
# 不同的索引表示同一值
print ( d1 [ 0 ], d2 [ 'a' ], d3 [ 2 ])
🔘 通过传入字典来创建 Series 对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
pop_dict = {
'California' : 38332521 ,
'Texas' : 26448193 ,
'New York' : 19651127 ,
'Florida' : 19552860 ,
'Illinois' : 12882135
}
pop = pd . Series ( pop_dict )
print ( pop )
print ( '----------------' )
# 取出 Series 对象 California 对应的值
print ( 'California人口数为:' , pop [ 'California' ])
⭕ DataFrame 对象
DataFrame 对象创建方式
通过单个 Series 对象创建
通过字典列表创建
通过 Series 对象字典创建
通过 NumPy 二维数组创建
通过 NumPy 结构化数组创建
……
🔘 通过单个 Series 对象创建
1
2
3
4
5
6
7
8
9
import pandas as pd
# 创建一个单个 Series 对象
data = pd . Series ([ 1 , 2 , 3 , 4 , 5 ])
# 使用该 Series 对象创建 DataFrame
df = pd . DataFrame ( data , columns = [ 'Column_Name' ])
print ( df )
🔘 通过字典列表创建
1
2
3
4
5
6
7
8
9
# 创建一个字典列表
data = [
{ 'a' : 1 , 'b' : 2 },
{ 'a' : 5 , 'b' : 10 , 'c' : 20 }
]
# 使用字典列表创建 DataFrame
df = pd . DataFrame ( data )
🔘 通过 Series 对象字典创建
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
area_dict = {
'Texas' : 695662 ,
'New York' : 141297 ,
'Florida' : 170312 ,
'Illinois' : 149995 ,
'California' : 423967
}
area = pd . Series ( area_dict )
# 两列,每列都为 Series 对象,通过字典传入
states = pd . DataFrame (
{ 'pop' : pop ,
'area' : area }
)
🔘 通过 NumPy 二维数组创建
1
2
3
4
5
6
7
8
9
10
11
12
13
14
import numpy as np
# 创建一个 NumPy 二维数组
data = np . array (
[[ 1 , 2 , 3 ],
[ 4 , 5 , 6 ]]
)
# 使用 NumPy 二维数组创建 DataFrame
df = pd . DataFrame (
data ,
columns = [ 'Column1' , 'Column2' , 'Column3' ]
)
🔘 通过 NumPy 结构化数组创建
1
2
3
4
5
6
7
8
9
# 创建一个 NumPy 结构化数组
data = np . array (
[( 1 , 'a' ), ( 2 , 'b' ), ( 3 , 'c' )],
dtype = [( 'Column1' , int ), ( 'Column2' , str )]
)
# 使用 NumPy 结构化数组创建 DataFrame
df = pd . DataFrame ( data )
🔘 通过传入 data、index、columns 参数创建
1
2
3
4
5
6
7
8
9
10
11
12
df = pd . DataFrame (
np . arange ( 6 ) . reshape ( 3 , 2 ),
# 设置【行索引】分别为 a、b、c
index = [ 'a' , 'b' , 'c' ],
# 设置【列名】分别为 foo、bar
columns = [ 'foo' , 'bar' ]
)
print ( df [ 'foo' ])
# 查看 DataFrame 对象 foo 列 a 行对应的值
print ( df [ 'foo' ][ 'a' ])
⭕ Index 对象
1
2
3
4
ind1 = pd . Index ([ 2 , 3 , 5 , 7 , 11 ])
ind2 = pd . Index ([ '2' , '3' , '5' , '7' ])
print ( ind1 )
print ( ind2 )
⭕ 数据选取
假设 data 是 Series 类型
1
2
3
4
5
6
# Serise 对象索引为 1 对应的值
print ( data [ 1 ])
# Serise 对象索引值为 0 到 2 的值
print ( data [ 0 : 3 ])
1
2
3
4
5
6
7
8
9
10
# 【显式索引】
# 使用的是行和列的【标签】
# 包括结束位置
df . loc [ '行标签' , '列标签' ]
# 【隐式索引】
# 使用行和列的整数下标
# 索引从 0 开始
df . iloc [ 1 , 2 ]
1
2
3
# 注意
df . iloc [ 32 ] # 结果是 Series
df . iloc [[ 32 ]] # 结果是 DataFrame
⭕ 一些代码操作
🔘 常用操作
1
2
3
4
5
6
7
8
9
# 某个 dataframe 中的值需要进行替换
# 我们可以使用 replace
# 也可以通过下面的方法
b = {
'原本的值1' : 新的值1 ,
'原本的值2' : 新的值2 ,
......
}
df [ '...' ] = df [ '...' ] . map ( b )
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# 查看字段名
df . columns
# 字段名重命名
df . rename ( columns = { '原来字段名' : '新字段名' }, inplace = True )
# 查看行列数
df . shape
# 查看数据前 5 行
df . head ()
# 查看数据后 5 行
df . tail ()
# 查看df的字段的数据类型
df . dtypes
# 数据类型的转换
df [ '...' ] . astype ( ... )
# 数据描述统计
df . describe () . T
# 其中最后五列的【分位数】等同于【np.percentile】
pd . DataFrame (
np . percentile (
df ,
q = [ 0 , 25 , 50 , 75 , 100 ],
axis = 0
) . T
)
🔘 增加列
1
2
3
4
5
6
7
8
states = pd . DataFrame (
{ 'pop' : pop ,
'area' : area }
)
# 类似字典新增键值对
states [ 'density' ] = states [ 'pop' ] / states [ 'area' ]
🔘 筛选数据
1
2
3
4
5
6
7
8
9
10
# 取出 grammer 字段中,值等于 Python 的数据
df [ df [ 'grammer' ] == 'Python' ]
# 取出 popularity 字段中,值大于 3 且小于 7 的数据
df [( df [ 'popularity' ] > 3 ) & ( df [ 'popularity' ] < 7 )]
# 取出 popularity 字段为最大值的数据
df [ df [ "popularity" ] == df [ "popularity" ] . max ()]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 取出 grammer 字段中,值含有 Python 的数据
result = df [ 'grammer' ] . str . contains ( 'Python' )
# result 是布尔值 Series,即一列的 True 或者 False
# 可能存在缺失值,故填补以防止报错
result . fillna ( value = False , inplace = True )
df [ result ]
# 结合 NumPy 的函数
# 筛选出薪资大于 5000 的最后 3 个数据
res = df . iloc [ np . where ( df [ 'salary' ] > 5000 )[ 0 ][ - 3 :], :]
# 筛选出字段 name 值出现在列表中的数据
df [ df [ 'name' ] . isin ([ '--' , '*' ])]
# 反向筛选 ~
df [ ~ df [ 'name' ] . isin ([ '--' , '*' ])]
# 提取某一字段在指定位置的值
df [ '...' ] . take ([ 1 , 10 , 15 ])
df [ '...' ] . iloc [[ 1 , 10 , 15 ]]
# 取出第 2、11、16 行的数据
🔘 数据描述统计
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 统计 grammer 字段中各类值出现的次数
df [ 'grammer' ] . value_counts ()
# 计算 popularity 字段的均值
df [ 'popularity' ] . mean ()
# 计算每一行的均值
df [[ "col1" , "col2" , "col3" ]] . mean ( axis = 1 )
# 通过匿名函数统计字段值的长度,生成新字段
df [ 'len_str' ] = df [ 'grammer' ] . map (
lambda x : len ( x )
)
# 获取某一字段的中位数
import numpy as np
np . median ( df [ 'popularity' ])
🔘 关于数据处理
1
2
3
4
5
6
7
8
9
10
11
# 利用平均值填补法,对popularity字段进行缺失值填补
# .interpolate()是插值函数,默认是线性插值
# 可以实现前后值的平均值
df [ 'popularity' ] . fillna (
df [ 'popularity' ] . interpolate ,
inplace = True
)
# 删除所有存在缺失值的行
data . dropna ( axis = 0 , how = 'any' , inplace = True )
1
2
3
4
5
6
7
8
9
# 根据 grammer 字段【去除重复值】
df . drop_duplicates (
[ 'grammer' ],
inplace = True
)
# 查看某一字段中有几类值
df [ '...' ] . nunique ()
🔘 Pandas 100
1
2
3
4
5
6
7
8
9
# 找局部极大值可以看作是:从侧面看一些起伏的山岭并找到所有的山顶
# 先做差分(后一个减去前一个),
# 再通过一个 sign()函数,使得:负数为 -1,正数为 1,0还是 0,
# 最后再做一次差分
tem = np . diff ( np . sign ( np . diff ( df4 [ "col1" ])))
# 局部极大值的下标
np . where ( tem ==- 2 )[ 0 ] + 1
🔘 其他
1
2
# 字段值转成列表
df [ 'grammer' ] . tolist ()
1
2
3
# 字段顺序的更换
cols = df . columns [[ 新的顺序 , 对原df的字段下标进行改变 ]]
df = df [ cols ]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 添加数据
row = {
'grammer' : 'Perl' ,
'popularity' : 6.6 ,
}
df = df . append ( row , ignore_index = True )
# 合并两个df
... = pd . concat ([ df1 , df2 ], axis = 1 ) #两列左右合并
... = pd . concat ([ df1 , df2 ], axis = 0 , ignore_index = True ) #上下合并
# 删除某字段
df . drop ([ 'popularity' ], axis = 1 )
# 或者
del df [ 'popularity' ]
1
2
3
4
5
6
# 根据 popularity 字段进行降序排列
df . sort_values (
'popularity' ,
inplace = True ,
ascending = False
)
1
2
3
4
5
6
7
8
# apply 的应用,比循环处理效率高
def func ( df ):
。。。。。。
return df
# 分别对每一列使用 func 函数
df = df . apply ( func , axis = 1 )
1
2
3
4
5
6
7
8
9
# 【数据分箱】
bins = [ 0 , 5000 , 20000 , 50000 ]
group_names = [ "低" , "中" , "高" ]
# 通过将salary字段分箱得到新字段categories的三种类别
dff [ 'categories' ] = pd . cut (
dff [ "salary" ],
bins = bins ,
labels = group_names
)
1
2
3
4
5
6
7
8
9
10
11
12
# 若两字段的值都是字符串,可以直接相加
# 通过 map 或者 astype 将数据转换成字符串类型
df [ 'test' ] = df [ '...' ] . map ( str ) + df [ '...' ] . map ( str )
df [ 'test' ] = df [ '...' ] . astype ( str ) + df [ '...' ] . astype ( str )
# map 函数是利用函数或者 lambda 匿名函数,实现转换成 str 类型的目的
# astype 是 pandas 中的函数
# 两个数值类型的字段可以直接进行算术运算
import numpy as np
# 计算二范数
np . linalg . norm ( df [ '...' ] - df [ '...' ])
1
2
3
4
5
6
7
8
9
10
# 将某一字段设置为索引
df = df . set_index ( '...' )
# 重置行索引
df = df . reset_index ()
# 修改列名
df . columns = [ '...' , '...' , ... ]
1
2
3
# 时间数据的处理【to_pydatetime】
# 原本值是 2020-03-20 11:30:18
dff . iloc [ i , 0 ] = dff . iloc [ i , 0 ] . to_pydatetime () . strftime ( '%m- %d ' )
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
# 前后两条数据的差值,也叫做【差分】
df [ '...' ] . diff ()
# 前后两条数据的变化率
df [ '...' ] . pct_change ()
# 设置长度为 5 的数据滑动窗口,并计算均值
df [ '...' ] . rolling ( 5 ) . mean ()
# 前四个值为空
# 累计均值
data [ "开盘价(元)" ] . expanding ( min_periods = 1 ) . mean ()
# 比如第一行是1月4号的开盘价均值,是它本身
# 第二行就是1月4号与1月5号的开盘价均值,两个相加求和取均值
# 依此类推,最后一行就是总的开盘价的均值
# .expanding(min_periods=1).sum() 则是计算累计和
# 注意和 rolling().mean()不一样,.expanding()的窗口在不断变大
# 将数据往后移动 5 行,在时间序列数据中也即向后推迟 5 个时间点
df . shift ( 5 )
# 反之向前移动
df . shift ( - 5 )
# 计算布林指数,公式如下:
# 中轨线 = N 日的移动平均线
df [ '...' ] . rolling ( N ) . mean ()
# 上轨线 = 中轨线 + 两倍的标准差
df [ '...' ] . rolling ( N ) . mean () + 2 * df [ '...' ] . rolling ( N ) . mean () . std ()
# 下轨线 = 中轨线 - 两倍的标准差
df [ '...' ] . rolling ( N ) . mean () - 2 * df [ '...' ] . rolling ( N ) . mean () . std ()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 卷积
np . convolve (
df [ "col2" ],
np . ones ( 3 ) / 3 ,
mode = "valid"
)
# 假设 df['col2'] 的前三个是 0、5、10
# 三个均乘以 np.ones(3)/3 的第一个数 1/3, 得到 0、5/3、10/3
# 同理乘以第二个数同样是 1/3, 得到 0、5/3、10/3
# 同理乘以第三个数同样是 1/3, 得到 0、5/3、10/3
# 最后求和:
# 0 5/3 10/3
# 0 5/3 10/3
# 0 5/3 10/3
# -----------------------
# 0 5/3 5 5 10/3
# 取最中间那位, 即 5
# 所以上面卷积结果 temp 的第一个数为 5
# 大概就是每做一次乘积移动一下, 最后错位相加起来, 再看 mode取的是默认的 full、same 还是 valid
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 将某些字段名转换成字段值
import pandas as pd
df = pd . DataFrame ({
'Name' : { 0 : 'Jane' , 1 : 'Tom' },
'Course' : { 0 : 'Master' , 1 : 'Graduate' },
'Age' : { 0 : 27 , 1 : 31 }
})
# 将 Course 和 Age 字段的种类合并作为var列
df1 = pd . melt (
df ,
id_vars = [ 'Name' ], value_vars = [ 'Course' , 'Age' ],
var_name = 'Course & Age' , value_name = 'values'
)
df1
Melt 意思是熔化
Pivot 是转动的意思
1
2
3
4
5
6
7
8
9
df2 = pd . pivot_table (
df1 ,
index = 'Name' ,
values = 'values' ,
columns = 'Course & Age'
)
df2 . columns . name = None
df2 . index . name = None
df2 # 对比原数据 df
Matplotlib
⭕ 基本设置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import numpy as np
import pandas as pd
import matplotlib as mpl
import matplotlib.pyplot as plt
# 设置中文、负号正常显示
plt . rcParams [ 'font.sans-serif' ] = 'SimHei'
plt . rcParams [ 'axes.unicode_minus' ] = False
# 设置画图风格
plt . style . use ( 'classic' )
# 'seaborn-whitegrid'
# 将 x 轴转换成【对数格式】
plt . semilogx ( ... , ... )
⭕ 编写风格
matlab 编写风格:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 创建一个画布
plt . figure ()
# 创建两行一列的子区域
plt . subplot ( 2 , 1 , 1 )
plt . plot ( x , y1 , '-' )
plt . subplot ( 2 , 1 , 2 )
plt . plot ( x , y2 , '--' )
# 设置图像标题
plt . title ( ' ' )
# 设置横、纵轴名称
plt . xlabel ( ' ' )
plt . ylabel ( ' ' )
# 添加图例
plt . legend ( ' ' )
# 限制坐标轴范围
# 列表的参数分别为[xmin, xmax, ymin, ymax]
plt . axis ( [ - 1 , 11 , - 1.5 , 1.5 ] )
# 多子图设置同一标题
fig . suptitle ( '...' , size = 20 )
面向对象编写风格:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
fig , ax = plt . subplots (
3 , 8 , figsize = ( 9 , 4 ),
sharex = 'col' , sharey = 'row' ,
subplot_kw = { 'xticks' :[], 'yticks' :[]},
gridspec_kw = dict ( hspace = 0.1 , wspace = 0.1 )
)
ax [ 0 ] . plot ( x , y1 , '-' ) # 注意索引从 0 开始
ax [ 1 ] . plot ( x , y2 , '--' )
ax [ 0 ] . set_title ( ' ' )
ax [ 0 ] . set_xlabel ( ' ' )
ax [ 0 ] . set_ylabel ( ' ' )
ax [ 0 ] . legend ( ' ' )
ax [ 0 ] . set_xlim ()
ax [ 0 ] . set_ylim ()
1
2
3
4
5
6
7
8
# 嵌套的子图绘制
# 先定义一个子区域来画图
ax1 = plt . axes ()
# 在其中定义一个小图
ax2 = plt . axes ([ 0.4 , 0.6 , 0.2 , 0.2 ])
x = np . linspace ( 0 , 10 , 100 )
ax1 . plot ( x , np . sin ( x ))
ax2 . plot ( x , np . cos ( x ))
1
2
# 绘制不规则子图
grid = plt . GridSpec ( 2 , 3 , wspace = 0.4 , hspace = 0.3 )
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 等高线绘制
# 通过 np.meshgrid(x,y) 得到网格型数据,即一个矩阵
# 再通过横纵坐标计算得到对应位置的 z 值
contours = plt . contour (
X , Y , Z ,
3 , # 这里的 3 用来控制等高线的疏密程度,越大越密
colors = 'black'
)
# 给等高线标上对应的值
plt . clabel (
contours ,
inline = True ,
fontsize = 10
)
plt . imshow (
Z ,
extent = [ 0 , 5 , 0 , 5 ],
origin = 'lower' ,
cmap = plt . cm . viridis
)
# 颜色条
cb = plt . colorbar ()
cb . set_label ( '。。。' ) # 为颜色条添加标题
1
2
3
4
5
6
# 二维直方图
plt . hist2d (
x , y ,
bins = 30 ,
cmap = plt . cm . gray_r
)
⭕ seaborn 绘图
1
2
3
4
import seaborn as sns
import pandas as pd
sns . set ()
sns . color_palette ( 'CMRmap_r' ) #调色盘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
# 绘制直方图,展示数据分布
sns . histplot (
x = data [ '...' ],
shrink = 0.8 , bins = 12 , hue = data [ '...' ],
multiple = "dodge" , palette = 'brg'
)
# 条形图,不同类别出现的频次
sns . barplot (
x = data [ '...' ] . value_counts () . index ,
y = data [ "..." ] . value_counts (),
saturation = 0.75
)
# 散点图
sns . scatterplot (
x = data [ '...' ],
y = data [ '...' ]
)
# 绘制正态分布曲线(也可以用来绘制等高线)
sns . kdeplot ( data , shade = True )
# 箱线图
sns . catplot (
data = data ,
x = '...' , y = '...' ,
kind = "box" ,
height = 6 , aspect = 1.5 , color = "#FBC02D" ,
)
# 热力图
sns . heatmap (
data = data [:, ? : ? ] . corr (),
annot = True ,
cbar = True ,
cmap = "binary"
)
# 多子图展示变量间的【相关性】
sns . jointplot (
"x" , "y" ,
data ,
kind = 'hex'
)
# 展示变量之间的关系
sns . pairplot (
iris [ cols ],
hue = 'species' , height = 2.5
)
常见的 marker:
常见的 marker
Scipy
可以用于:
插值、概率统计、曲线拟合、最优化、积分、解微分方程、处理稀疏矩阵、线性代数等等
plotly
带有交互式的可视化,详细见 “D:\jupyterproject\或许有用\Python 整理\kaggle\2022 机器学习与数据科学\比较高级的绘图方法.ipynb” 这一节
其他
logging
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
import logging
import datetime
# 创建logger
logger = logging . getLogger ( 'my_logger_1' )
logger . setLevel ( logging . DEBUG ) # 设置日志级别
# 创建file handler,用于写入日志文件
now = datetime . datetime . now () . strftime ( '%Y-%m- %d _%H-%M-%S' )
fh = logging . FileHandler ( f 'myapp_ { now } .log' , encoding = 'utf-8' )
fh . setLevel ( logging . DEBUG )
# 将日志输出到控制台
controlshow = logging . StreamHandler ()
controlshow . setLevel ( logging . DEBUG )
# 创建formatter,用于控制日志格式
formatter = logging . Formatter ( ' %(asctime)s - %(name)s - %(levelname)s - %(message)s ' )
# 为handler添加formatter
fh . setFormatter ( formatter )
controlshow . setFormatter ( formatter )
# 为logger添加handler
logger . addHandler ( fh )
logger . addHandler ( controlshow )
# 写入日志
logger . debug ( '这是一条debug级别的日志信息' )
logger . info ( '这是一条info级别的日志信息' )
logger . warning ( '这是一条warning级别的日志信息' )
logger . error ( '这是一条error级别的日志信息' )
logger . critical ( '这是一条critical级别的日志信息' )
# 关闭文件句柄
fh . close ()
文件解压
1
2
3
4
5
from zipfile import ZipFile
data_extract = ZipFile ( 'zip文件路径' , 'r' )
data_extract . extratall ()
时间模块
1
2
3
4
5
6
import datetime
t = datetime . datetime . now ( tz = None )
# 参数 tz 用于设置时区
# 省略或者None则默认使用本机的时区
1
2
3
4
5
6
t1 = t . strftime ( '%Y-%m- %d %H:%M:%S' )
# 输出:'2024-06-15 21:48:00'
# 逆操作
datetime . datetime . strptime ( t1 , '%Y-%m- %d %H:%M:%S' )
# 输出:datetime.datetime(2024, 6, 15, 21, 48, 0, ???)
上述内容中的 %Y 需要区分大小写
%y 得到的内容是 24 而不是 2024
1
2
3
4
5
# 时间戳
t2 = t . timestamp ()
# 逆操作
datetime . datetime . fromtimestamp ( t2 , tz = None )
时间加减
1
2
3
4
5
6
from datatime import timedelta
t3 = t + timedelta ( hours = 1 ) # t 时间上再加一小时
# hours=-1 即减去一小时
# 当然还可以设置其他参数,例如 weeks、days、minutes、seconds 等等
word 辅助模块
1
2
3
4
5
6
7
8
9
from docx import Document
from docx.enum.text import WD_ALIGN_PARAGRAPH #设置对象居中、对齐等。
from docx.enum.text import WD_TAB_ALIGNMENT , WD_TAB_LEADER , WD_LINE_SPACING #设置制表符等
from docx.shared import Inches #设置图像大小
from docx.shared import Pt , Cm #设置像素、缩进等
from docx.shared import RGBColor #设置字体颜色
from docx.shared import Length #设置宽度
from docx.oxml.ns import qn #设置中文版式
import os
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 创建一个空白文档
document = Document ()
# 正文格式设置
# 设置基础字体——西文
document . styles [ 'Normal' ] . font . name = 'Times New Roman'
# 设置基础中文字体——宋体
document . styles [ 'Normal' ] . _element . rPr . rFonts . set ( qn ( 'w:eastAsia' ), u '宋体' )
# 设置字体颜色为黑色
document . styles [ 'Normal' ] . font . color . rgb = RGBColor ( 0 , 0 , 0 )
# 设置字体大小
document . styles [ 'Normal' ] . font . size = Pt ( 10.5 )
# 设置段落、行间距、缩进
document . styles [ 'Normal' ] . paragraph_format . space_before = Pt ( 0 )
document . styles [ 'Normal' ] . paragraph_format . space_after = Pt ( 0 )
document . styles [ 'Normal' ] . paragraph_format . line_spacing = 1.5
document . styles [ 'Normal' ] . paragraph_format . alignment = WD_ALIGN_PARAGRAPH . JUSTIFY
document . styles [ 'Normal' ] . paragraph_format . first_line_indent = Pt ( 21 )
1
2
3
4
5
6
# 添加段落
document . add_paragraph () . add_run ( '需要添加的文本内容' )
document . add_paragraph () . add_run ( '' )
# 分页
document . add_page_break ()
单元测试
单元测试模块 unittest 小示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import unittest
class TestAddition ( unittest . TestCase ):
def setUp ( self ):
print ( "Setting up the test" )
def tearDown ( self ):
print ( "Tearing down the test" )
def test_twoPlusTwo ( self ):
total = 2 + 2
# 使用断言语句
self . assertEqual ( 4 , total )
if __name__ == '__main__' :
unittest . main ()
单元测试的开始 setUp 和结束 tearDown 函数,每个测试的开始和结束都会运行一次
添加不同类型的“断言”语句
其他
⭕ 如何实现想要的功能?思路分析
列出需要的功能
设计框架,功能对应需要通过哪些函数实现
各个函数实现的具体步骤
⭕ 继承问题
假设类 D 继承了类 B 和类 C,而类 B 和类 C 都继承了类 A
当创建 D 的实例 d 时,会按照方法解析顺序(Method Resolution Order,MRO)来确定调用父类的顺序
在 Python 中,MRO 是通过 C3 线性化算法来确定的
在这个算法中,首先会按照 广度优先搜索 的顺序来遍历继承关系图,然后保持子类在父类之前的顺序
在这个例子中,MRO 的顺序是 D -> B -> C -> A -> object
因此,在调用 super().__init__() 时,会按照这个顺序 依次调用父类的 __init__ 方法
⭕ @property
使得类中的 方法可以作为属性 调用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Pager :
def __init__ ( self , current_page ):
# 用户当前请求的页码(第一页、第二页...)
self . current_page = current_page
# 每页默认显示 10 条数据
self . per_items = 10
@property
def start ( self ):
val = ( self . current_page - 1 ) * self . per_items + 1
return val
@property
def end ( self ):
val = self . current_page * self . per_items
return val
p = Pager ( 2 )
print ( p . start ) # 就是起始值,即:11
print ( p . end ) # 就是结束值,即:20
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Goods :
# python3 中默认继承 object 类
# python3 中才有 @xxx.setter @xxx.deleter
@property
def price ( self ):
print ( '@property' )
@price.setter
def price ( self , value ):
print ( '@price.setter' )
@price.deleter
def price ( self ):
print ( '@price.deleter' )
obj = Goods ()
obj . price
obj . price = 123
del obj . price
或者
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Goods :
def get_price ( self ):
print ( 'price of goods' )
def set_price ( self , value ):
print ( 'set' )
def del_price ( self ):
print ( 'delete' )
goods = property (
get_price ,
set_price ,
del_price ,
'description...'
)
obj = Goods ()
obj . goods
obj . goods = '5元'
del obj . goods
⭕ 上下文管理器实现文件操作
with open('') as f: 语句就是通过定义 上下文方法 来实现文件的操作
下面我们尝试实现 MyFile() 类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class MyFile ( object ):
def __init__ ( self , file_name , file_model ):
# 创建实例属性
self . file_name = file_name
self . file_model = file_model
# 上文方法(打开资源)
def __enter__ ( self ):
# 打开文件,返回文件资源
self . file = open ( self . file_name , self . file_model )
return self . file
# 下文方法(关闭资源)
def __exit__ ( self , exc_type , exc_val , exc_tb ):
# 关闭文件资源
self . file . close ()
with MyFile ( '???.txt' , 'r' ) as f :
data = f . read ()
print ( data )
⭕ 文件 I/O
DataFrame 写入文件
1
2
df . to_excel ( '。。。.xlsx' , index = False )
df . to_csv ( '。。。.csv' , index = False )
路径标准化
1
2
3
4
import os
input_path = 'D:/jupyterproject/logo.jpg '
input_file = os . path . normpath ( input_path . strip ())
# normpath 统一为双反斜杠类型的 \\
图片的读取
1
2
3
4
5
6
7
8
9
10
11
12
13
14
input_file = '???.jpg'
from PIL import Image
import numpy as np
image = Image . open ( input_file ) # 用PIL中的Image.open打开图像
image = image . resize (( 512 , 512 ), Image . LANCZOS ) #修改图片大小
image = image . convert ( 'RGB' ) #转换成RGB模式
raw_image = np . array ( image ) #转化成numpy数组
print ( raw_image . size )
image_PIL = Image . fromarray (
np . uint8 ( raw_image ) . reshape (( 512 , 512 , 3 )),
mode = "RGB" , #array转换成图片
)
问题整理
Python 中写实例方法时先加 self,若后续发现无需访问实例属性,再改为静态方法(移除 self 并添加 @staticmethod)
self/this 的本质是让方法知道“当前操作的是哪个对象实例”,不同语言仅在“是否显式声明”上有差异,目的一致
实例方法、类方法、静态方法的使用场景
优先写实例方法,只要方法需要操作对象的具体数据(实例属性),就用 self 参数
若写完方法发现仅使用了类属性,仅处理类共享数据,则用 @classmethod + cls 装饰为类方法,避免硬编码类名
若方法不访问实例或者类属性,仅逻辑上归类,用 @staticmethod 装饰为静态方法
Python 通过属性/方法名前的下划线数量 来标记访问权限(非强制语法,更多是“约定”):
权限类型
声明方式
示例
公有
无下划线
name、get_name()
保护
单下划线 _
_age、_calc()
私有
双下划线 __
__id、__secret()
公有属性/方法(无下划线)
访问范围 :类内部、子类、类外部均可直接访问 。
用途 :开放给外部使用的“接口”,如 user.name、user.login()。
保护属性/方法(单下划线 _)
约定含义 :“仅供内部使用,不建议外部直接访问”(但无语法限制)。
访问行为 :
类内部/子类中可直接访问(如 self._age)。
类外部可以访问 (如 obj._age),但 Python 解释器(如 PyCharm)会发出警告 (提醒开发者遵守约定)。
用途 :类内部辅助逻辑,不希望被外部随意调用(如数据校验、中间计算)。
私有属性/方法(双下划线 __)
语法限制 :Python 会对名称进行“名字改写 ”(__xxx → _类名__xxx),外部无法直接访问。
访问行为 :
类内部可直接访问(如 self.__id)。
类外部直接访问会报错 (如 obj.__id → 提示“属性不存在”)。
类外部若强行访问,需用改写后的名称(如 obj._User__id),但强烈不建议 (破坏封装)。
用途 :类内部核心逻辑,完全禁止外部直接访问(如加密密钥、敏感数据)。
为什么要区分访问权限?核心目的不是为了安全,而是为了“代码可读性”和“使用规范”
明确接口边界 :告诉使用者哪些属性/方法是“安全可用”的(公有),哪些是“内部实现”(保护/私有),避免误用。例:一个类有 100 个方法,若全是公有,使用者难以判断哪些该调用、哪些是内部逻辑。
降低维护成本 :保护/私有成员的修改不会影响外部代码(因为外部不依赖它们)。
避免命名冲突 :私有属性通过“名字改写”,可防止子类意外覆盖父类的内部方法。
Java :访问权限是强制语法 (public/protected/private),编译时会检查,违规则报错。
Python :访问权限是约定为主 (仅私有有语法辅助),更依赖开发者自觉遵守(“我们都是成年人了”)。
优先用公有 :开放给外部的功能,明确标记为公有(无下划线)。
内部逻辑用保护 :类内部辅助方法/属性,加单下划线(_),提示外部“勿调用”。
核心机密用私有 :完全禁止外部访问的敏感数据/方法,加双下划线(__),通过语法限制防止误操作。