Python深入和提高 异常机制 异常的本质-调试错误的核心理念
try_except基本结构 try…一个except结构
try…except是最常见的异常处理结构。结构如下:
1 2 3 4 try : 被监控可能发生引起异常的语句块 except BaseException [as e]: 异常处理语句
try
块包含着可能引发异常的代码,except
块则用来捕捉和处理发生的异常。
执行的时候,如果try
块中没有引发异常,则跳过except
块继续执行后续代码;
执行的时候,如果try
块中发生了异常,则跳过try
块中的后续代码,跳到相应的except
块中处理异常;异常处理完后,继续执行后续代码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 try : print ("step1" ) a = 2 / 0 print ("step2" ) except BaseException as e: print ("step3" ) print (e) print ("step4" )""" step1 step3 division by zero step4 """
try_多个except结构 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 try : a = input ("请输入被除数:" ) b = input ("请输入除数:" ) c = float (a)/float (b) print (c) except ZeroDivisionError: print ("异常,除数不能为0" ) except TypeError: print ("异常:除数和被除数都应该为数值类型" ) except BaseException as e: print (e) print (type (e)) """ 请输入被除数:1 请输入除数:dd could not convert string to float: 'dd' <class 'ValueError'> """
try…except…else结构 try..except...else
结构增加了else
块。如果try
块中没有抛出异常,则执行else
块。如果try
块中抛出异常,则执行except
块,不执行else
块。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 try : a = input ("请输入被除数:" ) b = input ("请输入除数:" ) c = float (a)/float (b) print (c) except BaseException as e: print (e) print (type (e)) else : print ("除的结果为:" , c) """ 请输入被除数:3 请输入除数:4 0.75 除的结果为: 0.75 """
try…except…finally结构和return语句位置 try...except..finally
结构中,finally
块无论是否发生异常都会被执行;通常用来释放try
块中申请的资源。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 try : a = input ("请输入被除数:" ) b = input ("请输入除数:" ) c = float (a)/float (b) print (c) except BaseException as e: print (e) print (type (e)) else : print ("除的结果为:" , c) finally : print ("我是finally语句,无论是否发生异常我都会被执行" ) """ 请输入被除数:3 请输入除数:0 float division by zero <class 'ZeroDivisionError'> 我是finally语句,无论是否发生异常我都会被执行 """
return语句和异常处理问题
由于return
有两种作用:结束方法运行、返回值。我们一般不把return
放到异常处理结构中,而是放到方法最后。
常见异常汇总说明
异常名称
说明
ArithmeticError
所有数值计算错误的基类
AssertionError
断言语句失败
AttributeError
对象没有这个属性
BaseException
所有异常的基类
DeprecationWarning
关于被弃用的特征的警告
EnvironmentError
操作系统错误的基类
EOFError
没有内建输入,到达EOF 标记
Exception
常规错误的基类
FloatingPointError
浮点计算错误
FutureWarning
关于构造将来语义会有改变的警告
GeneratorExit
生成器(generator)发生异常来通知退出
ImportError
导入模块/对象失败
IndentationError
缩进错误
IndexError
序列中没有此索引(index)
IOError
输入/输出操作失败
KeyboardInterrupt
用户中断执行(通常是输入^C)
KeyError
映射中没有这个键
LookupError
无效数据查询的基类
MemoryError
内存溢出错误(对于Python 解释器不是致命的)
NameError
未声明/初始化对象 (没有属性)
NotImplementedError
尚未实现的方法
OSError
操作系统错误
OverflowError
数值运算超出最大限制
OverflowWarning
旧的关于自动提升为长整型(long)的警告
PendingDeprecationWarning
关于特性将会被废弃的警告
ReferenceError
弱引用(Weak reference)试图访问已经垃圾回收了的对象
RuntimeError
一般的运行时错误
RuntimeWarning
可疑的运行时行为(runtime behavior)的警告
StandardError
所有的内建标准异常的基类
StopIteration
迭代器没有更多的值
SyntaxError
Python 语法错误
SyntaxWarning
可疑的语法的警告
SystemError
一般的解释器系统错误
SystemExit
解释器请求退出
TabError
Tab 和空格混用
TypeError
对类型无效的操作
UnboundLocalError
访问未初始化的本地变量
UnicodeDecodeError
Unicode 解码时的错误
UnicodeEncodeError
Unicode 编码时错误
UnicodeError
Unicode 相关的错误
UnicodeTranslateError
Unicode 转换时错误
UserWarning
用户代码生成的警告
ValueError
传入无效的参数
Warning
警告的基类
WindowsError
系统调用失败
ZeroDivisionError
除(或取模)零 (所有数据类型)
with上下文管理资源
finally
块由于是否发生异常都会执行,通常我们放释放资源的代码。其实,我们可以通过with
上下文管理,更方便的实现释放资源的操作。
with
上下文管理的语法结构如下:
1 2 with context_expr [ as var]: 语句块
with
上下文管理可以自动管理资源,在with
代码块执行完毕后自动还原进入该代码之前的现场或上下文。不论何种原因跳出with
块,不论是否有异常,==总能保证资源正常释放==。极大的简化了工作,在文件操作、网络通信相关的场合非常常用。
1 2 3 with open ("d:/test.txt" ) as f: context = f.readline() print (context)
traceback模块的使用-异常写入日志文件
1 2 3 4 5 6 7 import tracebacktry : print ("step1" ) num = 1 / 0 except : traceback.print_exc()
1 2 3 4 5 6 7 8 9 import tracebacktry : print ("step1" ) num = 1 / 0 except : with open ("d:/test.txt" , "a" ) as f: traceback.print_exc(file=f)
自定义异常-raise抛出异常
程序开发中,有时候我们也需要自己定义异常类。自定义异常类一般都是运行时异常,通常继承Exception
或其子类即可。命名一般以Error
、Exception
为后缀。
自定义异常由raise
语句主动抛出。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 class AgeError (Exception ): def __init__ (self, errorinfo ): Exception.__init__(self) self.errorinfo = errorinfo def __str__ (self ): return str (self.errorinfo) + ",年龄错误,应该在1-150之间" if __name__ == "__main__" : age = int (input ("输入一个年龄:" )) if age < 1 or age > 150 : raise AgeError(age) else : print ("正常的年龄:" , age) """ 输入一个年龄:170 Traceback (most recent call last): File "D:\2022百战Python\Python深入和提高\异常机制\practice.py", line 86, in <module> raise AgeError(age) __main__.AgeError: 170,年龄错误,应该在1-150之间 """
Pycharm调试模式debug视图
文件处理 fle文件操作-操作系统底层关系-写入文件
Python标准库中,如下是文件操作相关的模块:
名称
说明
io模块
文件流的输入和输出操作 input output
os模块
基本操作系统功能,包括文件操作
glob模块
查找符合特定规则的文件路径名
fnmatch模块
使用模式来匹配文件路径名
fileinput模块
处理多个输入文件
filecmp模块
用于文件的比较
csv模块
用于csv文件处理
pickle和cPickle
用于序列化和反序列化
xml包
用于XML数据处理
bz2、gzip、zipfile、zlib、tarfile
用于处理压缩和解压缩文件(分别对应不同的算法)
创建文件对象open()
open0函数用于创建文件对象,基本语法格式如下:
open(文件名[打开方式])
如果只是文件名,代表在当前目录下的文件。文件名可以录入全路径,比如:D:\a\b.txt
为了减少\
的输入,可以使用原始字符串:r "d:\b.txt”
示例如下:
f = open(r"d:\b.txt", "w")
打开方式有如下几种:
模式
描述
r
读read模式
w
写wite模式。如果文件不存在则创建;如果文件存在,则重写新内容
a
追加append模式。如果文件不存在则创建;如果文件存在,则在文件末尾追加内容
b
二进制binary模式(可与其他模式组合使用)
+
读、写模式(可与其他模式组合使用)
文本文件对象和二进制文件对象的创建:
如果没有增加模式b
,则默认创建的是文本文件对象 ,处理的基本单元是“字符”。
如果是二进制模式b
,则创建的是二进制文件对象,处理的基本单元是“字节”。
基本的文件写入操作
文本文件的写入一般就是三个步骤:
创建文件对象
写入数据
关闭文件对象
1 2 3 f = open (r"d:\test.txt" , "a" ) f.write("hello world" ) f.close()
1 2 with open (r"d:\test.txt" , "a" ) as f: f.write("hello world" )
编码知识-中文乱码问题解决
1 2 3 with open (r"d:\test.txt" , "a" , encoding="utf-8" ) as f: f.write("你好" )
write()/writelines()写入数据
write(a)
:把字符串a写入到文件中
writelines(b)
:把字符串列表事入文件中,不添加换行符
1 2 3 4 with open (r"d:\test.txt" , "w" , encoding="utf-8" ) as f: s = ["年龄\n" , "姓名\n" , "学校\n" ] f.writelines(s)
关闭流要点
由于文件底层是由操作系统控制,所以我们打开的文件对象必须显式调用 close()
方法关闭文件对象。当调用close()
方法时,首先会把缓冲区数据写入文件(也可以直接调用flush()
方法),再关闭文件,释放文件对象。
为了确保打开的文件对象正常关闭,一般结合异常机制的finally
或者with
关键字实现无论何种情况都能关闭打开的文件对象。
finally异常管理 1 2 3 4 5 6 7 8 9 try : f = open (r"d:\test.txt" , "w" ) s = "hello world" f.write(s) except BaseException as e: print (e) finally : f.close()
with上下文管理 1 2 3 4 s = ["年龄\n" , "姓名\n" , "学校\n" ] with open (r"d:\test.txt" , "w" , encoding="utf-8" ) as f: f.writelines(s)
文本文件的读取 文件的读取一般使用如下三个方法:
read([size])
从文件中读取size
个字符,并作为结果返回。如果没有size
参数,则读取整个文件。
读取到文件末尾,会返回空字符串。
1 2 3 4 5 6 7 8 9 10 11 12 13 with open (r"d:\test.txt" , "r" , encoding="utf-8" ) as f: s = f.read(6 ) print (s) s2 = f.read() print ("第二次读的:" , s2) """ 年龄 姓名 第二次读的: 学校 """
readline()
读取一行内容作为结果返回。读取到文件末尾,会返回空字符串
1 2 3 4 5 6 7 with open (r"d:\test.txt" , "r" , encoding="utf-8" ) as f: s = f.readline() print (s) """ 年龄 """
readlines()
文本文件中,每一行作为一个字符串存入列表中,返回该列表
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 with open (r"d:\test.txt" , "r" , encoding="utf-8" ) as f: s = f.readlines() print (s) """ ['年龄\n', '姓名\n', '学校\n'] """ with open (r"d:\test.txt" , "r" , encoding="utf-8" ) as f: for line in f: print (line, end="" ) """ 年龄 姓名 学校 """ with open (r"d:\test.txt" , "r" , encoding="utf-8" ) as f: while True : line = f.readline() if not line: break else : print (line, end="" ) """ 年龄 姓名 学校 """
文本文件操作-为每行的末尾添加行号 1 2 3 4 5 6 7 8 9 10 11 12 13 14 with open (r"d:\test.txt" , "r" , encoding="utf-8" ) as f: lines = f.readlines() print (lines) lines2 = [lines.rstrip() + "#" + str (index) + "\n" for index, lines in zip (range (1 , len (lines) + 1 ), lines)] print (lines2) with open (r"d:\test.txt" , "w" , encoding="utf-8" ) as f: f.writelines(lines2) """ ['年龄\n', '姓名\n', '学校\n'] ['年龄#1\n', '姓名#2\n', '学校#3\n'] """
二进制文件的读写-图片文件的拷贝 二进制文件的处理流程和文本文件流程一致。首先还是要创建文件对象,不过,我们需要指定二进制模式,从而创建出二进制文件对象。例如:
f=open(r"d:\a.txt",wb)
#可写的、重写模式的二进制文件对象
f=open(r"d\a.txt",'ab')
#可写的、追加模式的二进制文件对象
f=open(r"d:\a.txt",'rb')
#可读的二进制文件对象
创建好二进制文件对象后,仍然可以使用wite()
、read()
实现文件的读写操作。
1 2 3 with open (r"src.png" , "rb" ) as srcFile, open (r"dist.png" , "wb" ) as distFile: for line in srcFile: distFile.write(line)
文件对象常用方法和属性总结-seek()任意位置操作 文件对象的属性
属性
说明
name
返回文件名字
mode
返回文件的打开模式
closed
若文件被关闭,则返回True
文件对象的打开方式
属性
说明
r
读模式
w
写模式
a
追加模式
b
二进制模式(可与其他模式组合)
+
读写模式(可与其他模式组合)
文件对象的常用方法
方法名
说明
read([size])
从文件中读取size个字节或字符的内容返回。若省略[size],则读取到文件末尾,即一次读取文件所有内容
readline()
从文本文件中读取一行内容
readlines()
把文本文件中每一行都作为独立的字符串对象,并将这些对象放入列表返回
write(str)
将字符串str内容写入文件
writelines(s)
将字符串列表s写入文件文件,不添加换行符
seek(offset [,whence])
把文件指针移动到新的位置,offset表示相对于whencet的多少个字节的偏移量。offset: off为正往结束方向移动,为负往开始方向移动 whence不同的值代表不同含义:0:从文件头开始计算(默认值)1:从当前位置开始计算2:从文件尾开始计算
tell()
返回文件指针的当前位置
truncate([size])
不论指针在什么位置,只留下指针前sz个字节的内容,其余全部删除;如果没有传入size,则当指针当前位置到文件末尾内容全部删除
flush()
把缓冲区的内容写入文件,但不关闭文件
close()
把缓冲区内容写入文件,同时关闭文件,释放文件对象相关资源
使用pickle实现序列化和反序列化
序列化指的是:将对象转化成“串行化”数据形式,存储到硬盘或通过网络传输到其他地方。
反序列化是指相反的过程,将读取到的“串行化数据”转化成对象。
我们可以使用pickle模块中的函数,实现序列化和反序列操作。
Python中,一切皆对象,对象本质上就是一个“存储数据的内存块”。有时候,我们需要将“内存块的数据”保存到硬盘上,或者通过网络传输到其他的计算机上。这时候,就需要“对象的序列化和反序列化”。对象的序列化机制广泛的应用在分布式、并行系统上。
序列化我们使用:
pickle.dump(obj, file)
obj
就是要被序列化的对象,file
指的是存储的文件
pickle.load(file)
从file
读取数据,反序列化成对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 import picklewith open ("data.dat" , "wb" ) as f: name = 'jack' age = 20 sorce = [70 , 80 , 90 ] resume = {'name' : name, 'age' : age, 'sorce' : sorce} pickle.dump(resume, f) with open ("data.dat" , "rb" ) as f: resume2 = pickle.load(f) print (resume2) """ {'name': 'jack', 'age': 20, 'sorce': [70, 80, 90]} """
csv文件的读取和写入 csv是逗号分隔符文本格式,常用于数据交换、Ec文件和数据库数据的导入和导出。
与excel文件不同,csv文件中:
值没有类型,所有值都是字符串
不能指定字体颜色等样式
不能指定单元格的宽高,不能合并单元格
没有多个工作表
不能嵌入图像图表
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 import csv headers = ["年龄" , "姓名" ] rows = [(20 , 'jack' ), (40 , 'john' )] with open ("data.csv" , "w" ) as f: b_csv = csv.writer(f) b_csv.writerow(headers) b_csv.writerows(rows) with open ("data.csv" ) as f: a_csv = csv.reader(f) headers = next (a_csv) for row in a_csv: print (row)
os模块
os模块可以帮助我们直接对操作系统进行操作。我们可以直接调用操作系统的可执行文件、命令,直接操作文件、目录等等。
:warning:os模块是做系统运维非常重要的基础。
调用操作系统可执行文件-控制台乱码问题
控制台乱码问题
获取文件信息-创建和删除文件夹 我们可以通过前面讲的文件对象实现对于文件内容的读写操作。如果,还需要对文件和目录做其他操作,可以使用os
和os.path
模块。
os
模块下常用操作文件的方法
方法名
描述
remove(path)
删除指定的文件
rename(src,dest)
重命名文件或目录
stat(path)
返回文件的所有属性
listdir(path)
返回path目录下的文件和目录列表
os
模块下关于目录操作的相关方法,汇总如下:
方法名
描述
mkdir(path)
创建目录
makedirs(path1/path2/path3/…)
创建多级目录
rmdir(path)
删除目录
removedirs(path1/path2…)
删除多级目录
getcwd()
返回当前工作目录:current work dir
chdir(path)
把path设为当前工作目录
walk()
遍历目录树
sep
当前操作系统所使用的路径分隔符
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 import osprint (os.name) print (os.sep) print (repr (os.linesep)) a = '3' print (a)print (repr (a)) print (os.stat("practice.py" ))print (os.getcwd()) os.chdir("d:" ) os.mkdir("test" ) os.rmdir("test" ) os.makedirs("test1/test2/test3" ) os.rename("test" , "测试" ) dirs = os.listdir("test1" ) print (dirs)
os.path模块-常用方法 os.path模块提供了目录相关(路径判断、路径切分、路径连接、文件夹遍历)的操作
方法
描述
isabs(path)
判断path是否绝对路径
isdir(path)
判断path是否为目录
isfile(path)
判断path是否为文件
exists(path)
判断指定路径的文件是否存在
getsize(filename)
返回文件的大小
abspath(path)
返回绝对路径
dirname(p)
返回目录的路径
getatime(filename)
返回文件的最后访问时间
getmtime(filename)
返回文件的最后修改时间
walk(top,func,arg)
递归方式遍历目录
join(path,*paths)
连接多个path
split(path)
对路径进行分割,以列表形式返回
splitext(path)
从路径中分割文件的扩展名
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 import os.pathpath = os.getcwd() file_list = os.listdir(path) for file_name in file_list: pos = file_name.rfind("." ) if file_name[pos + 1 :] == "py" : print (file_name) """ practice.py """ print ("*********************************" )file_list2 = [file_name for file_name in os.listdir(path) if file_name.endswith(".py" )] print (file_list2)""" ['practice.py'] """
使用walk递归遍历所有子目录和子文件 os.walk()方法是一个简单易用的文件、目录遍历器,可以帮助我们高效的处理文件、目录方面的事情。格式如下:
os.walk(top[,topdown=True[,onerror=None[,followlinks=False]]])
其中,top
:是要遍历的目录。topdown
:可选,True
, 先遍历top
目录再遍历子目录。
返回三元组(root、dirs、files)
:
root
: 当前正在遍历的文件夹本身
dirs
: 一个列表,该文件夹中所有的目录的名字
files
: 一个列表,该文件夹中所有的文件的名字
1 2 3 4 5 6 7 8 9 10 import ospath = os.getcwd() list_files = os.walk(path, topdown=False ) for root, dirs, files in list_files: for name in files: print (os.path.join(root, name)) for name in dirs: print (os.path.join(root, name))
shutil模块 shutil模块(拷贝和压缩) shutil
模块是python标准库 中提供的,主要用来做文件和文件夹的拷贝、移动、删除 等;还可以做文件和文件夹的压缩、解压缩 操作。
os
模块提供了对目录或文件的一般操作。shutil
模块作为补充,提供了移动、复制、压缩、解压等操作,这些os模块都没有提供。
1 2 3 4 5 import shutilshutil.copyfile("test.txt" , "test_copy.tet" ) shutil.copytree("source" , "destination" , ignore=shutil.ignore_patterns("*.html" , "*.htm" ))
shutil和zipfile模块-压缩和解压缩 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 import shutilimport zipfileshutil.make_archive("d:/destination" , "zip" , "d:/source" ) z = zipfile.ZipFile("test.zip" , "w" ) z.write("a.txt" ) z.write("b.txt" ) z.write("c.txt" ) z.close() z2 = zipfile.ZipFile("test.zip" , "r" ) z2.extractall("d:/" ) z2.close()
递归算法原理-自己复习前面讲过的算法原理 递归算法-目录树结构的展示 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 import osimport os.pathdef my_print_file (path, level ): child_files = os.listdir(path) for file in child_files: file_path = os.path.join(path, file) print ("\t" * level + file_path[file_path.rfind(os.sep) + 1 :]) if os.path.isdir(file_path): my_print_file(file_path, level + 1 ) my_print_file("test1" , 0 ) """ test2 test3 test.txt """
模块 模块化编程理念-什么是模块-哲学思想
Python程序由模块组成。一个模块对应python源文件,一般后缀名是:.py
模块由语句组成。运行Python程序时,按照模块中语句的顺序依次执行
语句是Python程序的构造单元,用于创建对象、变量赋值、调用函数、控制语句等
标准库模块(standard library)
与函数类似,模块也分为标准库模块和用户自定义模块。
Python标准库提供了操作系统功能、网络通信、文本处理、文件处理、数学运算等基本的功能。比如:random(随机数)、math(数学运算)、time(时间处理)、file(文件处理)、os(和操作系统交互)、sys(和解释器交互)等。
另外,Pythor还提供了海量的第三方模块,使用方式和标准库类似。功能覆盖了我们能想象到的所有领域,比如:科学计算、WEB开发、大数据、人工智能、图形系统等。
为什么需要模块化编程
模块(module)对应于Python源代码文件(.py文件)。模块中可以定义变量、函数、类、普通语句。这样,我们可以将一个Pythona程序分解成多个模块,便于后期的重复应用。
模块化编程(Modular Programming)将一个任务分解成多个模块。每个模块就像一个积木一样,便于后期的反复使用、反复搭建。
模块化编程有如下几个重要优势:
便于将一个任务分解成多个模块,实现团队协同开发,完成大规模程序
实现代码复用。一个模块实现后,可以被反复调用
可维护性增强
模块化编程的流程-设计和实现分离的思想 模块化编程的流程
模块化编程的一般流程:
①设计API,进行功能描述。
②编码实现API中描述的功能。
③在模块中编写测试代码,并消除全局代码。
④使用私有函数实现不被外部客户端调用的模块函数。
模块的API和功能描述要点
API(Application Programming Interface应用程序编程接口)是用于描述模块中提供的函数和类的功能描述和使用方式描述。
模块化编程中,首先设计的就是模块的AP!(即要实现的功能描述),然后开始编码实现API中描述的功能。最后,在其他模块中导入本模块进行调用。
可以通过help(模块名)查看模块的API。一般使用时先导入模块然后通过help函数查看。
【示例】设计计算薪水模块的API
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 """ 本模块是用来计算公司员工的薪资 """ company = "ailbaba" def yearSalary (monthSalary ): """ 根据传入的月薪,计算年薪 :param monthSalary: 月薪 :return: 年薪 """ pass def daySalary (monthSalary ): """ 根据传入的月薪,计算日薪(按照一个月22.5天计算) :param monthSalary: 月薪 :return: 日薪 """ pass import salaryprint (salary.__doc__)print (salary.yearSalary.__doc__)
模块的创建和测试代码
每个模块都有一个名称,通过特殊变量__name__
可以获取模块的名称。在正常情况下,模块名字对应源文件名。仅有一个例外,就是当一个模块被作为程序入口时(主程序、交互式提示符下) ,它的__name__
的值为__main__
。我们可以根据这个特点,将模块源代码文件中的测试代码进行独立的处理。例如:
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 """ 本模块是用来计算公司员工的薪资 """ company = "ailbaba" def yearSalary (monthSalary ): """ 根据传入的月薪,计算年薪 :param monthSalary: 月薪 :return: 年薪 """ return monthSalary * 12 def daySalary (monthSalary ): """ 根据传入的月薪,计算日薪(按照一个月22.5天计算) :param monthSalary: 月薪 :return: 日薪 """ return monthSalary / 22.5 if __name__ == "__main__" : print (yearSalary(6000 ))
模块文档字符串和API设计
我们可以在模块的第一行增加一个文档字符串,用于描述模块的相关功能。然后,通过__doc__
可以获得文档字符串的内容。
模块导入-import和from_import详解和区别 模块化设计的好处之一就是“代码复用性高”。写好的模块可以被反复调用,重复使用。模块的导入就是“在本模块中使用其他模块”。
import语句导入
import
语句的基本语法格式如下:
1 2 3 import 模块名 import 模块1 , 模块2 import 模块名 as 模块名
import
加载的模块分为四种类型:
使用python编写的代码.py
文件
已被编译为共享库或DLL
的C或C++扩展
一组模块的包
使用C编写并链接到python解释器的内置模块
我们一般通过import
语句实现模块的导入和使用,import本质上是使用了内置函数__import__()
。
当我们通过import
导入一个模块时,python解释器进行执行,最终会生成一个对象,这个对象就代表了被加载的模块。
1 2 3 4 5 6 7 8 9 10 11 12 import math as mprint (id (m))print (type (m))print (m.sqrt(4 ))""" 1630061747552 <class 'module'> 2.0 """
由上,我们可以看到math
模块被加载后,实际会生成一个module
类的对象,该对象被math
变量引用。我们可以通过math
变量引用模块中所有的内容。
我们通过import
导入多个模块,本质上也是生成多个module
类的对象而已。
有时候,我们也需要给模块起个别名,本质上,这个别名仅仅是新创建一个变量引用加载的模块对象而已。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 import math as mimport mathprint (id (m))print (type (m))print (m.sqrt(4 ))m2 = math print (type (m2))print (m2.sqrt(4 ))""" 1769170820448 <class 'module'> 2.0 <class 'module'> 2.0 """
from…import导入
Python中可以使用from...import
导入模块中的成员 。基本语法格式如下:
from 模块名 import 成员1,成员2,…
如果希望导入一个模块中的所有成员,则可以采用如下方式:
from 模块名 import *
:warning:尽量避免 from 模块名 import *
这种写法。*
它表示导入模块中所有的不是以下划线_
开头的名字都导入到当前位置。但你不知道你导入什么名字,很有可能会覆盖掉你之前已经定义的名字。而且可读性极其的差。一般生产环境中尽量避免使用,学习时没有关系。
1 2 3 4 5 6 7 8 9 10 from math import sqrt, sinprint (sqrt(9 ))print (sin(1 ))""" 3.0 0.479425538604203 """
import语句和from…importi语句的区别
import
导入的是模块。from...import
导入的是模块中的一个函数/一个类 。
如果进行类比的话,import
导入的是“文件””,我们要使用该“文件”下的内容,必须前面加“文件名称”。from...import
导入的是文件下的“内容”,我们直接使用这些内容”即可,前面再也不需要加“文件名称”了。
import加载底层原理-importlib实现动态导入 __import__()
动态导入
import
语句本质上就是调用内置函数__import__()
,我们可以通过它实现动态导入。给__import__()
动态传递不同的的参数值,就能导入不同的模块
注意:一般不建议我们自行使用`import ()导入,其行为在python.2和python.3中有差异,会导致意外错误。如果需要动态导入可以使用
importlib`模块
模块的加载问题
当导入一个模块时,模块中的代码都会被执行。不过,如果再次导入这个模块,则不会再次执行。
Python的设计者为什么这么设计?因为,导入模块更多的时候需要的是定义模块中的变量、函数、对象等。这些并不需要反复定义和执行。“只导入一次import-only-once
“就成了一种优化。
一个模块无论导入多少次,这个模块在整个解释器进程内有且仅有一个实例对象。
重新加载
有时候我们确实需要重新加载一个模块,这时候可以使用:importlib.reload()
方法
包的概念-创建包-导入包
包(package)的概念和结构
当一个项目中有很多个模块时,需要再进行组织。我们将功能类似的模块放到一起,形成了“包”。==本质上,“包”就是一个必须有__init__.py
的文件夹==。典型结构如下:
包下面可以包含“模块(module)”,也可以再包含“子包(subpackage)”。就像文件夹下面可以有文件,也可以有子文件夹一样。
Pycharm里建包:
导入包操作和本质
上一节中的包结构,我们需要导入module_AA.py
。方式如下:
import a.aa.module_AA
在使用时,必须加完整名称来引用,比如:a.aa.module_AA.fun_AA()
from a.aa import module_AA
在使用时,直接可以使用模块名。比如:module_AA.fun_AA()
from a.aa.module_AA import fun_AA
直接导入函数
在使用时,直接可以使用函数名。比如:fun_AA()
from package import item
这种语法中,item可以是包、模块,也可以是函数、类、变量。
import item1.item2
这种语法中,item必须是包或模块,不能是其他。
导入包的本质 其实是“==导入了包的__init__.py
“文件==。也就是说,import pack1
意味着执行了包pack1
下面的__init__.py
文件。这样,可以在__init__.py
中批量导入我们需要的模块,而不再需要一个个导入。
__init__.py
的三个核心作用:
①作为包的标识,不能删除。
②导入包实质是执行__init__.py
文件,可以在__init__.py
文件中做这个包的初始化、以及需要统一执行代码、批量导入
如上测试我们可以看出python的设计者非常巧妙的通过__init__.py
文件将包转成了模块的操作。
包的模糊导入 用*
导入包
import *
这样的语句理论上是希望文件系统找出包中所有的子模块,然后导入它们。这可能会花长时间等。Python解决方案是提供一个明确的包索引。
这个索引由__init__.py
定义__all__
变量,该变量为一个列表,如上例a
包下的__init__.py
中,可定义__all__= ["module_A","module_A2"]
这意味着,from sound.effects import *
会从对应的包中导入以上两个子模块
:warning:尽管提供import *
的方法,仍不建议在生产代码中使用这种写法。
PIP安装第三方库 库(Library)
Python中库是借用其他编程语言的概念,没有特别具体的定义。
模块和包侧重于代码组织,有明确的定义。库强调的是功能性,而不是代码组织。
我们通常将某个功能的“模块的集合”,称为库。
标准库(Standard Library)
Python拥有一个强大的标准库。Python语言的核心只包含数字、字符串、列表、字典、文件等常见类型和函数,而由Python标准库提供了系统管理、网络通信、文本处理、数据库接口、图形系统、ML处理等额外的功能。
Python标准库的主要功能有:
①文本处理,包含文本格式化、正则表达式匹配、文本差异计算与合并、Unicode支持,二进制数据处理等功能
②文件处理,包含文件操作、创建临时文件、文件压缩与归档、操作配置文件等功能
③操作系统功能,包含线程与进程支持、IO复用、日期与时间处理、调用系统函数、日志(logging)等功能
④网络通信,包含网络套接字,SSL加密通信、异步网络通信等功能
⑤网络协议,支持HTTP,FTP,SMTP,POP,IMAP,NNTP,XMLRPC等多种网络协议,并提供了编写网络服务器的框架
⑥W3C格式支持,包含HTML,SGML,XML的处理。
⑦其它功能,包括国际化支持、数学运算、HASH、Tkinter等
目前学过的有:random、math、time、file、os、sys等模块。
①random模块实现随机数处理
②math模块实现数学相关的运算
③time模块实现时间的处理
④file模块实现对文件的操作
⑤os模块实现和操作系统的交互
⑥sys模块实现和解释器的交互
PIP模块管理工具
pip是一个现代的,通用的Python包管理工具。提供了对Python包的查找、下载、安装、卸载的功能。
安装第三方扩展库的2种方式
第三方库有数十万种之多,以pymysql库为例讲解第三方扩展库的安装。
第一种方式:命令行下远程安装
以安装第三方pymysql库为例,在命令行提示符下输入:pip install pymysql
即可。
第二种方式:Pycharm中直接安装到项目中
在Pycharm中,依次点击:file->setting->Project 本项目名->Project Interpreter
GUI编程 GUI编程和tkinter介绍-第一个GUI程序 我们前面实现的都是基于控制台的程序,程序和用户的交互通过控制台来完成。
本章,我们将学习 GUI(Graphics User Interface),即图形用户界面编程 ,我们可以通过 python 提供的丰富的组件,快速的实现使用图形界面和用户交互。
GUI 编程类似于“搭积木”,将一个个组件(Widget)放到窗口中。如下是 windows 中的画图软件,就是一个典型的 GUI 程序
常用的 GUI 库
Tkinter
tkinter(Tk interface)是 Python 的标准 GUI 库,支持跨平台的 GUI 程序开发。tkinter适合小型的 GUI 程序编写,也特别适合初学者学习 GUI 编程。本书以 tkinter 为核心进行讲解。
wxPython
wxPython 是比较流行的 GUI 库,适合大型应用程序开发,功能强于 tkinter,整体设计框架类似于 MFC(Microsoft Foundation Classes 微软基础类库)。
PyQT
Qt 是一种开源的 GUI 库,适合大型 GUI 程序开发,PyQT 是 Qt 工具包标准的 Python 实现。我们也可以使用 Qt Desginer 界面设计器快速开发 GUI 应用程序。
基于 tkinter 模块创建 GUI 程序包含如下 4 个核心步骤:
创建应用程序主窗口对象(也称:根窗口)
在主窗口中,添加各种可视化组件,比如:按钮(Button)、文本框(Label)等。
通过几何布局管理器,管理组件的大小和位置
事件处理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 from tkinter import *from tkinter import messageboxroot = Tk() btn01 = Button(root) btn01["text" ] = "点击送花" btn01.pack() def songhua (e ): messagebox.showinfo("Message" , "送你花" ) print ("送花" ) btn01.bind("<Button-1>" , songhua) root.mainloop()
PEP8编码规范-窗口大小和位置 主窗口位置和大小
通过 geometry('wxh±x±y')
进行设置。w
为宽度,h
为高度。+x
表示距屏幕左边的距离;-x
表示距屏幕右边的距离;+y
表示距屏幕上边的距离;-y
表示距屏幕下边的距离。
1 2 3 4 5 6 from tkinter import *root = Tk() root.title("测试主窗口的位置和大小" ) root.geometry("500x400+100+200" ) root.mainloop()
GUI编程整体描述-常用组件汇总
常用组件汇总列表
Tkinter 类
名称
简介
Toplevel
顶层
容器类,可用于为其他组件提供单独的容器;Toplevel 有点类似于窗口
Button
按钮
代表按钮组件
Canvas
画布
提供绘图功能,包括直线、矩形、椭圆、多边形、位图等
Checkbutton
复选框
可供用户勾选的复选框
Entry
单行输入框
用户可输入内容
Frame
容器
用于装载其它 GUI 组件
Label
标签
用于显示不可编辑的文本或图标
LabelFrame
容器
也是容器组件,类似于 Frame,但它支持添加标题
Listbox
列表框
列出多个选项,供用户选择
Menu
菜单
菜单组件
Menubutton
菜单按钮
用来包含菜单的按钮(包括下拉式、层叠式等)
OptionMenu
菜单按钮
Menubutton 的子类,也代表菜单按钮,可通过按钮打开一个菜单
Message
消息框
类似于标签,但可以显示多行文本;后来当 Label 也能显示多行文本之后,该组件基本处于废弃状态
PanedWindow
分区窗口
该容器会被划分成多个区域,每添加一个组件占一个区域,用户可通过拖动分隔线来改变各区域的大小
Radiobutton
单选钮
可供用户点边的单选钮
Scale
滑动条
拖动滑块可设定起始值和结束值,可显示当前位置的精确值
Spinbox
微调选择器
用户可通过该组件的向上、向下箭头选择不同的值
Scrollbar
滚动条
用于为组件(文本域、画布、列表框、文本框)提供滚动功能
Text
多行文本框
显示多行文本
GUI程序的经典面向对象写法 本节程序也是 GUI 应用程序编写的一个主要结构,采用了==面向对象==的方式,更加合理的组织代码。
通过类 Application
组织整个 GUI 程序,类 Application
继承了 Frame
及通过继承拥有了父类的特性。通过构造函数__init__()
初始化窗口中的对象,通过 createWidgets()
方法创建窗口中的对象。
Frame
框架是一个 tkinter
组件,表示一个矩形的区域。Frame
一般作为容器使用,可以放置其他组件,从而实现复杂的布局。
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 """测试一个经典的GUI程序的写法,使用面向对象的方式""" from tkinter import *from tkinter import messageboxclass Application (Frame ): """一个经典的GUI程序类的写法""" def __init__ (self, master ): super ().__init__(master) self.master = master self.pack() self.createWidget() def createWidget (self ): """创建组件""" self.btn01 = Button(self) self.btn01["text" ] = "点击送花" self.btn01.pack() self.btn01["command" ] = self.songhua self.btnQuit = Button(self, text="退出" , command=root.destroy) self.btnQuit.pack() def songhua (self ): messagebox.showinfo("送花" , "送你花" ) if __name__ == '__main__' : root = Tk() root.geometry("400x100+200+300" ) root.title("一个经典的GUI程序类的测试" ) app = Application(master=None ) root.mainloop()
简单组件 Label标签-tkinter中图像正确显示全局变量写法 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 from tkinter import *from tkinter import messageboxclass Application (Frame ): """一个经典的GUI程序类的写法""" def __init__ (self, master ): super ().__init__(master) self.master = master self.pack() self.createWidget() def createWidget (self ): """创建组件""" self.label01 = Label(self, text="label test" , width=10 , height=2 , bg="black" , fg="white" ) self.label01.pack() self.label02 = Label(self, text="字体测试" , width=10 , height=2 , bg="blue" , fg="white" , font=("黑体" , 10 )) self.label02.pack() global photo photo = PhotoImage(file="../images/logo.gif" ) self.label03 = Label(self, image=photo) self.label03.pack() self.label04 = Label(self, text="这是第一行\n这是第二行\n这是第三行\n" , borderwidth=1 , relief="solid" , justify="center" ) self.label04.pack() if __name__ == '__main__' : root = Tk() root.geometry("400x250+500+300" ) root.title("Label的测试" ) app = Application(master=None ) root.mainloop()
Options选项详解-底层源码分析和阅读-可变参数和运算符重载复习 通过学习 Label 组件,我们发现可以通过 Options 设置组件的属性,从而控制组件的各种状态。比如:宽度、高度、颜色、位置等等。
我们可以通过三种方式设置 Options 选项,这在各种 GUI 组件中用法都一致。
创建对象时,使用可变参数
1 fred = Button(self, fg="red" , bg="blue" )
创建对象后,使用字典索引方式
1 2 fred["fg" ] = "red" fred["bg" ] = "blue"
创建对象后,使用 config()方法
1 fred.config(fg="red" , bg="blue" )
Button(按钮)用来执行用户的单击操作。Button 可以包含文本,也可以包含图像。按钮被单击后会自动调用对应事件绑定的方法。
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 from tkinter import *from tkinter import messageboxclass Application (Frame ): """一个经典的GUI程序类的写法""" def __init__ (self, master ): super ().__init__(master) self.master = master self.pack() self.createWidget() def createWidget (self ): """创建组件""" self.btn01 = Button(root, text="登录" , anchor=E, command=self.login) self.btn01.pack() global photo photo = PhotoImage(file="../images/start.gif" ) self.btn02 = Button(root, image=photo, command=self.login) self.btn02.pack() self.btn02.config(state="disabled" ) def login (self ): messagebox.showinfo("登陆成功" ) if __name__ == '__main__' : root = Tk() root.geometry("400x250+500+300" ) root.title("Button的测试" ) app = Application(master=root) root.mainloop()
Entry单行文本框-StringVar-登录界面设计和功实现 Entry 用来接收一行字符串的控件。如果用户输入的文字长度长于 Entry 控件的宽度时, 文字会自动向后滚动。如果想输入多行文本, 需要使用 Text 控件。
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 from tkinter import *from tkinter import messageboxclass Application (Frame ): """一个经典的GUI程序类的写法""" def __init__ (self, master ): super ().__init__(master) self.master = master self.pack() self.createWidget() def createWidget (self ): """创建组件""" self.label01 = Label(self, text="用户名" ) self.label01.pack() username = StringVar() self.entry01 = Entry(self, textvariable=username) self.entry01.pack() username.set ("admin" ) print (username.get()) print (self.entry01.get()) password = StringVar() self.entry02 = Entry(self, textvariable=password, show="*" ) self.entry02.pack() Button(root, text="登录" , anchor=E, command=self.login).pack() def login (self ): username = self.entry01.get() password = self.entry02.get() print ("去数据库对比用户名密码" ) print ("用户名:" + username) print ("密码:" + password) if username == "admin" and password == "123456" : messagebox.showinfo("登录系统" , "登陆成功" ) else : messagebox.showinfo("登录系统" , "登陆失败,用户名或密码错误" ) if __name__ == '__main__' : root = Tk() root.geometry("400x250+500+300" ) root.title("Entry 的测试" ) app = Application(master=root) root.mainloop()
Text多行文本框详解-复杂tag标记 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 """测试 Text 多行文本框组件的基本用法,使用面向对象的方式""" from tkinter import *import webbrowserclass Application (Frame ): def __init__ (self, master=None ): super ().__init__(master) self.master = master self.pack() self.createWidget() def createWidget (self ): self.w1 = Text(root, width=40 , height=12 , bg="gray" ) self.w1.pack() self.w1.insert(1.0 , "0123456789\nabcdefg" ) self.w1.insert(2.3 , "锄禾日当午,汗滴禾下土。谁知盘中餐,粒粒皆辛苦\n" ) Button(self, text="重复插入文本" , command=self.insertText).pack(side="left" ) Button(self, text="返回文本" , command=self.returnText).pack(side="left" ) Button(self, text="添加图片" , command=self.addImage).pack(side="left" ) Button(self, text="添加组件" , command=self.addWidget).pack(side="left" ) Button(self, text="通过 tag 精确控制文本" , command=self.testTag).pack(side="left" ) def insertText (self ): self.w1.insert(INSERT, ' Gaoqi ' ) self.w1.insert(END, '[sxt]' ) self.w1.insert(1.8 , "gaoqi" ) def returnText (self ): print (self.w1.get(1.2 , 1.6 )) print ("所有文本内容:\n" + self.w1.get(1.0 , END)) def addImage (self ): self.photo = PhotoImage(file="../images/logo.gif" ) self.w1.image_create(END, image=self.photo) def addWidget (self ): b1 = Button(self.w1, text='爱尚学堂' ) self.w1.window_create(INSERT, window=b1) def testTag (self ): self.w1.delete(1.0 , END) self.w1.insert(INSERT, "good good study,day day up!\n 北京尚学堂\n 百战程序员\n百度,搜一下就知道" ) self.w1.tag_add("good" , 1.0 , 1.9 ) self.w1.tag_config("good" , background="yellow" , foreground="red" ) self.w1.tag_add("baidu" , 4.0 , 4.2 ) self.w1.tag_config("baidu" , underline=True ) self.w1.tag_bind("baidu" , "<Button-1>" , self.webshow) def webshow (self, event ): webbrowser.open ("http://www.baidu.com" ) if __name__ == '__main__' : root = Tk() root.geometry("450x300+200+300" ) app = Application(master=root) root.mainloop()
利用 Tags 实现更加强大的文本显示和控制
Tags 通常用于改变 Text 组件中内容的样式和功能。你可以修改文本的字体、尺寸和颜色。另外,Tags 还允许你将文本、嵌入的组件和图片与鼠标和键盘等事件相关联。
Radiobutton 控件用于选择同一组单选按钮中的一个。Radiobutton 可以显示文本,也可以显示图像。
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 """测试 Radiobutton 组件的基本用法,使用面向对象的方式""" from tkinter import *from tkinter import messageboxclass Application (Frame ): def __init__ (self, master=None ): super ().__init__(master) self.master = master self.pack() self.createWidget() def createWidget (self ): self.v = StringVar() self.v.set ("F" ) self.r1 = Radiobutton(self, text="男性" , value="M" , variable=self.v) self.r2 = Radiobutton(self, text="女性" , value="F" , variable=self.v) self.r1.pack(side="left" ) self.r2.pack(side="left" ) Button(self, text="确定" , command=self.confirm).pack(side="left" ) def confirm (self ): messagebox.showinfo("测试" , "选择的性别:" + self.v.get()) if __name__ == '__main__' : root = Tk() root.geometry("400x50+200+300" ) app = Application(master=root) root.mainloop()
Checkbutton 控件用于选择多个按钮的情况。Checkbutton 可以显示文本,也可以显示图像。
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 """测试 Checkbutton 组件的基本用法,使用面向对象的方式""" from tkinter import *from tkinter import messageboxclass Application (Frame ): def __init__ (self, master=None ): super ().__init__(master) self.master = master self.pack() self.createWidget() def createWidget (self ): self.codeHobby = IntVar() self.videoHobby = IntVar() print (self.codeHobby.get()) self.c1 = Checkbutton(self, text="敲代码" , variable=self.codeHobby, onvalue=1 , offvalue=0 ) self.c2 = Checkbutton(self, text="看视频" , variable=self.videoHobby, onvalue=1 , offvalue=0 ) self.c1.pack(side="left" ) self.c2.pack(side="left" ) Button(self, text="确定" , command=self.confirm).pack(side="left" ) def confirm (self ): if self.videoHobby.get() == 1 : messagebox.showinfo("测试" , "看视频,都是正常人有的爱好!你喜欢看什么类型?" ) if self.codeHobby.get() == 1 : messagebox.showinfo("测试" , "抓获野生程序猿一只,赶紧送给他尚学堂的视频充饥" ) if __name__ == '__main__' : root = Tk() root.geometry("400x50+200+300" ) app = Application(master=root) root.mainloop()
Canvasl画布组件 canvas(画布)是一个矩形区域,可以放置图形、图像、组件等。本节我们简单介绍canvas 的使用,更加详细和深入的内容将在后面的“图形绘制”章节讲解。
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 """测试 Canvas 组件的基本用法,使用面向对象的方式""" from tkinter import *from tkinter import messageboximport randomclass Application (Frame ): def __init__ (self, master=None ): super ().__init__(master) self.master = master self.pack() self.createWidget() def createWidget (self ): self.canvas = Canvas(self, width=300 , height=200 , bg="green" ) self.canvas.pack() line = self.canvas.create_line(10 , 10 , 30 , 20 , 40 , 50 ) rect = self.canvas.create_rectangle(50 , 50 , 100 , 100 ) oval = self.canvas.create_oval(50 , 50 , 100 , 100 ) global photo photo = PhotoImage(file="../images/logo.gif" ) self.canvas.create_image(150 , 170 , image=photo) Button(self, text="画 10 个矩形" , command=self.draw50Recg).pack(side="left" ) def draw50Recg (self ): for i in range (0 , 10 ): x1 = random.randrange(int (self.canvas["width" ]) / 2 ) y1 = random.randrange(int (self.canvas["height" ]) / 2 ) x2 = x1 + random.randrange(int (self.canvas["width" ]) / 2 ) y2 = y1 + random.randrange(int (self.canvas["height" ]) / 2 ) self.canvas.create_rectangle(x1, y1, x2, y2) if __name__ == '__main__' : root = Tk() root.geometry("400x300+200+300" ) app = Application(master=root) root.mainloop()
布局管理器 一个 GUI 应用程序必然有大量的组件,这些组件如何排布?这时候,就需要使用 tkinter提供的布局管理器帮助我们组织、管理在父组件中子组件的布局方式。tkinter 提供了三种管理器:pack
、grid
、place
。
Grid布局管理器详解 grid 表格布局,采用==表格结构==组织组件。子组件的位置由行和列的单元格来确定,并且可以跨行和跨列,从而实现复杂的布局。
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 """测试 Grid 布局管理器的基本用法,使用面向对象的方式""" from tkinter import *from tkinter import messageboximport randomclass Application (Frame ): def __init__ (self, master=None ): super ().__init__(master) self.master = master self.pack() self.createWidget() def createWidget (self ): """通过 grid 布局实现登录界面""" self.label01 = Label(self, text="用户名" ) self.label01.grid(row=0 , column=0 ) self.entry01 = Entry(self) self.entry01.grid(row=0 , column=1 ) Label(self, text="用户名为手机号" ).grid(row=0 , column=2 ) Label(self, text="密码" ).grid(row=1 , column=0 ) Entry(self, show="*" ).grid(row=1 , column=1 ) Button(self, text="登录" ).grid(row=2 , column=1 , sticky=EW) Button(self, text="取消" ).grid(row=2 , column=2 , sticky=E) if __name__ == '__main__' : root = Tk() root.geometry("400x90+200+300" ) app = Application(master=root) root.mainloop()
计算器软件界面的设计 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 """计算器软件界面的设计""" from tkinter import *from tkinter import messageboximport randomclass Application (Frame ): def __init__ (self, master=None ): super ().__init__(master) self.master = master self.pack() self.createWidget() def createWidget (self ): """通过 grid 布局实现计算器软件的界面""" btnText = (("MC" , "M+" , "M-" , "MR" ), ("C" , "±" , "/" , "✖" ), (7 , 8 , 9 , "-" ), (4 , 5 , 6 , "+" ), (1 , 2 , 3 , "=" ), (0 , "." )) Entry(self).grid(row=0 , column=0 , columnspan=4 , pady=10 ) for rindex, r in enumerate (btnText): for cindex, c in enumerate (r): if c == "=" : Button(self, text=c, width=2 )\ .grid(row=rindex + 1 , column=cindex, rowspan=2 , sticky=EW) elif c == 0 : Button(self, text=c, width=2 )\ .grid(row=rindex + 1 , column=cindex, columnspan=2 , sticky=EW) elif c == "." : Button(self, text=c, width=2 )\ .grid(row=rindex + 1 , column=cindex+1 , sticky=EW) else : Button(self, text=c, width=2 )\ .grid(row=rindex+1 , column=cindex, sticky=EW) if __name__ == '__main__' : root = Tk() root.geometry("200x300+200+300" ) app = Application(master=root) root.mainloop()
Pack布局管理器 pack 按照组件的创建顺序将子组件添加到父组件中,按照垂直或者水平的方向自然排布。如果不指定任何选项,默认在父组件中自顶向下垂直添加组件。
pack 是代码量最少,最简单的一种,可以用于快速生成界面。
钢琴软件界面设计 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 from tkinter import *root = Tk() root.geometry("700x220" ) f1 = Frame(root) f1.pack() f2 = Frame(root) f2.pack() btnText = ("流行风" , "中国风" , "日本风" , "重金属" , "轻音乐" ) for txt in btnText: Button(f1, text=txt).pack(side="left" , padx="10" ) for i in range (1 , 20 ): Button(f2, width=5 , height=10 , bg="black" if i % 2 == 0 else "white" ).pack(side="left" ) root.mainloop()
Place管理器 place 布局管理器可以通过坐标精确控制组件的位置,适用于一些布局更加灵活的场景。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 from tkinter import *root = Tk() root.geometry("500x300" ) root.title("布局管理 place" ) root["bg" ] = "white" f1 = Frame(root, width=200 , height=200 , bg="green" ) f1.place(x=30 , y=30 ) Button(root, text="尚学堂" ).place(relx=0.5 , rely=0 , x=100 , y=200 , relwidth=0.2 , relheight=0.2 ) Button(f1, text="百战程序员" ).place(relx=0.6 , rely=0.7 ) Button(f1, text="高淇老师" ).place(relx=0.2 , rely=0.2 ) root.mainloop()
事件处理 扑克游戏界面设计-增加事件操作 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 """扑克牌游戏的界面设计""" from tkinter import *class Application (Frame ): def __init__ (self, master=None ): super ().__init__(master) self.master = master self.place() self.createWidget() def createWidget (self ): """通过place布局管理器实现扑克牌位置控制""" self.photos = [PhotoImage(file="../images/puke/puke" + str (i+1 ) + ".gif" ) for i in range (10 )] self.pukes = [Label(self.master, image=self.photos[i]) for i in range (10 )] for i in range (10 ): self.pukes[i].place(x=10 +i*40 , y=50 ) self.pukes[0 ].bind_class("Label" , "<Button-1>" , self.chupai) def chupai (self, event ): print (event.widget.winfo_geometry()) print (event.widget.winfo_y()) if event.widget.winfo_y() == 50 : event.widget.place(y=30 ) else : event.widget.place(y=50 ) if __name__ == '__main__' : root = Tk() root.geometry("600x300+200+300" ) app = Application(master=root) root.mainloop()
lambda表达式-事件传参应用 lambda 表达式定义的是一个匿名函数,只适合简单输入参数,简单计算返回结果,不适合功能复杂情况。
lambda 定义的匿名函数也有输入、也有输出,只是没有名字。语法格式如下:
lambda 参数值列表:表达式
参数值列表即为输入。
表达式计算的结构即为输出。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 from tkinter import *root = Tk() root.geometry("270x50" ) def mouseTest1 (): print ("command 方式,简单情况:不涉及获取 event 对象,可以使用" ) def mouseTest2 (a, b ): print ("a={0},b={1}" .format (a, b)) Button(root, text="测试 command1" , command=mouseTest1).pack(side="left" ) Button(root, text="测试 command2" , command=lambda : mouseTest2("gaoqi" , "xixi" )).pack(side="left" ) root.mainloop()
三种事件绑定方式总结 多种事件绑定方式汇总
组件对象的绑定
通过 command
属性绑定(适合简单不需获取 event 对象)
Button(root,text="登录",command=login)
通过 bind()
方法绑定(适合需要获取 event 对象)
c1 = Canvas(); c1.bind("<Button-1>",drawLine)
组件类的绑定
调用对象的 bind_class
函数,将该组件类所有的组件绑定事件:
w.bind_class("Widget","event",eventhanler)
比如:btn01.bind_class(“Button”,"<Button-1>",func)
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 from tkinter import *root = Tk() root.geometry("270x30" ) def mouseTest1 (event ): print ("bind()方式绑定,可以获取 event 对象" ) print (event.widget) def mouseTest2 (a, b ): print ("a={0},b={1}" .format (a, b)) print ("command 方式绑定,不能直接获取 event 对象" ) def mouseTest3 (event ): print ("右键单击事件,绑定给所有按钮啦!!" ) print (event.widget) b1 = Button(root, text="测试 bind()绑定" ) b1.pack(side="left" ) b1.bind("<Button-1>" , mouseTest1) b2 = Button(root, text="测试 command2" , command=lambda : mouseTest2("gaoqi" , "xixi" )) b2.pack(side="left" ) b1.bind_class("Button" , "<Button-2>" , mouseTest3) root.mainloop()
其他组件 OptionMenu(选择项)用来做多选一,选中的项在顶部显示。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 """optionmenu 的使用测试""" from tkinter import *root = Tk() root.geometry("200x100" ) v = StringVar(root) v.set ("百战程序员" ) om = OptionMenu(root, v, "尚学堂" , "百战程序员" , "卓越班[保底 18 万]" ) om["width" ] = 10 om.pack() def test1 (): print ("最喜爱的机构:" , v.get()) Button(root, text="确定" , command=test1).pack() root.mainloop()
scale滑块 Scale(移动滑块)用于在指定的数值区间,通过滑块的移动来选择值。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 """ Scale(移动滑块)的使用测试:使用Scale控制字体大小变化""" from tkinter import *root = Tk() root.geometry("400x150" ) def test1 (value ): print ("滑块的值:" , value) newFont = ("宋体" , value) a.config(font=newFont) s1 = Scale(root, from_=10 , to=50 , length=200 , tickinterval=5 , orient=HORIZONTAL, command=test1) s1.pack() a = Label(root, text="百战程序员" , width=10 , height=1 , bg="black" , fg="white" ) a.pack() root.mainloop()
颜色框 颜色选择框可以帮助我们设置背景色、前景色、画笔颜色、字体颜色等等。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 """askcolor 颜色选择框的测试,改变背景色""" from tkinter import *from tkinter.colorchooser import *root = Tk() root.geometry("400x150" ) def test1 (): s1 = askcolor(color="red" , title="选择背景色" ) print (s1) root.config(bg=s1[1 ]) Button(root, text="选择背景色" , command=test1).pack() root.mainloop()
文件选择框 文件对话框帮助我们实现可视化的==操作目录、操作文件==。最后,将文件、目录的信息传入到程序中。文件对话框包含如下一些常用函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 """文件对话框获取文件""" from tkinter import *from tkinter.filedialog import *root = Tk(); root.geometry("400x100" ) def test1 (): f = askopenfilename(title="上传文件" , initialdir="d:/" , filetypes=[("视频文件" , ".mp4" )]) show["text" ] = f Button(root, text="选择编辑的视频文件" , command=test1).pack() show = Label(root, width=40 , height=3 , bg="green" ) show.pack() root.mainloop()
读取文件内容 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 from tkinter import *from tkinter.filedialog import *root = Tk() root.geometry("400x100" ) def test1 (): with askopenfile(title="上传文件" , initialdir="d:" , filetypes=[("文本文件" , ".txt" )]) as f: show["text" ] = f.read() Button(root, text="选择读取的文本文件" , command=test1).pack() show = Label(root, width=40 , height=3 , bg="green" ) show.pack() root.mainloop()
简单对话框 simpledialog(简单对话框)包含如下常用函数:
参数中,title 表示窗口标题;
prompt 是提示信息;
命名参数 kw 为各种选项:initialvalue(初始值)、minvalue(最小值)、maxvalue(最大值)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 """简单对话框""" from tkinter.simpledialog import *from tkinter import *root = Tk() root.geometry("400x100" ) def test1 (): a = askinteger(title="输入年龄" , prompt="请输入年龄" , initialvalue=18 , minvalue=1 , maxvalue=150 ) show["text" ] = a Button(root, text="你多大了? 请输入" , command=test1).pack() show = Label(root, width=40 , height=3 , bg="green" ) show.pack() root.mainloop()
通用消息框 messagebox(通用消息框)用于和用户简单的交互,用户点击确定、取消。如下列出了
messagebox 的常见函数:
1 2 3 4 5 6 7 8 9 10 11 """简单对话框""" from tkinter import *from tkinter.messagebox import *root = Tk() root.geometry("400x100" ) a1 = showinfo(title="尚学堂" , message="Python400 集从零开始,深入底层,\ 深入算法,打好基础。还手写神经网络" )print (a1)root.mainloop()
ttk子模块问题 我们再前面学的组件是 tkinter 模块下的组件,整体风格较老较丑。为了弥补这点不足,推出了 ttk 组件。ttk 组件更加美观、功能更加强大。 新增了 LabeledScale(带标签的Scale)、Notebook(多文档窗口)、Progressbar(进度条)、Treeview(树)等组件。
使用 ttk 组件与使用普通的 Tkinter 组件并没有多大的区别,只要导入 ttk 模块即可。
:warning:此处我们不展开细讲 ttk。如果你的项目确实需要用到复杂的界面,推荐大家使用wxpython
或者 pyQt
.
菜单 主菜单 主菜单一般包含:文件、编辑、帮助等,位于 GUI 窗口的上面。创建主菜单一般有如下 4步:
创建主菜单栏对象
创建菜单,并添加到主菜单栏对象
1 2 file_menu = tk.Menu(menubar) menubar.add_cascade(label="文件" ,menu=file_menu)
添加菜单项到 2 步中的菜单
1 2 3 4 file_menu.add_command(label="打开" ) file_menu.add_command(label="保存" ,accelerator="ctrl + s" command=mySaveFile) file_menu.add_separator() file_menu.add_command(label="退出" )
将主菜单栏添加到根窗口
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 """开发记事本软件的菜单""" from tkinter import *from tkinter.filedialog import *from tkinter.colorchooser import *class Application (Frame ): def __init__ (self, master=None ): super ().__init__(master) self.master = master self.textpad = None self.pack() self.createWidget() def createWidget (self ): menubar = Menu(root) menuFile = Menu(menubar) menuEdit = Menu(menubar) menuHelp = Menu(menubar) menubar.add_cascade(label="文件(F)" , menu=menuFile) menubar.add_cascade(label="编辑(E)" , menu=menuEdit) menubar.add_cascade(label="帮助(H)" , menu=menuHelp) menuFile.add_command(label="新建" , accelerator="ctrl+n" , command=self.test) menuFile.add_command(label="打开" , accelerator="ctrl+o" , command=self.test) menuFile.add_command(label="保存" , accelerator="ctrl+s" , command=self.test) menuFile.add_separator() menuFile.add_command(label="退出" , accelerator="ctrl+q" , command=self.test) root["menu" ] = menubar self.textpad = Text(root, width=50 , height=30 ) self.textpad.pack() def test (self ): pass if __name__ == '__main__' : root = Tk() root.geometry("450x300+200+300" ) root.title("简易记事本" ) app = Application(master=root) root.mainloop()
上下文菜单 快捷菜单(上下文菜单)是通过鼠标右键单击组件而弹出的菜单,一般是和这个组件相关的操作,比如:剪切、复制、粘贴、属性等。创建快捷菜单步骤如下:
创建菜单
1 2 menubar = tk.Menu(root) menubar.add_command(label="字体" )
绑定鼠标右键单击事件
1 2 3 def test (event ): menubar.post(event.x_root,event.y_root) root.bind("<Button-3>" ,test)
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 """开发记事本软件的菜单""" from tkinter import *from tkinter.filedialog import *from tkinter.colorchooser import *class Application (Frame ): def __init__ (self, master=None ): super ().__init__(master) self.master = master self.textpad = None self.pack() self.createWidget() def createWidget (self ): menubar = Menu(root) menuFile = Menu(menubar) menuEdit = Menu(menubar) menuHelp = Menu(menubar) menubar.add_cascade(label="文件(F)" , menu=menuFile) menubar.add_cascade(label="编辑(E)" , menu=menuEdit) menubar.add_cascade(label="帮助(H)" , menu=menuHelp) menuFile.add_command(label="新建" , accelerator="ctrl+n" , command=self.test) menuFile.add_command(label="打开" , accelerator="ctrl+o" , command=self.test) menuFile.add_command(label="保存" , accelerator="ctrl+s" , command=self.test) menuFile.add_separator() menuFile.add_command(label="退出" , accelerator="ctrl+q" , command=self.test) root["menu" ] = menubar self.textpad = Text(root, width=50 , height=30 ) self.textpad.pack() self.contextMenu = Menu(root) self.contextMenu.add_command(label="背景颜色" , command=self.test) root.bind("<Button-3>" , self.createContextMenu) def test (self ): pass def createContextMenu (self, event ): self.contextMenu.post(event.x_root, event.y_root) if __name__ == '__main__' : root = Tk() root.geometry("450x300+200+300" ) root.title("简易记事本" ) app = Application(master=root) root.mainloop()
记事本项目 结合所学 GUI 知识,开发一款模仿 windows 记事本的软件。包含了基本的功能:
新建文本文件
保存文件
修改文件内容
退出
各种快捷键处理
修改文本区域背景色
【01】打开和保存修改文件的实现 【02】新建文件-背景色改变-快捷键功能 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 """ 开发一个简单的记事本。 包含:新建、保存、修改文本内容、退出 包含:各种快捷键的处理 version 1.0 """ from tkinter import *from tkinter.filedialog import *from tkinter.colorchooser import *class Application (Frame ): def __init__ (self, master=None ): super ().__init__(master) self.master = master self.textpad = None self.pack() self.createWidget() def createWidget (self ): menubar = Menu(root) menuFile = Menu(menubar) menuEdit = Menu(menubar) menuHelp = Menu(menubar) menubar.add_cascade(label="文件(F)" , menu=menuFile) menubar.add_cascade(label="编辑(E)" , menu=menuEdit) menubar.add_cascade(label="帮助(H)" , menu=menuHelp) menuFile.add_command(label="新建" , accelerator="ctrl+n" , command=self.newfile) menuFile.add_command(label="打开" , accelerator="ctrl+o" , command=self.openfile) menuFile.add_command(label="保存" , accelerator="ctrl+s" , command=self.savefile) menuFile.add_separator() menuFile.add_command(label="退出" , accelerator="ctrl+q" , command=self.exit) root["menu" ] = menubar root.bind("<Control-n>" , lambda event: self.newfile()) root.bind("<Control-o>" , lambda event: self.openfile()) root.bind("<Control-s>" , lambda event: self.savefile()) root.bind("<Control-q>" , lambda event: self.exit()) self.textpad = Text(root, width=50 , height=30 ) self.textpad.pack() self.contextMenu = Menu(root) self.contextMenu.add_command(label="背景颜色" , command=self.openAskColor) root.bind("<Button-3>" , self.createContextMenu) def newfile (self ): self.textpad.delete(1.0 , END) self.filename = asksaveasfilename(title="另存为" , initialfile="未命名.txt" , filetypes=[("文本文档" , "*.txt" )], defaultextension=".txt" ) self.savefile() def openfile (self ): self.textpad.delete(1.0 , END) with askopenfile(title="打开文本文件" ) as f: self.textpad.insert(INSERT, f.read()) self.filename = f.name def savefile (self ): with open (self.filename, "w" ) as f: c = self.textpad.get(1.0 , END) f.write(c) def exit (self ): root.quit() def openAskColor (self ): color = askcolor(color="red" , title="选择背景颜色" ) self.textpad.config(bg=color[1 ]) def createContextMenu (self, event ): self.contextMenu.post(event.x_root, event.y_root) if __name__ == '__main__' : root = Tk() root.geometry("450x300+200+300" ) root.title("简易记事本" ) app = Application(master=root) root.mainloop()
【03】python项目打包成exe可执行文件 我们可以使用 pyinstaller 模块实现将 python 项目打包成 exe 文件。操作步骤如下:
安装 pyinstaller 模块
在 pycharm 中操作:file—>setting—>Project:xxx —>Project interpretor,再点击+即可。
在 pycharm 的 Terminal 终端输入如下命令:
pyinstaller -F xxxx.py
:warning:相关参数如下:
—icon= 图标路径(pyinstaller -F --icon=my.ico XXXX.py
)
-F 打包成一个 exe 文件
-w 使用窗口,无控制台
-c 使用控制台,无窗口
-D 创建一个目录,里面包含 exe 以及其他一些依赖性文件
在项目的 dist 目录下可以看到生成了 exe 文件,直接在 windows 系统中使用即可
:warning:==exe 文件本质是将 python 解释器和程序打包到了一起,这样我们执行程序时就不用管 windows 系统是不是有 python 解释器。==
画图项目 开发一款简单的画图软件, 包含如下功能:
画笔
矩形/椭圆绘制
清屏
橡皮擦
直线/带箭头的直线
修改画笔颜色、背景颜色
【01】界面实现 【02】绘制直线-拖动删除上一个图形 【03】箭头直线-矩形绘制 【04】画笔和橡皮擦实现 【05】清屏-颜色框-快捷键处理 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 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 """ 开发画图软件的菜单 """ from tkinter import *from tkinter.filedialog import *from tkinter.colorchooser import *win_heigth = 450 win_width = 900 class Application (Frame ): def __init__ (self, master=None , bgcolor="#000000" ): super ().__init__(master) self.master = master self.bgcolor = bgcolor self.x = 0 self.y = 0 self.fgcolor = "#ff0000" self.lastDraw = 0 self.startDrawFlag = False self.pack() self.createWidget() def createWidget (self ): self.drawpad = Canvas(root, width=win_width, height=win_heigth * 0.9 , bg=self.bgcolor) self.drawpad.pack() btn_start = Button(root, text="开始" , name="start" ) btn_start.pack(side="left" , padx="10" ) btn_pen = Button(root, text="画笔" , name="pen" ) btn_pen.pack(side="left" , padx="10" ) btn_rect = Button(root, text="矩形" , name="rect" ) btn_rect.pack(side="left" , padx="10" ) btn_clear = Button(root, text="清屏" , name="clear" ) btn_clear.pack(side="left" , padx="10" ) btn_eraser = Button(root, text="橡皮擦" , name="eraser" ) btn_eraser.pack(side="left" , padx="10" ) btn_lineArrow = Button(root, text="箭头直线" , name="lineArrow" ) btn_lineArrow.pack(side="left" , padx="10" ) btn_color = Button(root, text="颜色" , name="color" ) btn_color.pack(side="left" , padx="10" ) btn_pen.bind_class("Button" , "<1>" , self.event_Manager) self.drawpad.bind("ButtonRelease-1" , self.stopDraw) root.bind("<KeyPress-r>" ,self.kuaijiejian) root.bind("<KeyPress-g>" ,self.kuaijiejian) root.bind("<KeyPress-y>" ,self.kuaijiejian) def event_Manager (self, event ): name = event.widget.winfo_name() print (name) if name == "line" : self.drawpad.bind("<B1-Motion>" , self.myLine) elif name == "lineArrow" : self.drawpad.bind("<B1-Motion>" , self.myLineArrow) elif name == "rect" : self.drawpad.bind("<B1-Motion>" , self.myRect) elif name == "pen" : self.drawpad.bind("<B1-Motion>" , self.myPen) elif name == "eraser" : self.drawpad.bind("<B1-Motion>" , self.myEraser) elif name == "clear" : self.drawpad.delete("all" ) elif name == "color" : c = askcolor(color=self.fgcolor, title="选择画笔颜色" ) self.fgcolor = c[1 ] def stopDraw (self, event ): self.startDrawFlag = False self.lastDraw = 0 def startDraw (self, event ): self.drawpad.delete(self.lastDraw) if not self.startDrawFlag: self.startDrawFlag = True self.x = event.x self.y = event.y def myLine (self, event ): self.startDraw(event) self.lastDraw = self.drawpad.create_line(self.x, self.y, event.x, event.y, fill=self.fgcolor) def myLineArrow (self, event ): self.startDraw(event) self.lastDraw = self.drawpad.create_line(self.x, self.y, event.x, event.y, arrow=LAST, fill=self.fgcolor) def myRect (self, event ): self.startDraw(event) self.lastDraw = self.drawpad.create_rectangle(self.x, self.y, event.x, event.y, outline=self.fgcolor) def myPen (self, event ): self.startDraw(event) self.drawpad.create_line(self.x, self.y, event.x, event.y, fill=self.fgcolor) self.x = event.x self.y = event.y def myEraser (self, event ): self.startDraw(event) self.drawpad.create_rectangle(event.x - 4 , event.y - 4 , event.x + 4 , event.y + 4 , fill=self.bgcolor) self.x = event.x self.y = event.y def kuaijiejian (self, event ): if event.char == "r" : self.fgcolor = "#ff0000" if event.char == "g" : self.fgcolor = "#00ff00" if event.char == "y" : self.fgcolor = "#ffff00" if __name__ == '__main__' : root = Tk() root.geometry(str (win_width) + "x" + str (win_heigth) + "+200+300" ) root.title("画图软件" ) app = Application(master=root) root.mainloop()
游戏开发-坦克大战 pygame模块的安装
面向对象分析项目需求 坦克大战游戏的需求
1.顶目中有哪些类
2.每个类中有哪些方法
(1)坦克类(我方坦克、敌方坦克)
射击 移动类 显示坦克的方法
(2)子弹类
移动 显示子弹的方法
(3)墙壁类
属性:是否可以通过
(4)爆炸效果类
展示爆炸效果
(5)音效类
播放音乐
(6)主类
开始游戏
结束游戏
坦克大战项目框架搭建 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 class MainGame (): def __init__ (self ): pass def startGame (self ): pass def endGame (self ): pass class Tank (): def __init__ (self ): pass def move (self ): pass def shot (self ): pass def displayTank (self ): pass class MyTank (Tank ): def __init__ (self ): pass class EnemyTank (Tank ): def __init__ (self ): pass class Bullet (): def __init__ (self ): pass def move (self ): pass def displayBullet (self ): pass class Wall (): def __init__ (self ): pass def displayWall (self ): pass class Expode (): def __init__ (self ): pass def displayExpode (self ): pass class Music (): def __init__ (self ): pass def play (self ): pass
加载主窗口 1 2 3 新增功能: 加载主窗口 pygame官方网址:www.pygame.org
坦克大战之事件处理 1 2 3 4 新增功能: 添加事件 1.点击关闭 关闭窗口 2.按下键盘时候,判断键盘按下的是什么键,分别对不同的键做处理
左上角文字的绘制 1 2 3 新增功能: 左上角文字绘制: 左上角输出敌方坦克的数量6
加载我方坦克
我方坦克切换方向移动 1 2 3 新增功能: 1.我方坦克切换方向 2.我方坦克移动
我方坦克移动优化
我方坦克优化2 1 2 3 新增功能: 优化:按下方向键,坦克一直移动 松开方向键,坦克停止
加载敌方坦克 1 2 3 新增功能: 1.完善敌方坦克初始化方法 2.创建敌方坦克并展示
敌方坦克随机移动 1 2 3 4 5 新增功能: 1.优化左上角 文字的显示,将敌方坦克的数量进行修改 2.敌方坦克随机移动 思路:新增加一个变量步数,当移动时候步数进行递减 当步数<=0时候修改敌方坦克的方向,并将步数复位
完善子弹类
我方坦克发射子弹
子弹移动 1 2 新增功能: 完成我方坦克发射子弹,并完成子弹的移动
子弹消亡及数量控制 1 2 3 新增功能: 优化:1.如果子弹碰到墙壁,让子弹消失 2.最多可以发射3颗子弹,不能一直发射
敌方坦克发射子弹 1 2 3 新增功能: 优化:1.如果子弹碰到墙壁,让子弹消失 2.最多可以发射3颗子弹,不能一直发射
我方子弹与敌方坦克的碰撞 1 2 3 新增功能: 我方子弹与敌方坦克的碰撞 精灵类 Sprite类
实现爆炸效果 1 2 3 新增功能: 1.完善爆炸效果类 2.在窗口中展示爆炸效果
我方坦克的消亡 1 2 3 新增功能: 1.敌方子弹与我方坦克的碰撞 2.添加爆炸效果
我方坦克无限重生 1 2 3 4 新增功能: 让我方坦克无限重生 1.按下键盘的Esc让重生 2.重生及重新创建我方坦克
加载墙壁 1 2 3 4 新增功能: 添加墙壁 1.完善墙壁类,初始化方法 2.初始化墙壁,并将墙壁存储到列表中,在窗口中加载墙壁
子弹不穿墙 1 2 3 4 新增功能: 1.子弹不能穿墙 子弹碰撞到墙壁时候,让子弹消失 2.墙壁被击中,墙壁的生命值处理
坦克不能穿墙 1 2 3 4 新增功能: 1.坦克不能穿墙 坦克碰撞到墙壁,不能再移动 不能再移动及坐标不能发生变化
敌我双方坦克发生碰撞 1 2 3 4 5 新增功能: 1.我方坦克与敌方坦克发生碰撞 让我方坦克不能再继续移动 stay() 2.敌方坦克与我方坦克发生碰撞 让地方坦克不能再移动 stay()
音效处理 1 2 3 4 新增功能: 1.完善音效类 2.添加开场音效 3.我方坦克发射子弹添加音效
坦克大战完整代码 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 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 """ pygame模块的安装 面向对象分析项目需求 坦克大战项目框架搭建 加载主窗口 坦克大战之事件处理 左上角文字的绘制 加载我方坦克 我方坦克切换方向移动 我方坦克移动优化 我方坦克优化2 加载敌方坦克 敌方坦克随机移动 完善子单类 我方坦克发射子弹 子弹移动 子弹消亡及数量控制 敌方坦克发射子弹 我方子弹与敌方坦克的碰撞 实现爆炸效果 我方坦克的消亡 我方坦克无限重生 加载墙壁 子弹不穿墙 坦克不能穿墙 敌我双方坦克发生碰撞 音效处理 """ import pygame, time, randomfrom pygame.sprite import SpriteSCREEN_WIDTH = 800 SCREEN_HEIGHT = 500 BG_COLOR = pygame.Color(0 , 0 , 0 ) TEXT_COLOR = pygame.Color(255 , 0 , 0 ) class BaseItem (Sprite ): def __init__ (self, color, width, height ): pygame.sprite.Sprite.__init__(self) class MainGame (): window = None my_tank = None enemyTankList = [] enemyTankCount = 5 myBulletList = [] enemyBulletList = [] explodeList = [] wallList = [] def __init__ (self ): pass def startGame (self ): pygame.display.init() MainGame.window = pygame.display.set_mode([SCREEN_WIDTH, SCREEN_HEIGHT]) self.createMyTank() self.createEnemyTank() self.createWall() pygame.display.set_caption("坦克大战" ) while True : time.sleep(0.02 ) MainGame.window.fill(BG_COLOR) self.getEvent() MainGame.window.blit(self.getTextSuface("敌方坦克剩余数量%d" % len (MainGame.enemyTankList)), (10 , 10 )) if MainGame.my_tank and MainGame.my_tank.live: MainGame.my_tank.displayTank() else : del MainGame.my_tank MainGame.my_tank = None self.blitEnemyTank() self.blitMyBullet() self.blitEnemyBullet() self.blitExplode() self.blitWall() if MainGame.my_tank and MainGame.my_tank.live: if not MainGame.my_tank.stop: MainGame.my_tank.move() MainGame.my_tank.hitWall() MainGame.my_tank.myTank_hit_enemyTank() pygame.display.update() def createMyTank (self ): MainGame.my_tank = MyTank(350 , 300 ) music = Music("./img/start.wav" ) music.play() def createEnemyTank (self ): top = 100 for i in range (MainGame.enemyTankCount): left = random.randint(0 , 600 ) speed = random.randint(0 , 4 ) enemy = EnemyTank(left, top, speed) MainGame.enemyTankList.append(enemy) def createWall (self ): for i in range (6 ): wall = Wall(i * 130 , 220 ) MainGame.wallList.append(wall) def blitEnemyTank (self ): for enemyTank in MainGame.enemyTankList: if enemyTank.live: enemyTank.displayTank() enemyTank.randMove() enemyTank.hitWall() if MainGame.my_tank and MainGame.my_tank.live: enemyTank.enemyTank_hit_myTank() enemyBullet = enemyTank.shot() if enemyBullet: MainGame.enemyBulletList.append(enemyBullet) else : MainGame.enemyTankList.remove(enemyTank) def blitMyBullet (self ): for myBullet in MainGame.myBulletList: if myBullet.live: myBullet.displayBullet() myBullet.move() myBullet.myBullet_hit_enemyTank() myBullet.hitWall() else : MainGame.myBulletList.remove(myBullet) def blitEnemyBullet (self ): for enemyBullet in MainGame.enemyBulletList: if enemyBullet.live: enemyBullet.displayBullet() enemyBullet.move() enemyBullet.enemyBullet_hit_myTank() enemyBullet.hitWall() else : MainGame.enemyBulletList.remove(enemyBullet) def blitExplode (self ): for explode in MainGame.explodeList: if explode.live: explode.displayExplode() music = Music("./img/fire.wav" ) music.play() else : MainGame.explodeList.remove(explode) def blitWall (self ): for wall in MainGame.wallList: if wall.live: wall.displayWall() else : MainGame.wallList.remove(wall) def endGame (self ): print ("感谢使用,欢迎再次使用" ) exit() def getTextSuface (self, text ): pygame.font.init() font = pygame.font.SysFont("adobe宋体stdl" , 18 ) textSurface = font.render(text, True , TEXT_COLOR) return textSurface def getEvent (self ): eventList = pygame.event.get() for event in eventList: if event.type == pygame.QUIT: self.endGame() if event.type == pygame.KEYDOWN: if not MainGame.my_tank: if event.key == pygame.K_ESCAPE: self.createMyTank() if MainGame.my_tank and MainGame.my_tank.live: if event.key == pygame.K_LEFT: MainGame.my_tank.direction = "L" MainGame.my_tank.stop = False print ("按下左键,坦克左移" ) elif event.key == pygame.K_RIGHT: MainGame.my_tank.direction = "R" MainGame.my_tank.stop = False print ("按下右键,坦克右移" ) elif event.key == pygame.K_UP: MainGame.my_tank.direction = "U" MainGame.my_tank.stop = False print ("按下上键,坦克上移" ) elif event.key == pygame.K_DOWN: MainGame.my_tank.direction = "D" MainGame.my_tank.stop = False print ("按下下键,坦克下移" ) elif event.key == pygame.K_SPACE: print ("发射子弹" ) if len (MainGame.myBulletList) < 3 : myBullet = Bullet(MainGame.my_tank) MainGame.myBulletList.append(myBullet) music = Music("./img/hit.wav" ) music.play() if event.type == pygame.KEYUP: if event.key == pygame.K_LEFT or event.key == pygame.K_RIGHT or event.key == pygame.K_UP or event.key == pygame.K_DOWN: if MainGame.my_tank and MainGame.my_tank.live: MainGame.my_tank.stop = True class Tank (BaseItem ): def __init__ (self, left, top ): self.images = { "U" : pygame.image.load("./img/p1tankU.gif" ), "D" : pygame.image.load("./img/p1tankD.gif" ), "L" : pygame.image.load("./img/p1tankL.gif" ), "R" : pygame.image.load("./img/p1tankR.gif" ), } self.direction = "U" self.image = self.images[self.direction] self.rect = self.image.get_rect() self.rect.left = left self.rect.top = top self.speed = 5 self.stop = True self.live = True self.oldLeft = self.rect.left self.oldTop = self.rect.top def move (self ): self.oldLeft = self.rect.left self.oldTop = self.rect.top if self.direction == "L" : if self.rect.left > 0 : self.rect.left -= self.speed elif self.direction == "R" : if self.rect.left + self.rect.height < SCREEN_WIDTH: self.rect.left += self.speed elif self.direction == "U" : if self.rect.top > 0 : self.rect.top -= self.speed elif self.direction == "D" : if self.rect.top + self.rect.height < SCREEN_HEIGHT: self.rect.top += self.speed def shot (self ): return Bullet(self) def stay (self ): self.rect.left = self.oldLeft self.rect.top = self.oldTop def hitWall (self ): for wall in MainGame.wallList: if pygame.sprite.collide_rect(self, wall): self.stay() def displayTank (self ): self.image = self.images[self.direction] MainGame.window.blit(self.image, self.rect) class MyTank (Tank ): def __init__ (self, left, top ): super (MyTank, self).__init__(left, top) def myTank_hit_enemyTank (self ): for enemyTank in MainGame.enemyTankList: if pygame.sprite.collide_rect(self, enemyTank): self.stay() class EnemyTank (Tank ): def __init__ (self, left, top, speed ): super (EnemyTank, self).__init__(left, top) self.images = { "U" : pygame.image.load("./img/enemy1U.gif" ), "D" : pygame.image.load("./img/enemy1D.gif" ), "R" : pygame.image.load("./img/enemy1R.gif" ), "L" : pygame.image.load("./img/enemy1L.gif" ), } self.direction = self.randDirection() self.image = self.images[self.direction] self.rect = self.image.get_rect() self.rect.left = left self.rect.top = top self.speed = 5 self.stop = True self.step = 50 def randDirection (self ): num = random.randint(1 , 4 ) if num == 1 : return "U" elif num == 2 : return "D" elif num == 3 : return "L" elif num == 4 : return "R" def randMove (self ): if self.step <= 0 : self.direction = self.randDirection() self.step = 50 else : self.move() self.step -= 1 def shot (self ): num = random.randint(1 , 100 ) if num < 10 : return Bullet(self) def enemyTank_hit_myTank (self ): if pygame.sprite.collide_rect(self, MainGame.my_tank): self.stay() class Bullet (BaseItem ): def __init__ (self, tank ): self.image = pygame.image.load("./img/enemymissile.gif" ) self.direction = tank.direction self.rect = self.image.get_rect() if self.direction == "U" : self.rect.left = tank.rect.left + tank.rect.width / 2 - self.rect.width / 2 self.rect.top = tank.rect.top - self.rect.height elif self.direction == "D" : self.rect.left = tank.rect.left + tank.rect.width / 2 - self.rect.width / 2 self.rect.top = tank.rect.top + self.rect.height elif self.direction == "R" : self.rect.left = tank.rect.left + self.rect.width self.rect.top = tank.rect.top + tank.rect.width / 2 - self.rect.width / 2 elif self.direction == "L" : self.rect.left = tank.rect.left - tank.rect.width / 2 - self.rect.width / 2 self.rect.top = tank.rect.top + tank.rect.width / 2 - self.rect.width / 2 self.speed = 6 self.live = True def move (self ): if self.direction == "U" : if self.rect.top > 0 : self.rect.top -= self.speed else : self.live = False elif self.direction == "D" : if self.rect.top + self.rect.height < SCREEN_HEIGHT: self.rect.top += self.speed else : self.live = False elif self.direction == "R" : if self.rect.left + self.rect.width < SCREEN_WIDTH: self.rect.left += self.speed else : self.live = False elif self.direction == "L" : if self.rect.left > 0 : self.rect.left -= self.speed else : self.live = False def hitWall (self ): for wall in MainGame.wallList: if pygame.sprite.collide_rect(self, wall): self.live = False wall.hp -= 1 if wall.hp < 0 : wall.live = False def displayBullet (self ): MainGame.window.blit(self.image, self.rect) def myBullet_hit_enemyTank (self ): for enemyTank in MainGame.enemyTankList: if pygame.sprite.collide_rect(enemyTank, self): enemyTank.live = False self.live = False explode = Expode(enemyTank) MainGame.explodeList.append(explode) def enemyBullet_hit_myTank (self ): if MainGame.my_tank and MainGame.my_tank.live: if pygame.sprite.collide_rect(MainGame.my_tank, self): explode = Expode(MainGame.my_tank) MainGame.explodeList.append(explode) self.live = False MainGame.my_tank.live = False class Wall (): def __init__ (self, left, top ): self.image = pygame.image.load("./img/steels.gif" ) self.rect = self.image.get_rect() self.rect.left = left self.rect.top = top self.live = True self.hp = 3 def displayWall (self ): MainGame.window.blit(self.image, self.rect) class Expode (): def __init__ (self, tank ): self.rect = tank.rect self.images = [ pygame.image.load("./img/blast0.gif" ), pygame.image.load("./img/blast1.gif" ), pygame.image.load("./img/blast2.gif" ), pygame.image.load("./img/blast3.gif" ), pygame.image.load("./img/blast4.gif" ), ] self.step = 0 self.image = self.images[self.step] self.live = True def displayExplode (self ): if self.step < len (self.images): self.image = self.images[self.step] self.step += 1 MainGame.window.blit(self.image, self.rect) else : self.live = False self.step = 0 class Music (): def __init__ (self, filename ): self.filename = filename pygame.mixer.init() pygame.mixer.music.load(self.filename) def play (self ): pygame.mixer.music.play() if __name__ == '__main__' : MainGame().startGame()