Python自定义模块 带解释

本文介绍了Python中创建带滚动条的窗口及其组件的使用,包括常用窗口、Frame和treeview的实现。同时,文章还探讨了列表差异比较、treeview排序、邮件内容解码以及Selenium的显式等待和网页元素操作。这些内容对于Python GUI编程和自动化测试具有实用价值。

带滚动条的窗口

由于滚动条不能绑定窗口或框架,只能在窗口下创建frame,在frame下创建canvas,滚动条绑定canvas

  1. 创建窗口,固定其尺寸,不允许修改
  2. 创建画布 - 创建滚动条 - 画布和滚动条放在同一个parent下
  3. 创建frame,其尺寸占画布宽、高的90%
  4. 结合窗口、画布、frame,创建带滚动条的窗口
  5. 为滚动条设置滚动范围(canvas绑定滚动条要设置scrollregion才能生效)
from tkinter import *

def create_win(title=None, size_str=None):
    win = Tk()
    win.title(title)
    win.geometry(size_str)
    win.resizable(0,0)
    return win

def create_canvas(parent):
    canvas = Canvas(parent)
    canvas.pack(fill=Y, side=LEFT)
    bar = Scrollbar(canvas)
    bar.place(relx=0.95, relheight=1.0)   
    bar.configure(command=canvas.yview)
    canvas.config(yscrollcommand=bar.set)
    return canvas

def create_frame(canvas):
    w = canvas.winfo_reqwidth()
    h = canvas.winfo_reqheight()
    frame = Frame(canvas, width=w*0.9, height=h*0.9)
    return frame, w, h
    
def win_with_bar(title=None, size_str=None):
    win         = create_win(title=title, size_str=size_str)     #创建窗口
    canvas      = create_canvas(win)        #创建与滚动条绑定的canvas
    frame, w, h = create_frame(canvas)      #创建frame
    #把frame放在canvas的中心位置,受滚动条影响,中心点横坐标略有偏移
    canvas.create_window((w/2-10, h/2), window=frame)   
    return win, canvas, frame, h

def set_scrollregion(canvas, frame, h):
    L = [i.winfo_reqheight() for i in frame.winfo_children()] #组件高度累加
    frame_h = sum(L) + 10     #部件总高度,加上边距10,作为frame的高度
    y1 = (h - frame_h)/2      #滚动范围的开始位置:(画布高度 - frame高度) / 2
    y2 = y1 + frame_h         #滚动范围的结束位置:开始位置 + frame高度
    canvas.config(scrollregion=(0, y1, 0, y2))
    
'''
#应用程序:创建窗口、画布、frame——在frame中创建部件——根据部件高度,设置滚动范围
win, canvas, frame, h = win_with_bar(title='标题', size_str=None)
for i in range(50): Label(frame, text='占位标签'+str(i)).pack()
set_scrollregion(canvas, frame, h)
'''

常用窗口:标签 + 滚动文本框 + 按钮

  1. 此函数不带参数
  2. 仅在函数内部导入模块
  3. 窗口、框架、标签、文本框、按钮都有名称,并全部返回
  4. 后期可以调用所有组件,也可以调用部分组件
def label_text_button_win():
    import tkinter
    win = tkinter.Tk()
    win.title("标题")    
    frame = tkinter.Frame(win)
    frame.pack(padx=20, pady=20)
    label = tkinter.Label(frame, text="文本行", anchor='sw')
    label.pack(fill='x')    
    from tkinter import scrolledtext
    text = scrolledtext.ScrolledText(frame, width=30, height=15)
    text.pack(pady=10)    
    button = tkinter.Button(frame, text='确认', width=12)
    button.pack()
    return win, frame, label, text, button

button = label_text_button_win()[-1]

常用Frame:标题行 + 多列组件

  1. 需要指定标题、组件类型、列宽、行数
  2. 标题外的组件全部放在字典D里,由行号和列号组成key,以组件为value
  3. 返回frame和批量组件的字典,方便后期调用
  4. 注意:由于标题行的存在,部件的grid位置 = 组件名中的行号 + 1
  5. 局限:行数和列数都不能超过9。如需更多行数,组件命名时,用分隔符分离行号、列号
def frame_table(parent, labels, commands, widths, row=1):
    frame = Frame(win)
    frame.pack(padx=20, pady=20)

    D = {}
    for c in range(len(labels)):
        Label(frame, text=labels[c]).grid(row=0, column=c, padx=1, pady=1)
        for r in range(row):
            name = f"{r}{c}"
            D[name] = commands[c](frame, width=widths[c])
            D[name].grid(row=1+r, column=c, pady=1, padx=1)
    return frame, D

labels  = ["标题1", "标题2", "标题3"]
comms   = [Entry, Button, Button]
widths  = [15, 15, 15]
frame, D = frame_table(parent, labels, commands, widths, row=1)

