# 网络编程
# socket
又称套接字,程序通过其向网络发出或者应答请求,也可以用户主机间或者进程间的通讯
创建
socket.socket([family[, type[, proto]]]) | |
#都是可选值,family 默认 AF_INET |
family:
套接字家族,可以是 AF_UNIX 或者 AF_INET (6)
前者表示进程间通信,后者表示使用 IPv4 (6)
type:
类型,面向连接 SOCK_STREAM (默认值); 非连接 SOCK_DGRAM
前者是流式套接字,对应 TCP 协议,或者是数据报套接字,对应 UDP
proto:
协议号。一般不填,默认为 0,表示自动匹配。这个值取决于前两个值
当使用 AF_INET 以及 SOCK_RAW(原始套接字,需要 root 权限)时才需指定。
存在:IPPROTO_TCP(6)
IPPROTO_UDP(17)
IPPROTO_ICMP(1)
IPPROTO_IP(0)ICMP 可以用于处理 ping 命令时
内建方法
服务端 | |
s.bind() | |
#绑定地址到套接字,在 AF_INET 下时元组形式保存(host,port) | |
s.listen() | |
#TCP 监听。只有在转为监听后才能 accept | |
s.accept() | |
#被动接受 | |
-------------------- | |
客户端 | |
s.connect() | |
#主动初始化 TCP 服务连接,一般格式是元组(hostname,port) | |
s.connect_ex() | |
#上一个的扩展,遇到错误不抛异常,而是返回出错码,没有错误返回 0 | |
-------------------- | |
公用 | |
s.recv() | |
#接受 TCP 数据 | |
s.send() | |
s.sendall() | |
s.recvfrom() | |
#接受 UDP 数据 | |
s.sendto() | |
#发送 UDP 数据 | |
s.close() | |
#关闭套接字 | |
s.getpeername() | |
#返回连接的远程地址,元组(ipaddr,port) | |
s.getsockname() | |
#返回套接字自己的地址,元组(ipaddr,port) |
10061/111:connect refused
10060: time out
# 实例
import socket | |
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) | |
host = socket.gethostname() | |
port = 9999 | |
server.bind((host, port)) | |
#设置最大连接数,超过后排队 | |
server.listen(5) | |
while True: | |
#建立客户端连接,conn 是生成的链接实例,这里只是等待链接 | |
conn,addr = serversocket.accept() | |
while Ture: | |
data = conn.recv(1234) #接收数据 | |
conn.send(data.upper()) #发送数据 | |
conn.close(); | |
#客户端: | |
s=socket.socket() | |
s。connect((socket.gethostname,1111)) | |
s.recv(1234) | |
s.close |
可能不是那么重要的:
s.setblocking(True/False) | |
#默认为 True,阻塞模式,send/recv/accept 都要等待返回才继续执行,非阻塞反之 | |
#非阻塞中,若操作无法完成,会报错 socket.error | |
s.setsockopt(level,optname,value) | |
#设置给定套接字选项的值 | |
s.getsockopt(level,optname[.buflen]) | |
#返回套接字选项的值 |
level
选项所属协议层级,指定对什么类型的套接字生效,SOL_SOCKET 表示所有
此外就是 IPPROTO_TCP 一类的optname
选项名称,是 socket 模块定义的常量。包括 SO_REUSEADDR(端口重用)等value
选项值,整数(一般是 1/0,表示开启关闭)或者字节流
选项:
SO_REUSEADDR
通过 fork 子进程来继承 socket,实现多个进程共用一个 socketSO_REUSEPORT
通过多个 socket 共用一个端口实现,可以多个进程同时监听使用,而不是同一时间能有一个,这些进程必须有相同的 euid(有效用户 id)和 uidtip: 有配置 SUID 的文件在运行时会强制 euid 为文件所有者,需要注意
# request
比较常见(?
response=request.get('http://potato.top')
response=request.request('get','http://potato.top')
#其他省略
response.status_code
response.headers #是字典
response.content #响应内容,单位为字节
response.text #响应内容,unicode
.cookies
.elapsed #从发送到返回的时间,是个对象,用于测试响应速度
.json #如果返回的是
.ok #状态码小于400true
.encoding #查看头部字符编码
post 可以由多个参数
request.post(url,data={key:value},json={key1:value1,key2:value2},args) args可以是cookies,headers,verify,这些都是字典
# scapy
发送
ip=IP(src='192.168.0.1',dst='192.168.0.2') | |
send(ip,iface='ens37') | |
#参数用于指定端口,参数可选,此时 protocol 为 0,没有协议 | |
sendp(Ether(dst)/ARP(hwsrc,psrc,hwdst,pdst)/'xx',iface) | |
#构造 ARP: | |
hwsrc 源MAC | |
psrc 源IP | |
hwdst 目标MAC | |
(p)dst 目标IP | |
'xx' ARP报文(payload) |
发送 & 接收
ans,unans=sr(IP(dst)/ICMP())#返回元组,其中是两个列表,Result 以及 Unanswered | |
#当指向不存在的同网段主机,ARP 广播找不到目标,需要手动终止 | |
ans.show/summary/nsummary() | |
#查看。 | |
ans,unans=srp(Ether(dst)/ARP(pdst),timeout=3,iface) |
ans是一个二维数组,
ans[0]
ans[0][0]第一个发出的包
ans[0][1]第一个发出包接受的第一个包
以上基于 IP 和 Ether 的三 / 二层报文,对于四级:
ans,uans=sr(IP()/TCP(sport,dport ,flags="S")) #S 即 SYN | |
#sport 可用 RandShort (),RandNum () 随机生成,可以两参指定范围 | |
#有些时候不会写具体端口,而是对应网络协议的名字,效果一样 | |
#fuzz (TCP (dport,flags)), 自动填充 sport |
SYN 扫描
有四种情况:
SYN/ACK,正常
RST,端口关闭
Unreachable 的 ICMP,被 ACL / 防火墙过滤。这个回复不是 TCP
无响应,对于 ICMP 来说的被过滤
TCP:dport=[22,80,123] | |
for sent,rece in ans: | |
if rece.haslayler(TCP) and str(rece[TCP].flags)=='SA': | |
print(str(sent(TCP).dport)+"Open") | |
if ...'RA': | |
"Closed" | |
if ...ICMP...'3': | |
"Filterd" | |
for sent in uans: | |
"Filterd" | |
#hasLayer,查找返回包中对应协议 |
# paramiko
包括 SSHClient 和 SFTPClient 两部分,作用类似于 ssh 和 sftp
基础名词:
Channel 一种类Socket | |
Transport 一种加密会话,使用时同步创建一个加密的Tunnels(通道),这个通道就是Channel | |
Session 是client和Server保持连接的对象,用connect()/start_client()/start_server()开始会话 |
常用方法:
connect()
hostname
port=<ssh_post>
username=None
password=None
pkey=None
key_filename=None 一个文件名或者文件列表用于指定私钥
timeout=None 可选的 TCP 超时
allow_agent=True 默认 True,是否允许连接到 ssh 代理
look_for_keys=True 是否在~/.ssh 搜索私钥文件,默认为 True,否则应该通过 key_filename 指定
compress=False
exec_command()
open_sftp()
在当前 ssh 会话的基础上创建一个 sftp 会话,返回一个 SFTPCclient 对象
#client 是 SSHClient 对象 | |
sftp=client.open_sftp() | |
sftp.put('lcoal.txt','remote.txt') |
# 实例
import paramiko | |
ssh=paramiko.SSHClient() | |
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())#自动保存主机名和密钥 | |
ssh.connect(hostname='127.0.0.1',port=22,username='root',password='xxx') | |
stdin,stdout,stderr=ssh.exec_command('ls') | |
print(stdout.read().deccode('utf8')) | |
ssh.close() | |
#连接时使用突然 transport 封装 | |
transport=paramiko.transport(("127.0.0.1",22)) | |
transport.connect(username='root',password='xxx') | |
ssh=paramiko.SSHClient() | |
ssh._transport=transport | |
... | |
transport.close() | |
#登录使用密钥,也可以类似上面使用 transport 封装 | |
private=paramiko.RSAKey.from_private_key_file(<path_to_key_file>) | |
... | |
ssh.connect(hostname='127.0.0.1',username="root",pkey=private) | |
... |
使用 SFTP
from_transport(cls,t)#创建已经连通的 SFTP 客户端通道 | |
put(localpath,remotepath,callback=None,confirm=True) | |
#confirm 会调用 stat () 方法查看文件状态,返回 ls -l 结果 | |
get(remotepath,localpath,callback=None) | |
remove() | |
rename() | |
stat()#如果不想跟随符号链接,使用 lstat () | |
listdir() #列出服务器目录下的文件,比起 stat (), 不能具体到文件,也不能得知类型 |
import paramiko | |
tran=paramiko.transport(('127.0.0.1',22)) | |
tran.connect(username='root',password='xxx') | |
sftp=paramilo.SFTPClient.from_transport(tran)#这里使用通道,不是 ssh.open_sftp () | |
sftp.put(localpath,remotepath) | |
sftp.get(remotepath,localpath) | |
tran.close() |
推荐将整个功能封装
import paramiko | |
class ssh(object): | |
def __init__(self,hostdata): | |
self.ip=hostdata['ip'] | |
self.port=hostdata['port'] | |
self.username=hostdata['user'] | |
self.passwd=hostdata['passwd'] | |
self.transport=paramiko.Transport((self.ip,self.port)) | |
self.transport.connect(username=self.username,password=self.passwd) | |
self.ssh=paramiko.SSHClient() | |
self.ssh._transport=self.transport | |
self.sftp=self.ssh.open_sftp() | |
def command(self,cmd): | |
if (isinstance(cmd, (str, int, float))): | |
stdin,stdout,stderr=self.ssh.exec_command(cmd) | |
return stdout.read() | |
else: | |
self.resultlist=[] | |
for c in cmd: | |
stdin,stdout,stderr=self.ssh.exec_command(c) | |
self.resultlist.append(stdout.read()) | |
return self.resultlist | |
def get(self,remote_path,local_path): | |
try: | |
self.sftp.get(remote_path,local_path) | |
return local_path | |
except Exception as e: | |
return print("ERROR: %s" % e) | |
def put(self,local_path,remote_path): | |
try: | |
self.sftp.get(local_path,remote_path) | |
return self.sftp.lstat(remote_path) | |
except Exception as e: | |
return print("ERROR: %s" % e) | |
def close(self): | |
self.sftp.close() | |
self.transport.close() | |
if __name__ == '__main__': | |
host_data={ | |
'ip':'127.0.0.1', | |
'port':22, | |
'user':"root", | |
'passwd':'xxx', | |
} | |
host=ssh(host_data) | |
print(host.command('ls -l').decode('utf-8')) | |
print(host.get("./index.php",r"C:\")) |
# 系统交互
# os/subprocess
有些方法一看就懂,就不多赘述,参数自行体会
os.getcwd() //pwd | |
os.chdir() //cd | |
os.listdir() | |
os.mkdir() //raise FileExistsError | |
os.rmdir() //raise OSError ,如果目录不为空 | |
os.remove() //raise FileNotFoundError | |
os.rename(src,dst) | |
os.getenv() | |
os.system() | |
#other------ | |
os.access(path,mode)//mode包括os.F_OK(存在),os.R_OK/W_OK/X_OK(rwx),返回布尔值 | |
os.chmod(path,mode)//import stat,mode自己搜有什么 | |
os.chwon(path,uid,gid) | |
os.chroot(path) //改变当前进程目录 |
subprocess 用于创建和管理子进程,比起 os.system/popen () 可以精细调控
res=subprocess.run(['ls','-l'],capture_output=True,text=True) | |
#text 项自动处理编码,否则应传入字节流,并且输出也是字节流 | |
#去掉 captrue_output 项会使得直接输出直接到终端 | |
print(res.stdout) |
subprocess.run(['grep','python'],input='hello\npython\nworld',text=True) | |
#相当于 | |
grep 'python' | |
hello | |
python | |
world | |
#或者 | |
echo -e "hello\npython\nworld" | grep 'python' |
subprocess 还可以处理子进程的错误,因为当返回非 0 时,会抛出 CalledProcessError 错误,可以通过 res.returncode 获取退出码
import subprocess | |
try: | |
res=subprocess.run(['ls','NOT EXISTS FILE'],capture_output =True,text=True,check=True) | |
#check 的作用是在失败 (非 0 退出码) 时强制抛出错误 CalledProcessError | |
except subprocess.CalledProcessError as e: | |
print(f"Command Not Found:{e.stderr}{e.returncode}") |
使用更为底层的接口,并且它是非阻塞的,所以需要下面的 while 循环:
#启动子进程 | |
process=subprocess.Popen(['ping','potatowo.top'],stdout=subprocess.PIPE,text=True)#这里将输出捕获到管道里,不直接输出 | |
while True: | |
out_put=process.stdout.readline()#逐行读取 | |
if out_put=="" and process.poll() is not None: | |
#无输出,并且子进程已经结束 | |
break | |
if out_put: | |
print(output.strip()) | |
return_code=process.poll()#poll 的返回值是退出码,未结束是 None,见上 | |
printf(f"Process finished with retuen code {return_code}") |
也支持使用管道:
p1=subprocess.Popen(['ls','-l'],stdout=subprocess.PIPE) | |
p2=subprocess.Popen(['grep','py'],stdin=p1.stdout,stdout=subprocess.PIPE,text=True) | |
out_put=p2.communicate()[0] | |
#交互式输入输出: | |
#std_out,std_err=process.communicate(input="data") |
其他:
wait() #阻塞 | |
terminate() #发送终止信号(SIGTREM) | |
kill() #强制终止(SIGKILL) | |
#以下三个都是输入 / 输出流对象,使用特定方法写入 / 读取内容 | |
stdin().write() | |
stdout().read() | |
stderr().read() |
对于 run 和 Popen,都存在以下参数:
PS:timeout 如果超时,会抛出 subprocess.TimeoutExpired
# ctypes
# winreg
# psutil
# 数据处理
# re
首先了解下正则表达式的语法:
^ 匹配字符串开头
$ 匹配字符串末尾
。匹配任意字符,未指定 re.DOTALL 时不匹配换行符
[...] 匹配这其中的任意一个字符
[^...] 匹配不再这其中的任意一个字符
re* 匹配 0 + 个表达式,也就是 * 可以是任意一个字符
re+ 匹配 1 + 个表达式,也就是到下一个表达之前中间任意
re? 匹配 0/1 个前面定义的正则表达式片段,非贪婪
re {n} 匹配前面 n 个表达式,也就是前面表达式重复次
re {n,} 精确匹配前面 n 个表达式n=0, 等同于 re {1}
n=1, 等同于 +
n>1, 除了不匹配 re {1},其他同 n=1re {n,m} n 到 m 次,贪婪
a|b 满足 a 或者 b 表达式
(re) 匹配括号内表达式,也表示一个组
(?imx) 三种可选标识,只影响括号中的部分,
(?-imx) 不使用
(?re) 类似 (re) 但是不表示组
(?imx: re) 对 re 使用标识
(?-imx: re) 不对 re 使用
(?#...) 注释
(?= re) 前向肯定,其中 re 满足时该式满足,反之有 (?! re)
更一般的:


实例:
(\d{4})-(\d{2})-(\d{2}) #2025-11-20 |
函数:
re.match(pattern,string,flags=0) | |
#从起始位置匹配一个模式,不在起始位置匹配成功也返回 None | |
re.serch(pattern,string,flags=0) | |
#返回第一个匹配到的 | |
re.findall() | |
#查询所有匹配的子串,并返回一个列表 | |
match=re.sub(pattern,"potaowo",text) | |
#替换匹配到的部分 | |
if match: | |
print(match.group) |
flag 是修饰符,包括:
re.I 忽略大小写
re.M 多行匹配,使得 ^ 和 $ 可以匹配每一行字符串
re.S/DOTALL 使。可以匹配所有字符,包括换行符
re.ASCII 使得 \w,\W,\b,\B,\d,D,\s,\S 只匹配 ASCII
re.X 忽略空格和注释组合使用时使用 | 连接
实例
#匹配电话号码 | |
pattern=r"\d{3}-\d{4}-\d{4}" | |
text="My phone number is 123-456-789" | |
match=re.search(pattern,match.group) | |
if match: | |
print(match.group) | |
#匹配邮箱 | |
pattern=r"^[a-zA-Z0-9_.+-]+@[a-zA-z0-9]+\.[a-zA-Z0-9-.]+$" | |
email="example@example.com" | |
if re.match(pattern,email): | |
print("is Email") |
此外还有其他的函数:
re.fullmatch(r'\d+','123') #整个字符串完全匹配 | |
re.finditer(r'd+','alb2') #这个返回的是迭代器 | |
re.split(r'\d+','a1b2c) #按照匹配项划分,匹配的部分删除,返回列表 |
finditer
for m in re.finditer(r'\d+','a1b2c'): print(m.group)#输出 1,2
对于返回的匹配对象,有:
group() #返回整个匹配的字符串 | |
group(n) #返回第 n 个捕获组的内容 | |
groups() #返回所有捕获组的元组 | |
start/end() #匹配的起始 / 结束位置 | |
span() #返回匹配范围 |
捕获组:
也就是上面所说的,被 () 包裹的 “一个组”
m=re.search(r'(\d)(\d)',"12");m.group(1)#返回 1
regex=re.compile(pattern,flags=0) #预编译正则表达式,生成 Pattern 对象 | |
text="xxx" | |
result=regex.findall(text) |
# json
json 全称是 JavaScript Object Notation(对象表示法)
一种轻量级的文本数据交换格式,用于描述 JavaScript 的数据对象,支持多种编程语言
一个实例:
{ | |
"site":[ | |
{"name":"土豆","url"="www.potatowo.xyz"}, | |
{"name":"番茄","url"="www.fanqie.xyz"} | |
] | |
} |
之前我在写 bot 的时候,对 api 返回的 json 出现过读取上的问题,经常不知道怎么读,尤其是在结构比较复杂的情况下(反复嵌套)
JSON 有以下基本数据类型组成:
- 对象,由 {} 包围的无序键值对,使用:分隔键值,键只能是字符串,值任意
- 数组,由 [] 包围的有序值的集合,多个值使用逗号分隔,且值本身任意
- 字符串,由 “” 包围的字符序列
- 数字
- 布尔值,true 或 false
- 空值,null
实例:
json_data={ | |
"name":"potato", | |
"age": 1000, | |
"is_potato": false, | |
"address": { | |
"street" : ["nju","xdu","pku"], | |
"city" : null | |
} | |
} |
import json | |
#读取 | |
with open('data.json','r') as file: | |
data=json.load(file) #解析为类似结构 | |
print(data) | |
#写入,文件不存在会自动创建 | |
with open('data.json','w') as file: | |
data={ | |
"name" = "patatowo" | |
} | |
json.dump(data,file) | |
#这里要与 dumps 区分开来,dumps 是将对对应的结构转换成字符串,是类似于序列化的过程 | |
json_string=json.dumps(data) |
对于像是上面相对复杂的数据结构
name=json_data["name"] | |
address_street_1=json_data["address"]["street"][1] | |
#修改嵌套元素同理 |
处理异常:
FileNotFoundError/json.JSONDecodeError (解析错误)
try: with open('xxx.json','r'): data=json.load(file); except FileNotFoundError: print("NOT Found") except json.JSONDecodeError as e: print(e)
# xml
XML 是可扩展标记语言(eXtensiable Markup Language),标准通用标记语言的子集,用于标记电子文件使其具有结构性的标记语言,用于传输和存储数据
在 python 中由三种方法解析:ElementTree,SAX,DOM,只介绍第一种
import xml.etree.ElementTree as ET | |
xml_string = '<root><element>Some data</element></root>' | |
root=ET.fromstring(xml_string) | |
#从文件中读取 | |
tree=ET.parse('example.xml') | |
root = tree.getroot() |
遍历 XML 树:
title=root.find('title') #只找到具有该标签的第一个元素 | |
book=root.findall('book') |
访问元素的属性或者文本
price=book.attrib['price'] | |
title_text=title.text |
创建 / 修改 XML
new_element=ET.Element('new')#创建新元素 | |
new_sub_element=ET.SubElement(root,'new_sub') | |
#创建具有指定标签的子元素 | |
root.move(title) |
实例:
xml=''' | |
<bokstore> | |
<book> | |
<title>POTATOWO</title> | |
<author>POTATO</author> | |
<price>$114514</price> | |
</book> | |
<book> | |
<title>POTATOWO2</title> | |
<author>POTATO2</author> | |
<price>$514114</price> | |
</book> | |
</bookstore>''' | |
root=ET.fromstring(xml_string) | |
for book in root.findall('book'): | |
title=book.find('title').text | |
author=book.find('author').text | |
price=book.find('author').text | |
print(f"result:{title}{author}{price}") |
反过来,创建上面的 xml 文件:
root=ET.Element('bookstore') | |
#构建 book1 | |
book1=ET.SubElement(root,'book') | |
title1=ET.SubElement(book1,'title') | |
title1.text='POTATOWO' | |
author1=ET.SubElement(book1,'author') | |
price1=ET.SubElement(book1,'price') | |
price1.text='$114514' | |
#book2 类似 | |
... | |
#保存 | |
tree=ET.ElementTree(root) | |
tree.write('books.xml') | |
#反过来解析 | |
parse_tree=ET.parse('books.xml') | |
parse_root=parse_tree.getroot() | |
#下面类上 | |
... |
# base64/hashlib
这两个只是过一过,感觉相对来说不是那么重要(?)
base64
import base64 | |
#编码 | |
data=b'xxx' | |
encode=base64.b64encode(data) #只接受 bytes 类型的数据 | |
print(encode.decode()) #上面的返回的也是 bytes,这里 decode 转回 str | |
#解码 | |
data=b'xxx' | |
decode=base64.b64decode(data) | |
print(decode.decode()) |
有时会用到 url 安全的 base64 编码(之前有遇到 JWT 就是使用这个的),使用:
base64.urlsafe_b64encode(data) | |
base64.urlsafe_b63ddecode(data) |
你可以用它对文本,JSON 数据,或者图片进行加密
data={"name"="potatowo","password"=base64.b64.encode(b'passwd')} | |
with open('data.json') as file: | |
data=json.load(file) | |
data=base64.b64encode(data["data"]) | |
with open("decoded_image.png","wb") as img_out: | |
img_out.write(base64.b64decode(encoded_img)) |
hashlib
import hashlib | |
sha256_hash=hashlib.new('sha256') | |
sha256_hash.update(b'POTATOWO') #更新对应对象的内容 | |
print(sha256_hash.hexdigest())#获取对应 16 进制表示的哈希值 |
也可以直接用对应算法名创建对象
md5_hash=hashlib.md5(b'POTATOWO') | |
print(md5_hash.digest())#获取对应的二进制表达式 |
常见的算法有:
MD5,SHA-1,SHA-256,SHA-512
# argparse
用于解析命令行参数,对于简单的任务并不需要用到它:
import sys | |
print(sys.argv) | |
source = sys.argv[1] |
但是显然太过简陋
import argparse | |
def main(): | |
parser=argparse.ArgumentParser( | |
prog='666', #程序名 | |
description='test', #描述 | |
epilog='Copyright(r), 2025', #说明 | |
) | |
#定义位置参数 | |
parser.add_argument('outfile') | |
parser.add_argument('--host',default='localhost') | |
parser.add_argument('--port',default='3306',type=int) | |
parser.add_argument('-u',required=True,help='666') | |
parser.add_argument('-g','--gz',action='store_true', required=False) #对于无需参数值的指定 | |
#解析参数,其中 - h 参数是默认的,输出 help,无需手动设定。使用时无需错误处理,参数不对会自动退出 | |
args=parser.parse_args() | |
if __name__ == '__main__' : | |
main() |
# 随便看看
# tkinker
python 的一个图形开发界面库:
有四个要素:
- 窗口:外层容器
- 控件:标签,输入框,按钮等
- 布局:如何排列
- 事件:点击后发生的事件
一个最简单的 helloworld
import tkinter as tk | |
from tkinter import messagebox | |
def greet(): | |
name=entry.get() | |
if name.strip(): | |
messagebox.showinfo("greet",f"Hello,{name}!") | |
else: | |
messagebox.showwarning("Input Error","Please enter your name") | |
root=tk.Tk() | |
root.title("Hello Tkinter App") | |
root.geometry("400x200") | |
label=tk.Label(root,text="Enter your name:",font=("Arial",12))#字体设置只能用元组 | |
label.pack(pady=10) | |
entry=tk.Entry(root,width=30) | |
entry.pack(pady=5) | |
button=tk.Button(root,text="Say Hello",command=greet)#command 是控件函数,只传函数名 | |
button.pack(pady=10) | |
root.mainloop() #进入消息循环,必须放在最后 |
效果类似:

小操作:
#软件图标 | |
root.iconbitmap('app.ico') | |
跨平台: | |
from PIL import Image,ImageTk | |
image=Image.open('ico.png') | |
photo=ImageTk.PhotoImage(image) | |
root.iconphoto(Flase,photo) | |
#窗口正中央 | |
def center_window(window,width,height): | |
screen_width=window.winfo_screenwidth() | |
screen_height=window.winfo_screenheight() | |
x=(screen_width//2) - (width//2) | |
y=(screen_height//2) - (height//2) | |
window.geometry(f"{width}x{height}+{x}+{y}") | |
center_window=(root,600,400) | |
#毛玻璃 | |
root.attributes("-alpha",0.9)#90% 不透明度 | |
#退出确认 | |
def on_closing(): | |
if messagebox.askokcancel("退出确认","你确认要退出嘛?"): | |
print("正在清理资源") | |
root.destory() | |
root.protocol("WM_DELETE_WINDOW",on_closing)#拦截关闭事件 | |
def safe_exit(): | |
response=messagebox.askyesorncancel( | |
title="save?", | |
message="save", | |
icon="warning" | |
) | |
if response is True: | |
save_file() | |
root.destroy() | |
elif response is False: | |
root.destory() | |
elif reponse is None: | |
pass #取消关闭 |
事件处理的流程大致是:
操作系统事件 -> 事件队列 ->A:mainloop () 检测 ->GUI 事件?查找绑定回调:忽略或者默认处理 -> 执行用户定义函数 -> 更新界面 -> 回到 A显然 GUI 程序都是事件驱动的
# 控件
Label
变量绑定和动态刷新
text=tk.StringVar(value="defaut_value") | |
label=tk.Lable(root,textvariable=text) | |
text.set("new_value") #新值 |

插入图片:

Button/command
传递参数:
btn= tk.Buttom(root,text="666:",command=lamnda: greet(“dd”))#显然这里是依赖注入 | |
from fuctools import partial | |
btn= tk.Buttom(root,text="666:",command=partial(delete_item,item_id)) |
Entry
passwd=tk.Entry(root,show="*")#隐藏密码 | |
vcmd=(root.register(only_numbers),"%S") | |
entry=tk.Entry(root,validate="key",validatecommand=vcmd)#限制输入为数字 |

方法:

以及校验:

其中 validate 的值:
- focus : 获得或者失去焦点时验证
- focusin/out : 得到 / 失去焦点时验证
- key : 被编辑时验证
- all : 以上都要
- none : 默认值,不验证
invalidcommand 在 validcommand 返回 False 时执行
Text
text_area = tk.Text(undo=True,maxundo=50)#后悔药 | |
scroll=tk.Scrollbar(root,orient="vertical",command=text_area.yview)#滑条 | |
text_area.config(yscrollcommand=scroll.set) | |
text_widget.see(tk.END) #自动滚到底 |

# 布局
pack
用于线性排列,会自动排布
main_area.pack(expand=True,fill="both")#fill,占据所有剩余空间,缩放时会自动调制大小 |

grid
精确排布,用于做表单,要注意分配权重
root.grid_columnconfigure(1,weight=1) | |
#权重也就是 0-1,这样也会随着缩放自动调整 |
混合布局
不建议混用布局器,应当使用 Frame 划分区域
left=tk.Frame(root) | |
left.pack(side="right",fill="y") | |
right=tk.Frame(root) | |
right.pack(side="right",expand=True,fill='both')#占据剩余空间 |