借字典提取两列表差异项

  1. 【for i in L1: if i not in L2】效率较慢,列表较长时,用字典中转可提高效率
  2. 流程:以L1中的元素为key创建字典 - 按L2中的元素改写字典 - 只保留字典中未改写的key
def for_not_in(L1, L2): 
    D = {i:0 for i in L1}
    for i in L2: D[i] = 1
    L = [i for i in D if D[i] == 0]
    return L

常用Frame:treeview

Treeview + 滚动条 + 适应列宽 + 排序

  1. 参数列表:父组件、标题列表、数据列表、高度
  2. 创建frame,【expand=True】使fill生效,【fill=BOTH】
  3. 创建基本的tree,不显示第一列(空列),按列名设置标题、居中,放入数据
  4. 创建滚动条 - 绑定组件与滚动条 - 放置滚动条
  5. 放置tree
  6. 设置列宽与内容相关联
    1. tups重组,获取每列内容的最大长度。各内容求长度前先转str以兼容日期、数字
    2. 字符长度*10或250中取最小值,避免单列内容过宽
  7. 添加排序功能
def frame_tree(parent, titles, tups, height):
    f = Frame(parent)
    f.pack(expand=True, fill=BOTH, padx=10, pady=10)
    
    tree = ttk.Treeview(f, show="headings", columns=titles, height=height)
    for title in titles:    tree.heading(title, text=title)
    for title in titles:    tree.column(title, anchor=CENTER)
    for tup in tups:        tree.insert('', END, value=tup)

    bar = Scrollbar(parent, command=widget.yview)
    tree.config(yscrollcommand=bar.set)
    bar.pack(side='right', fill=Y)

    tree.pack(expand=True, fill=BOTH)
    
    lens = [max([len(str(i)) for i in tup]) for tup in zip_list(tups)]
    for i in range(len(lens)):  
        tree.column(titles[i], width=min(lens[i]*10, 250))
    
    tree_sort(tree, titles, tups)
    return tree

tree标题绑定排序功能

tree排序 -源数据

  1. 避免遇到无法排序的大元组,统一把数据转化为大列表格式。
  2. 定义函数sort_,给tree的一个标题添加排序功能。参数:tree,title,数据,reverse值
    1. 获取标题位置p
    2. 原始数据按指定位置p排序,reverse值为【r】
    3. tree表清空旧数据 - 放入新数据
    4. 标题绑定sort_命令,reverse值为【not r】——保证排序在正序、倒序之间切换
  3. 把sort_函数应用到所有标题。reverse初始值为False,后续在False和not False之间切换
def tree_sort(tree, titles, tups):
    tups = [list(tup) for tup in tups]
    def sort_(tree, title, tups, r):
        p = titles.index(title)
        tups.sort(key=lambda tup:tup[p], reverse=r)
        tree.delete(*tree.get_children())
        for tup in tups:    tree.insert('', END, value=tup)
        command = lambda:sort_(tree, title, tups, not r)
        tree.heading(title, command=command)
    for title in titles:
        command = lambda t=title: sort_(tree, t, tups, False)
        tree.heading(title, command=command)

tree排序 -tree数据

直接从tree中获取tups - tups重新排序 - 更新tree。

优点:无需tups参数,无需考虑元组问题

缺点:从tree中提取的数据都是文本格式

def tree_sort(tree, titles):
    def sort_(tree, title, r):
        p = titles.index(title)
        tups = [tree.item(i,"values") for i in tree.get_children()]
        tups.sort(key=lambda tup:tup[p], reverse=r)
        tree.delete(*tree.get_children())
        for tup in tups:    tree.insert('', END, value=tup)
        tree.heading(title, command=lambda:sort_(tree, title, not r))
    for title in titles:
        tree.heading(title, command=lambda t=title: sort_(tree, t, False))

复制tree内容

  1. 获取行号i,根据行号获得整行内容
  2. 用分隔符连接各元素,用换行符连接各行,最后加上标题
  3. 用pyperclip(只能复制文本),把内容复制到剪贴板,自行粘贴到Excel即可
def copy_all():
    full = [tree.item(i,"values") for i in tree.get_children()]
    full = ['\t'.join(tup) for tup in full]
    full = '\n'.join(full)
    import pyperclip
    pyperclip.copy(full)

工作簿转字典

  1. 获取工作簿 - 遍历工作表 - 按行获取表中数据。
  2. 以工作表名称为key,以表中数据为value,储存在字典D中
def get_datas(file):     
    from openpyxl import load_workbook
    D = {}
    book = load_workbook(file)
    for table in book.sheetnames:
        sheet = [[cell.value for cell in row] for row in book[table]]
        D[table] = [tuple(L) for L in sheet]
    return D

字典转SQLITE数据表

  1. 连接数据库
  2. 逐个工作表处理
  3. 拆分工作表中的标题和具体数据
  4. 以工作表名为数据表名,以标题为字段,创建数据表
  5. 构建sql语句,其参数字段由若干个问号组成,问号的数量 = 标题的数量
  6. 批量录入数据
  7. 提交
def create_sql_database(D):  
    conn, cur = connect()
    for table in D:
        titles, *tups = D[table]
        cur.execute(f"create table {table}{titles}")
        value = ["?" for i in tups[0]]
        value = ",".join(value)
        sql = f'insert into {table} values ({value});'
        cur.executemany(sql, tups)
        conn.commit()
    cur.close()

解码邮件单行内容

  1. 导入库
  2. 创建一个空字符串,用于存放结果
  3. 从原内容中拆分出具体内容和编码方式[(content1, code1), (content2, code2), …]
  4. 用for循环对逐个元组处理
    1. 内容有可能是str或bytes格式,仅bytes格式需要处理
      1. 编码方式为None,用默认方式解码
      2. 编码方式非None,用给出的编码方式解码
from email import header
def deal_line(line):
    new_line = ""
    L = header.decode_header(line)
    for content, code in L:
        if type(content) is bytes:
            if code is None: content = content.decode()
            else:            content = content.decode(code)           
        new_line = new_line + (content)
    return new_line

下载邮件

完美解析邮件各部件是非常困难的事情,下载字节串保存,使用时用foxmail等软件读取即可。(备注:conn为POP3连接)

conn_uids = [i.decode().split(' ') for i in conn.uidl()[1]] #大列表:['1', 'POPSPAM_20201125']
D = {L[1]:L[0] for L in conn_uids} #创建字典:{uid:num}
for uid in D:
	msg     = conn.retr(D[uid])[1]
	filename= open(f'{uid}.eml', 'wb').write(b'\r\n'.join(msg))

按行获取文本框内容

  1. 获取所有内容,按换行符切割,得到内容列表
  2. 只保留长度大于0的行,并且去除行首、行尾的空白字符,以列表形式返回
lines = text.get(0.0, END).split('\n') 
lines = [i.strip() for i in lines if len(i)>0]

None转空字符串

先把内容统一成字符串格式,然后用替换改变字符串的内容

L = [[str(j).replace('None', '') for j in i] for i in L]

子列表重新组合

条件:所有子列表的长度一致

步骤:

  1. 以原始大列表中,第一个子列表的长度为step
  2. 大列表展开
  3. 按步长提取元素组成新的大列表
  4. 返回新的大列表
  • 重组2次可以得到原始大列表
def zip_list(L):                                    
    step = len(L[0])                                
    L = [j for i in L for j in i]                   
    L = [L[i:len(L):step] for i in range(step)]     
    return L

SQL语句

构建where条件下,in 后面的列表,对格式有几个要求:

  1. 必需是圆括号,不能是中括号,括号里面的内容必须要用引号——不宜用join
  2. 括号里一定要有内容——列表为空时,用空字符串占位
  3. 最后一个内容后面没有逗号——列表长度为1时,不能直接转元组
def in_clause(L): 
    if len(L) == 0:     clause = "('')"
    elif len(L) == 1:   clause = f"('{L[0]}')"
    else:               clause = tuple(set(L))
    return clause

selenium 显式等待

  1. 引入等待模块、条件模块
  2. 等待时间为30秒,每0.5秒检查一次条件是否满足
  3. 条件:以指定方式定位指定元素。方式和元素由tup指定,如:(By.ID, “multiSearch”)
  4. 获取元素并返回
def my_wait(driver, name, value):
    from selenium.webdriver.support.ui import WebDriverWait
    from selenium.webdriver.support import expected_conditions as EC
    wait = WebDriverWait(driver, 30, 0.5)
    condition = (name, value)
    condition = EC.presence_of_element_located(condition)
    element = wait.until(condition)
    return element
  1. 使用示例。By的可选项有:CLASS_NAME,CSS_SELECTOR,ID,LINK_TEXT,NAME,PARTIAL_LINK_TEXT,TAG_NAME,XPATH
from selenium.webdriver.common.by import By
my_wait(driver, By.ID, "multiSearch")

Selenium伪装

options = webdriver.ChromeOptions()
options.add_experimental_option('excludeSwitches', ['enable-automation'])
options.add_argument("--disable-blink-features=AutomationControlled")
driver = webdriver.Chrome(options=options)
driver.execute_cdp_cmd("Page.addScriptToEvaluateOnNewDocument", {
    "source": """
                Object.defineProperty(navigator, 'webdriver', {
                  get: () => undefined
                })
              """
})
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Ailsa2019

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值