Python深入和提高

Python深入和提高

异常机制

异常的本质-调试错误的核心理念

try_except基本结构

try…一个except结构

try…except是最常见的异常处理结构。结构如下:

try:
    被监控可能发生引起异常的语句块
except BaseException [as e]:
    异常处理语句
  1. try块包含着可能引发异常的代码,except块则用来捕捉和处理发生的异常。
  2. 执行的时候,如果try块中没有引发异常,则跳过except块继续执行后续代码;
  3. 执行的时候,如果try块中发生了异常,则跳过try块中的后续代码,跳到相应的except
  4. 块中处理异常;异常处理完后,继续执行后续代码。
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结构

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块。

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块中申请的资源。

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迭代器没有更多的值
SyntaxErrorPython 语法错误
SyntaxWarning可疑的语法的警告
SystemError一般的解释器系统错误
SystemExit解释器请求退出
TabErrorTab 和空格混用
TypeError对类型无效的操作
UnboundLocalError访问未初始化的本地变量
UnicodeDecodeErrorUnicode 解码时的错误
UnicodeEncodeErrorUnicode 编码时错误
UnicodeErrorUnicode 相关的错误
UnicodeTranslateErrorUnicode 转换时错误
UserWarning用户代码生成的警告
ValueError传入无效的参数
Warning警告的基类
WindowsError系统调用失败
ZeroDivisionError除(或取模)零 (所有数据类型)

with上下文管理资源

finally块由于是否发生异常都会执行,通常我们放释放资源的代码。其实,我们可以通过with上下文管理,更方便的实现释放资源的操作。

with上下文管理的语法结构如下:

with context_expr [ as var]:
    语句块

with上下文管理可以自动管理资源,在with代码块执行完毕后自动还原进入该代码之前的现场或上下文。不论何种原因跳出with块,不论是否有异常,总能保证资源正常释放。极大的简化了工作,在文件操作、网络通信相关的场合非常常用。

with open("d:/test.txt") as f:
    context = f.readline()
    print(context)

traceback模块的使用-异常写入日志文件

import traceback

try:
    print("step1")
    num = 1 / 0
except:
    traceback.print_exc()  # 打印异常信息
import traceback

try:
    print("step1")
    num = 1 / 0
except:
    # 将异常信息输出到文件
    with open("d:/test.txt", "a") as f:
        traceback.print_exc(file=f)

自定义异常-raise抛出异常

程序开发中,有时候我们也需要自己定义异常类。自定义异常类一般都是运行时异常,通常继承Exception或其子类即可。命名一般以ErrorException为后缀。

自定义异常由raise语句主动抛出。

# 自定义异常类
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__":  # 如果是true,则模块是作独立文件执行,下面可以写测试代码
    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模式(可与其他模式组合使用)
+读、写模式(可与其他模式组合使用)

文本文件对象和二进制文件对象的创建:

  1. 如果没有增加模式b,则默认创建的是文本文件对象,处理的基本单元是“字符”。
  2. 如果是二进制模式b,则创建的是二进制文件对象,处理的基本单元是“字节”。

基本的文件写入操作

文本文件的写入一般就是三个步骤:

  1. 创建文件对象
  2. 写入数据
  3. 关闭文件对象
f = open(r"d:\test.txt", "a")
f.write("hello world")
f.close()
with open(r"d:\test.txt", "a") as f:
    f.write("hello world")

编码知识-中文乱码问题解决

# 指定文件编码解决中文乱码问题
with open(r"d:\test.txt", "a", encoding="utf-8") as f:
    f.write("你好")

write()/writelines()写入数据

write(a):把字符串a写入到文件中

writelines(b):把字符串列表事入文件中,不添加换行符

with open(r"d:\test.txt", "w", encoding="utf-8") as f:
    # f.write("你好")
    s = ["年龄\n", "姓名\n", "学校\n"]
    f.writelines(s)

关闭流要点

由于文件底层是由操作系统控制,所以我们打开的文件对象必须显式调用close()方法关闭文件对象。当调用close()方法时,首先会把缓冲区数据写入文件(也可以直接调用flush()方法),再关闭文件,释放文件对象。

为了确保打开的文件对象正常关闭,一般结合异常机制的finally或者with关键字实现无论何种情况都能关闭打开的文件对象。

finally异常管理

# 结合异常机制的finally,确保关闭文件对象
try:
    f = open(r"d:\test.txt", "w")
    s = "hello world"
    f.write(s)
except BaseException as e:
    print(e)
finally:
    f.close()

with上下文管理

# 使用with管理文件写入操作
s = ["年龄\n", "姓名\n", "学校\n"]
with open(r"d:\test.txt", "w", encoding="utf-8") as f:
    f.writelines(s)

文本文件的读取

文件的读取一般使用如下三个方法:

  1. read([size])
    从文件中读取size个字符,并作为结果返回。如果没有size参数,则读取整个文件。
    读取到文件末尾,会返回空字符串。
    with open(r"d:\test.txt", “r”, encoding=“utf-8”) as f:
    s = f.read(6)
    print(s)
    s2 = f.read()
    print(“第二次读的:”, s2)

    “”"
    年龄
    姓名

    第二次读的: 学校
    “”"

  2. readline()
    读取一行内容作为结果返回。读取到文件末尾,会返回空字符串
    with open(r"d:\test.txt", “r”, encoding=“utf-8”) as f:
    s = f.readline()
    print(s)

    “”"
    年龄
    “”"

  3. readlines()
    文本文件中,每一行作为一个字符串存入列表中,返回该列表
    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=“”)

    “”"
    年龄
    姓名
    学校
    “”"

文本文件操作-为每行的末尾添加行号

# 为文本文件每行的末尾添加行号
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()实现文件的读写操作。

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读取数据,反序列化成对象

import pickle

with 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文件中:

  • 值没有类型,所有值都是字符串
  • 不能指定字体颜色等样式
  • 不能指定单元格的宽高,不能合并单元格
  • 没有多个工作表
  • 不能嵌入图像图表
import csv
        
# 写csv文件
headers = ["年龄", "姓名"]
rows = [(20, 'jack'), (40, 'john')]
with open("data.csv", "w") as f:
    # 创建csv对象
    b_csv = csv.writer(f)
    # 写入一行(标题)
    b_csv.writerow(headers)
    # 写入多行(数据)
    b_csv.writerows(rows)

    
# 读取csv文件
with open("data.csv") as f:
    # 创建csv对象,他是一个包含所有数据的列表,每一行为一个元素
    a_csv = csv.reader(f)
    # 获得列表对象,它包含标题列的信息
    headers = next(a_csv)
    # 循环打印各行内容
    for row in a_csv:
        print(row)

os模块

os模块可以帮助我们直接对操作系统进行操作。我们可以直接调用操作系统的可执行文件、命令,直接操作文件、目录等等。

⚠️os模块是做系统运维非常重要的基础。

调用操作系统可执行文件-控制台乱码问题

控制台乱码问题

获取文件信息-创建和删除文件夹

我们可以通过前面讲的文件对象实现对于文件内容的读写操作。如果,还需要对文件和目录做其他操作,可以使用osos.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当前操作系统所使用的路径分隔符
import os

# 打印基本信息
print(os.name)  # windows--nt  linux-->posix
print(os.sep)  # windows-->\  linux-->/
print(repr(os.linesep))  # windows-->\r\n  linux-->\n
a = '3'
print(a)
print(repr(a))  # repr可显示数据信息
# 获取文件和文件夹的相关信息
print(os.stat("practice.py"))
# 关于工作目录的操作
print(os.getcwd())  # 获取当前工作目录
os.chdir("d:")  # 当前工作目录就变成了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§返回目录的路径
getatime(filename)返回文件的最后访问时间
getmtime(filename)返回文件的最后修改时间
walk(top,func,arg)递归方式遍历目录
join(path,*paths)连接多个path
split(path)对路径进行分割,以列表形式返回
splitext(path)从路径中分割文件的扩展名
import os.path

path = os.getcwd()
# 列出子目录和子文件
file_list = os.listdir(path)
# 只打印以.py结尾的文件名
for file_name in file_list:
    pos = file_name.rfind(".")
    if file_name[pos + 1:] == "py":
        print(file_name)
        
"""
practice.py
"""

print("*********************************")
# 用推导式列出目录下所有的.py文件
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: 一个列表,该文件夹中所有的文件的名字

import os

path = os.getcwd()
# topdown=False:先遍历子目录再遍历top目录
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模块都没有提供。

import shutil

shutil.copyfile("test.txt", "test_copy.tet")
# 递归拷贝  忽略所有的html,htm文件
shutil.copytree("source", "destination", ignore=shutil.ignore_patterns("*.html", "*.htm"))

shutil和zipfile模块-压缩和解压缩

import shutil
import zipfile

# 压缩某个文件
shutil.make_archive("d:/destination", "zip", "d:/source")
# 压缩:将指定多个文件压缩到一个zip文件
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()

递归算法原理-自己复习前面讲过的算法原理

递归算法-目录树结构的展示

import os
import os.path

# 递归遍历目录树
def 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
"""

模块

模块化编程理念-什么是模块-哲学思想

  1. Python程序由模块组成。一个模块对应python源文件,一般后缀名是:.py
  2. 模块由语句组成。运行Python程序时,按照模块中语句的顺序依次执行
  3. 语句是Python程序的构造单元,用于创建对象、变量赋值、调用函数、控制语句等

标准库模块(standard library)

与函数类似,模块也分为标准库模块和用户自定义模块。

Python标准库提供了操作系统功能、网络通信、文本处理、文件处理、数学运算等基本的功能。比如:random(随机数)、math(数学运算)、time(时间处理)、file(文件处理)、os(和操作系统交互)、sys(和解释器交互)等。

另外,Pythor还提供了海量的第三方模块,使用方式和标准库类似。功能覆盖了我们能想象到的所有领域,比如:科学计算、WEB开发、大数据、人工智能、图形系统等。

为什么需要模块化编程

模块(module)对应于Python源代码文件(.py文件)。模块中可以定义变量、函数、类、普通语句。这样,我们可以将一个Pythona程序分解成多个模块,便于后期的重复应用。

模块化编程(Modular Programming)将一个任务分解成多个模块。每个模块就像一个积木一样,便于后期的反复使用、反复搭建。

模块化编程有如下几个重要优势:

  1. 便于将一个任务分解成多个模块,实现团队协同开发,完成大规模程序
  2. 实现代码复用。一个模块实现后,可以被反复调用
  3. 可维护性增强

模块化编程的流程-设计和实现分离的思想

模块化编程的流程

模块化编程的一般流程:

①设计API,进行功能描述。

②编码实现API中描述的功能。

③在模块中编写测试代码,并消除全局代码。

④使用私有函数实现不被外部客户端调用的模块函数。

模块的API和功能描述要点

API(Application Programming Interface应用程序编程接口)是用于描述模块中提供的函数和类的功能描述和使用方式描述。

模块化编程中,首先设计的就是模块的AP!(即要实现的功能描述),然后开始编码实现API中描述的功能。最后,在其他模块中导入本模块进行调用。

可以通过help(模块名)查看模块的API。一般使用时先导入模块然后通过help函数查看。

【示例】设计计算薪水模块的API

# encoding=utf-8
"""
本模块是用来计算公司员工的薪资
"""
company = "ailbaba"

def yearSalary(monthSalary):
    """
    根据传入的月薪,计算年薪    :param monthSalary: 月薪    :return: 年薪    """
    pass

def daySalary(monthSalary):
    """
    根据传入的月薪,计算日薪(按照一个月22.5天计算)    :param monthSalary: 月薪    :return: 日薪    """
    pass

##################################################################################

# 在别的模块中使用
import salary

print(salary.__doc__)
print(salary.yearSalary.__doc__)

模块的创建和测试代码

每个模块都有一个名称,通过特殊变量__name__可以获取模块的名称。在正常情况下,模块名字对应源文件名。仅有一个例外,就是当一个模块被作为程序入口时(主程序、交互式提示符下),它的__name__的值为__main__。我们可以根据这个特点,将模块源代码文件中的测试代码进行独立的处理。例如:

# encoding=utf-8
"""
本模块是用来计算公司员工的薪资
"""
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语句的基本语法格式如下:

import 模块名             # 导入一个模块
import 模块1, 模块2       # 导入多个模块
import 模块名 as 模块名	# 导入模块并使用新名字

import加载的模块分为四种类型:

  1. 使用python编写的代码.py文件
  2. 已被编译为共享库或DLL的C或C++扩展
  3. 一组模块的包
  4. 使用C编写并链接到python解释器的内置模块

我们一般通过import语句实现模块的导入和使用,import本质上是使用了内置函数__import__()

当我们通过import导入一个模块时,python解释器进行执行,最终会生成一个对象,这个对象就代表了被加载的模块。

# encoding=utf-8
import math as m

print(id(m))
print(type(m))
print(m.sqrt(4))

"""
1630061747552
<class 'module'>
2.0
"""

由上,我们可以看到math模块被加载后,实际会生成一个module类的对象,该对象被math变量引用。我们可以通过math变量引用模块中所有的内容。

我们通过import导入多个模块,本质上也是生成多个module类的对象而已。

有时候,我们也需要给模块起个别名,本质上,这个别名仅仅是新创建一个变量引用加载的模块对象而已。

# encoding=utf-8
import math as m
import math

print(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 *

⚠️尽量避免 from 模块名 import *这种写法。*它表示导入模块中所有的不是以下划线_开头的名字都导入到当前位置。但你不知道你导入什么名字,很有可能会覆盖掉你之前已经定义的名字。而且可读性极其的差。一般生产环境中尽量避免使用,学习时没有关系。

from math import sqrt, sin

print(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。方式如下:

  1. import a.aa.module_AA
    在使用时,必须加完整名称来引用,比如:a.aa.module_AA.fun_AA()
  2. from a.aa import module_AA
    在使用时,直接可以使用模块名。比如:module_AA.fun_AA()
  3. from a.aa.module_AA import fun_AA 直接导入函数

在使用时,直接可以使用函数名。比如:fun_AA()

  1. from package import item这种语法中,item可以是包、模块,也可以是函数、类、变量。
  2. 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 *会从对应的包中导入以上两个子模块

⚠️尽管提供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 库

  1. Tkinter
    tkinter(Tk interface)是 Python 的标准 GUI 库,支持跨平台的 GUI 程序开发。tkinter适合小型的 GUI 程序编写,也特别适合初学者学习 GUI 编程。本书以 tkinter 为核心进行讲解。
  2. wxPython
    wxPython 是比较流行的 GUI 库,适合大型应用程序开发,功能强于 tkinter,整体设计框架类似于 MFC(Microsoft Foundation Classes 微软基础类库)。
  3. PyQT

Qt 是一种开源的 GUI 库,适合大型 GUI 程序开发,PyQT 是 Qt 工具包标准的 Python 实现。我们也可以使用 Qt Desginer 界面设计器快速开发 GUI 应用程序。

基于 tkinter 模块创建 GUI 程序包含如下 4 个核心步骤:

  1. 创建应用程序主窗口对象(也称:根窗口)
  2. 在主窗口中,添加各种可视化组件,比如:按钮(Button)、文本框(Label)等。
  3. 通过几何布局管理器,管理组件的大小和位置
  4. 事件处理
from tkinter import *
from tkinter import messagebox

# 1.创建应用程序主窗口对象(也称:根窗口)
root = Tk()
# 2.在主窗口中,添加各种可视化组件,比如:按钮(Button)、文本框(Label)等。
btn01 = Button(root)
btn01["text"] = "点击送花"
# 3.通过几何布局管理器,管理组件的大小和位置
btn01.pack()

# 4.事件处理
def songhua(e):  # e就是事件对象
    messagebox.showinfo("Message", "送你花")
    print("送花")

btn01.bind("<Button-1>", songhua)

root.mainloop()  # 调用组件的mainloop()方法,进入事件循环

PEP8编码规范-窗口大小和位置

主窗口位置和大小

通过 geometry('wxh±x±y')进行设置。w 为宽度,h 为高度。+x 表示距屏幕左边的距离;-x 表示距屏幕右边的距离;+y 表示距屏幕上边的距离;-y 表示距屏幕下边的距离。

from tkinter import *

root = Tk()
root.title("测试主窗口的位置和大小")
root.geometry("500x400+100+200")  # 宽度 500,高度 400;距屏幕左边 100,距屏幕上边200
root.mainloop()

GU编程整体描述-常用组件汇总

常用组件汇总列表

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多行文本框显示多行文本

GU程序的经典面向对象写法

本节程序也是 GUI 应用程序编写的一个主要结构,采用了面向对象的方式,更加合理的组织代码。

通过类 Application 组织整个 GUI 程序,类 Application 继承了 Frame 及通过继承拥有了父类的特性。通过构造函数__init__()初始化窗口中的对象,通过 createWidgets()方法创建窗口中的对象。

Frame 框架是一个 tkinter 组件,表示一个矩形的区域。Frame 一般作为容器使用,可以放置其他组件,从而实现复杂的布局。

"""测试一个经典的GUI程序的写法,使用面向对象的方式"""
from tkinter import *
from tkinter import messagebox

class Application(Frame):
    """一个经典的GUI程序类的写法"""

    def __init__(self, master):
        super().__init__(master)  # super代表的是父类的定义,而不是父类对象
        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)
        # 将组件放入application里
        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中图像正确显示全局变量写法

from tkinter import *
from tkinter import messagebox

class Application(Frame):
    """一个经典的GUI程序类的写法"""

    def __init__(self, master):
        super().__init__(master)  # super代表的是父类的定义,而不是父类对象
        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声明为全局变量,如果是局部变量,本方法执行完毕后,图像对象会被销毁,窗口显示不出图像
        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”)
  2. 创建对象后,使用字典索引方式
    fred[“fg”] = “red”
    fred[“bg”] = “blue”
  3. 创建对象后,使用 config()方法
    fred.config(fg=“red”, bg=“blue”)

Button-anchor位置控制

Button(按钮)用来执行用户的单击操作。Button 可以包含文本,也可以包含图像。按钮被单击后会自动调用对应事件绑定的方法。

from tkinter import *
from tkinter import messagebox

class Application(Frame):
    """一个经典的GUI程序类的写法"""

    def __init__(self, master):
        super().__init__(master)  # super代表的是父类的定义,而不是父类对象
        self.master = master
        # 几何布局管理器,管理组件的大小和位置
        self.pack()
        self.createWidget()

    def createWidget(self):
        """创建组件"""
        self.btn01 = Button(root, text="登录", anchor=E, command=self.login)  # anchor控制字体位置
        self.btn01.pack()
        # 显示图片
        global photo   # 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 控件。

from tkinter import *
from tkinter import messagebox

class Application(Frame):
    """一个经典的GUI程序类的写法"""

    def __init__(self, master):
        super().__init__(master)  # super代表的是父类的定义,而不是父类对象
        self.master = master
        # 几何布局管理器,管理组件的大小和位置
        self.pack()
        self.createWidget()

    def createWidget(self):
        """创建组件"""
        self.label01 = Label(self, text="用户名")
        self.label01.pack()

        # StringVar变量绑定到指定的组件
        # StringVar变量的值发生变化,组件内容也发生变化
        # 组件内容发生变化,StringVar变量的值也发生变化
        username = StringVar()
        self.entry01 = Entry(self, textvariable=username)
        self.entry01.pack()
        username.set("admin")
        print(username.get())       # admin
        print(self.entry01.get())   # admin

        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标记

"""测试 Text 多行文本框组件的基本用法,使用面向对象的方式"""

from tkinter import *
import webbrowser

class Application(Frame):
    def __init__(self, master=None):
        super().__init__(master)  # super()代表的是父类的定义,而不是父类对象
        self.master = master
        self.pack()
        self.createWidget()

    def createWidget(self):
        # 宽度 20 个字母(10 个汉字),高度一个行高
        self.w1 = Text(root, width=40, height=12, bg="gray")
        self.w1.pack()
        self.w1.insert(1.0, "0123456789\nabcdefg")  # 1.0 第一行第一列  行号以 1 开始 列号以 0 开始
        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):
        # INSERT 索引表示在光标处插入
        self.w1.insert(INSERT, ' Gaoqi ')
        # END 索引号表示在最后插入
        self.w1.insert(END, '[sxt]')
        self.w1.insert(1.8, "gaoqi")

    def returnText(self):
        # Indexes(索引)是用来指向 Text 组件中文本的位置,Text 的组件索引也是对应实际字符之间的位置。
        # 核心:行号以 1 开始 列号以 0 开始
        print(self.w1.get(1.2, 1.6))
        print("所有文本内容:\n" + self.w1.get(1.0, END))

    def addImage(self):
        # global photo
        self.photo = PhotoImage(file="../images/logo.gif")
        self.w1.image_create(END, image=self.photo)

    def addWidget(self):
        b1 = Button(self.w1, text='爱尚学堂')
        # 在 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")  # 改变tag的背景,字体颜色
        self.w1.tag_add("baidu", 4.0, 4.2)
        self.w1.tag_config("baidu", underline=True)  # tag加下划线
        self.w1.tag_bind("baidu", "<Button-1>", self.webshow)  # tag绑定事件

    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 控件用于选择同一组单选按钮中的一个。Radiobutton 可以显示文本,也可以显示图像。

"""测试 Radiobutton 组件的基本用法,使用面向对象的方式"""
from tkinter import *
from tkinter import messagebox

class Application(Frame):
    def __init__(self, master=None):
        super().__init__(master)   # super()代表的是父类的定义,而不是父类对象
        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 控件用于选择多个按钮的情况。Checkbutton 可以显示文本,也可以显示图像。

"""测试 Checkbutton 组件的基本用法,使用面向对象的方式"""
from tkinter import *
from tkinter import messagebox

class Application(Frame):
    def __init__(self, master=None):
        super().__init__(master)  # super()代表的是父类的定义,而不是父类对象
        self.master = master
        self.pack()
        self.createWidget()

    def createWidget(self):
        self.codeHobby = IntVar()
        self.videoHobby = IntVar()
        print(self.codeHobby.get())  # 默认值是 0
        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 的使用,更加详细和深入的内容将在后面的“图形绘制”章节讲解。

"""测试 Canvas 组件的基本用法,使用面向对象的方式"""
from tkinter import *
from tkinter import messagebox
import random

class Application(Frame):
    def __init__(self, master=None):
        super().__init__(master)  # super()代表的是父类的定义,而不是父类对象
        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)  # 三个点 (x, y)
        # 画一个矩形.
        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 提供了三种管理器:packgridplace

Grid布局管理器详解

grid 表格布局,采用表格结构组织组件。子组件的位置由行和列的单元格来确定,并且可以跨行和跨列,从而实现复杂的布局。

"""测试 Grid 布局管理器的基本用法,使用面向对象的方式"""
from tkinter import *
from tkinter import messagebox
import random

class Application(Frame):
    def __init__(self, master=None):
        super().__init__(master)  # super()代表的是父类的定义,而不是父类对象
        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()

计算器软件界面的设计

"""计算器软件界面的设计"""
from tkinter import *
from tkinter import messagebox
import random

class Application(Frame):
    def __init__(self, master=None):
        super().__init__(master)    # super()代表的是父类的定义,而不是父类对象
        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 == "=":                      # rowspan跨行,columnspan跨列
                    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 是代码量最少,最简单的一种,可以用于快速生成界面。

钢琴软件界面设计

# 测试 pack 布局管理
from tkinter import *

root = Tk()
root.geometry("700x220")
# Frame 是一个矩形区域,就是用来防止其他子组件
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 布局管理器可以通过坐标精确控制组件的位置,适用于一些布局更加灵活的场景。

# coding=utf-8
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()

事件处理

扑克游戏界面设计-增加事件操作

"""扑克牌游戏的界面设计"""

from tkinter import *

class Application(Frame):

    def __init__(self, master=None):
        super().__init__(master)  # super()代表的父类的定义,而不是父类对象
        self.master = master
        self.place()
        self.createWidget()

    def createWidget(self):
        """通过place布局管理器实现扑克牌位置控制"""
        # 显示一张扑克
        # self.photo = PhotoImage(file="../images/puke/puke1.gif")
        # self.puke1 = Label(self.master, image=self.photo)
        # self.puke1.place(x=10, y=50)
        # 用列表推导式显示所有图片
        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)

        # 为所有Label增加事件处理
        self.pukes[0].bind_class("Label", "<Button-1>", self.chupai)  # 绑定左键

    def chupai(self, event):
        print(event.widget.winfo_geometry())
        print(event.widget.winfo_y())   # 获得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 参数值列表:表达式

参数值列表即为输入。

表达式计算的结构即为输出。

# coding=utf-8
# 测试 command 属性绑定事件,测试 lambda 表达式帮助传参
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") # lambda 表达式帮助传参
root.mainloop()

三种事件绑定方式总结

多种事件绑定方式汇总

组件对象的绑定

  1. 通过 command 属性绑定(适合简单不需获取 event 对象)

Button(root,text="登录",command=login)

  1. 通过 bind()方法绑定(适合需要获取 event 对象)

c1 = Canvas(); c1.bind("<Button-1>",drawLine)

组件类的绑定

调用对象的 bind_class 函数,将该组件类所有的组件绑定事件:

w.bind_class("Widget","event",eventhanler)

比如:btn01.bind_class(“Button”,"<Button-1>",func)

# coding=utf-8
# 多种事件绑定方式汇总
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")
# bind 方式绑定事件
b1.bind("<Button-1>", mouseTest1)
# command 属性直接绑定事件
b2 = Button(root, text="测试 command2",
            command=lambda: mouseTest2("gaoqi", "xixi"))
b2.pack(side="left")
# 给所有 Button 按钮都绑定右键单击事件<Button-2>
b1.bind_class("Button", "<Button-2>", mouseTest3)
root.mainloop()

其他组件

optionmenu选项菜单

OptionMenu(选择项)用来做多选一,选中的项在顶部显示。

"""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())

# v.set("尚学堂")    # 直接修改了 optionmenu 中选中的值
Button(root, text="确定", command=test1).pack()
root.mainloop()

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

scale滑块

Scale(移动滑块)用于在指定的数值区间,通过滑块的移动来选择值。

""" 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()

颜色框

颜色选择框可以帮助我们设置背景色、前景色、画笔颜色、字体颜色等等。

"""askcolor 颜色选择框的测试,改变背景色"""
from tkinter import *
from tkinter.colorchooser import *

root = Tk()
root.geometry("400x150")

def test1():
    s1 = askcolor(color="red", title="选择背景色")
    print(s1)
    # s1 的值是:((0.0, 0.0, 255.99609375), '#0000ff')
    root.config(bg=s1[1])

Button(root, text="选择背景色", command=test1).pack()
root.mainloop()

文件选择框

文件对话框帮助我们实现可视化的操作目录、操作文件。最后,将文件、目录的信息传入到程序中。文件对话框包含如下一些常用函数:

"""文件对话框获取文件"""
from tkinter import *
from tkinter.filedialog import *

root = Tk();
root.geometry("400x100")

def test1():
    f = askopenfilename(title="上传文件",
                        initialdir="d:/", filetypes=[("视频文件", ".mp4")])
    # print(f)
    show["text"] = f

Button(root, text="选择编辑的视频文件", command=test1).pack()
show = Label(root, width=40, height=3, bg="green")
show.pack()
root.mainloop()

读取文件内容

# coding=utf-8
# askcolor 颜色选择框的测试,改变背景色
# 打开指定 txt 文件,并读出文件内容到窗口
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(最大值)。

"""简单对话框"""
from tkinter.simpledialog import *
from tkinter import *

root = Tk()
root.geometry("400x100")

def test1():
    a = askinteger(title="输入年龄", prompt="请输入年龄", initialvalue=18, minvalue=1, maxvalue=150)
    # askstring、askfloat 框使用方式一样
    show["text"] = a

Button(root, text="你多大了? 请输入", command=test1).pack()
show = Label(root, width=40, height=3, bg="green")
show.pack()
root.mainloop()

通用消息框

messagebox(通用消息框)用于和用户简单的交互,用户点击确定、取消。如下列出了

messagebox 的常见函数:

"""简单对话框"""
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 模块即可。

⚠️此处我们不展开细讲 ttk。如果你的项目确实需要用到复杂的界面,推荐大家使用wxpython 或者 pyQt.

菜单

主菜单

主菜单一般包含:文件、编辑、帮助等,位于 GUI 窗口的上面。创建主菜单一般有如下 4步:

  1. 创建主菜单栏对象
menubar = tk.Menu(root)
  1. 创建菜单,并添加到主菜单栏对象
file_menu = tk.Menu(menubar)
menubar.add_cascade(label="文件",menu=file_menu)
  1. 添加菜单项到 2 步中的菜单
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. 将主菜单栏添加到根窗口
root["menu"]=menubar
"""开发记事本软件的菜单"""
from tkinter import *
from tkinter.filedialog import *
from tkinter.colorchooser import *

class Application(Frame):
    def __init__(self, master=None):
        super().__init__(master)    # super()代表的是父类的定义,而不是父类对象
        self.master = master
        self.textpad = None         # textpad 表示 Text 文本框对象
        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. 创建菜单
menubar = tk.Menu(root)
menubar.add_command(label="字体")
  1. 绑定鼠标右键单击事件
def test(event):
    menubar.post(event.x_root,event.y_root)  #在鼠标右键单击坐标处显示菜单
    root.bind("<Button-3>",test)
"""开发记事本软件的菜单"""
from tkinter import *
from tkinter.filedialog import *
from tkinter.colorchooser import *

class Application(Frame):
    def __init__(self, master=None):
        super().__init__(master)    # super()代表的是父类的定义,而不是父类对象
        self.master = master
        self.textpad = None         # textpad 表示 Text 文本框对象
        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 记事本的软件。包含了基本的功能:

  1. 新建文本文件
  2. 保存文件
  3. 修改文件内容
  4. 退出
  5. 各种快捷键处理
  6. 修改文本区域背景色

【01】打开和保存修改文件的实现

【02】新建文件-背景色改变-快捷键功能

"""
开发一个简单的记事本。
包含:新建、保存、修改文本内容、退出
包含:各种快捷键的处理
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)  # super()代表的是父类的定义,而不是父类对象
        self.master = master
        self.textpad = None  # textpad 表示 Text 文本框对象
        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):
        # 每次打开文件之前清空text控件之前的内容
        self.textpad.delete(1.0, END)
        self.filename = asksaveasfilename(title="另存为", initialfile="未命名.txt",
                                          filetypes=[("文本文档", "*.txt")],
                                          defaultextension=".txt")
        self.savefile()

    def openfile(self):
        # 每次打开文件之前清空text控件之前的内容
        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="选择背景颜色")
        # 返回格式 ((0,0,0,255),"0000ff")
        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 文件。操作步骤如下:

  1. 安装 pyinstaller 模块
    在 pycharm 中操作:file–>setting–>Project:xxx -->Project interpretor,再点击+即可。
  2. 在 pycharm 的 Terminal 终端输入如下命令:
    pyinstaller -F xxxx.py
    ⚠️相关参数如下:
    –icon= 图标路径(pyinstaller -F --icon=my.ico XXXX.py
    -F 打包成一个 exe 文件
    -w 使用窗口,无控制台
    -c 使用控制台,无窗口
    -D 创建一个目录,里面包含 exe 以及其他一些依赖性文件
  3. 在项目的 dist 目录下可以看到生成了 exe 文件,直接在 windows 系统中使用即可

⚠️exe 文件本质是将 python 解释器和程序打包到了一起,这样我们执行程序时就不用管 windows 系统是不是有 python 解释器。

画图项目

开发一款简单的画图软件, 包含如下功能:

  1. 画笔
  2. 矩形/椭圆绘制
  3. 清屏
  4. 橡皮擦
  5. 直线/带箭头的直线
  6. 修改画笔颜色、背景颜色

【01】界面实现

【02】绘制直线-拖动删除上一个图形

【03】箭头直线-矩形绘制

【04】画笔和橡皮擦实现

【05】清屏-颜色框-快捷键处理

"""
开发画图软件的菜单
"""
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)  # super()代表的是父类的定义,而不是父类对象
        self.master = master
        self.bgcolor = bgcolor
        self.x = 0
        self.y = 0
        self.fgcolor = "#ff0000"
        # 表示最后绘制图像的id
        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="选择画笔颜色")
            # [(255,0,0),"#ff0000"]
            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模块的安装

pip install pygame

面向对象分析项目需求

坦克大战游戏的需求

1.顶目中有哪些类

2.每个类中有哪些方法

(1)坦克类(我方坦克、敌方坦克)

射击 ​ 移动类 ​ 显示坦克的方法

(2)子弹类

移动 ​ 显示子弹的方法

(3)墙壁类

属性:是否可以通过

(4)爆炸效果类

展示爆炸效果

(5)音效类

播放音乐

(6)主类

开始游戏

结束游戏

坦克大战项目框架搭建

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

加载主窗口

新增功能:
    加载主窗口
    pygame官方网址:www.pygame.org

坦克大战之事件处理

新增功能:
    添加事件
    1.点击关闭 关闭窗口
    2.按下键盘时候,判断键盘按下的是什么键,分别对不同的键做处理

左上角文字的绘制

新增功能:
    左上角文字绘制:
    左上角输出敌方坦克的数量6

加载我方坦克

新增功能:
    加载我方坦克

我方坦克切换方向移动

新增功能:
    1.我方坦克切换方向
    2.我方坦克移动

我方坦克移动优化

新增功能:
    优化我方坦克移动的方法

我方坦克优化2

新增功能:
    优化:按下方向键,坦克一直移动
         松开方向键,坦克停止

加载敌方坦克

新增功能:
    1.完善敌方坦克初始化方法
    2.创建敌方坦克并展示

敌方坦克随机移动

新增功能:
    1.优化左上角 文字的显示,将敌方坦克的数量进行修改
    2.敌方坦克随机移动
      思路:新增加一个变量步数,当移动时候步数进行递减
            当步数<=0时候修改敌方坦克的方向,并将步数复位

完善子弹类

新增功能:
     完善子弹类

我方坦克发射子弹

新增功能:
     完成我方坦克发射子弹

子弹移动

新增功能:
     完成我方坦克发射子弹,并完成子弹的移动

子弹消亡及数量控制

新增功能:
     优化:1.如果子弹碰到墙壁,让子弹消失
           2.最多可以发射3颗子弹,不能一直发射

敌方坦克发射子弹

新增功能:
     优化:1.如果子弹碰到墙壁,让子弹消失
           2.最多可以发射3颗子弹,不能一直发射

我方子弹与敌方坦克的碰撞

新增功能:
     我方子弹与敌方坦克的碰撞
       精灵类 Sprite类

实现爆炸效果

新增功能:
     1.完善爆炸效果类
     2.在窗口中展示爆炸效果

我方坦克的消亡

新增功能:
     1.敌方子弹与我方坦克的碰撞
     2.添加爆炸效果

我方坦克无限重生

新增功能:
    让我方坦克无限重生
      1.按下键盘的Esc让重生
      2.重生及重新创建我方坦克

加载墙壁

新增功能:
     添加墙壁
     1.完善墙壁类,初始化方法
     2.初始化墙壁,并将墙壁存储到列表中,在窗口中加载墙壁

子弹不穿墙

新增功能:
     1.子弹不能穿墙
        子弹碰撞到墙壁时候,让子弹消失
     2.墙壁被击中,墙壁的生命值处理

坦克不能穿墙

新增功能:
     1.坦克不能穿墙
        坦克碰撞到墙壁,不能再移动
        不能再移动及坐标不能发生变化

敌我双方坦克发生碰撞

新增功能:
     1.我方坦克与敌方坦克发生碰撞
       让我方坦克不能再继续移动   stay()
     2.敌方坦克与我方坦克发生碰撞
       让地方坦克不能再移动   stay()

音效处理

新增功能:
     1.完善音效类
     2.添加开场音效
     3.我方坦克发射子弹添加音效

坦克大战完整代码

# coding=utf-8
"""
pygame模块的安装
面向对象分析项目需求
坦克大战项目框架搭建
加载主窗口
坦克大战之事件处理
左上角文字的绘制
加载我方坦克
我方坦克切换方向移动
我方坦克移动优化
我方坦克优化2
加载敌方坦克
敌方坦克随机移动
完善子单类
我方坦克发射子弹
子弹移动
子弹消亡及数量控制
敌方坦克发射子弹
我方子弹与敌方坦克的碰撞
实现爆炸效果
我方坦克的消亡
我方坦克无限重生
加载墙壁
子弹不穿墙
坦克不能穿墙
敌我双方坦克发生碰撞
音效处理
"""
import pygame, time, random
from pygame.sprite import Sprite

SCREEN_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 = 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()
                # 敌方子弹是否为None,如果不为None则添加到敌方子弹列表
                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()
        # 查看所有字体名称
        # print(pygame.font.get_fonts())
        # 获取字体font对象
        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:
                    # 如果按下的是ESC键,让我方坦克重生
                    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.move()
                        # 修改坦克的的开关
                        MainGame.my_tank.stop = False
                        print("按下左键,坦克左移")
                    elif event.key == pygame.K_RIGHT:
                        # 切换方向
                        MainGame.my_tank.direction = "R"
                        # MainGame.my_tank.move()
                        # 修改坦克的的开关
                        MainGame.my_tank.stop = False
                        print("按下右键,坦克右移")
                    elif event.key == pygame.K_UP:
                        # 切换方向
                        MainGame.my_tank.direction = "U"
                        # MainGame.my_tank.move()
                        # 修改坦克的的开关
                        MainGame.my_tank.stop = False
                        print("按下上键,坦克上移")
                    elif event.key == pygame.K_DOWN:
                        # 切换方向
                        MainGame.my_tank.direction = "D"
                        # 修改坦克的的开关
                        MainGame.my_tank.stop = False
                        # MainGame.my_tank.move()
                        print("按下下键,坦克下移")
                    elif event.key == pygame.K_SPACE:
                        print("发射子弹")
                        # 如果当前我方子弹列表的大小<3时候才可以创建
                        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):
    # 添加距离左边left 距离上边top
    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()
        # 设置区域的left和top
        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]
        # 调用blit方法展示
        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()
        # 设置区域的left和top
        self.rect.left = left
        self.rect.top = top
        # 速度决定移动的快慢
        self.speed = 5
        # 坦克移动的开关
        self.stop = True
        # 新增加一个步数变量 step
        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

    # 重写shot()
    def shot(self):
        # 随机生成100以内的数
        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()
        # 子弹的left和top和方向有关
        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):
        # 将图片surface加载到窗口
        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()
        # 设置位置left和top
        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()
    # MainGame().getTextSuface()

以上就是“Python深入和提高”的全部内容,希望对你有所帮助。

关于Python技术储备

学好 Python 不论是就业还是做副业赚钱都不错,但要学会 Python 还是要有一个学习规划。最后大家分享一份全套的 Python 学习资料,给那些想学习 Python 的小伙伴们一点帮助!

一、Python所有方向的学习路线

Python所有方向的技术点做的整理,形成各个领域的知识点汇总,它的用处就在于,你可以按照上面的知识点去找对应的学习资源,保证自己学得较为全面。

在这里插入图片描述

二、Python必备开发工具

img

三、Python视频合集

观看零基础学习视频,看视频学习是最快捷也是最有效果的方式,跟着视频中老师的思路,从基础到深入,还是很容易入门的。

img

四、实战案例

光学理论是没用的,要学会跟着一起敲,要动手实操,才能将自己的所学运用到实际当中去,这时候可以搞点实战案例来学习。

img

五、Python练习题

检查学习结果。

img

六、面试资料

我们学习Python必然是为了找到高薪的工作,下面这些面试题是来自阿里、腾讯、字节等一线互联网大厂最新的面试资料,并且有阿里大佬给出了权威的解答,刷完这一套面试资料相信大家都能找到满意的工作。

img

最后祝大家天天进步!!

上面这份完整版的Python全套学习资料已经上传至CSDN官方,朋友如果需要可以直接微信扫描下方CSDN官方认证二维码免费领取【保证100%免费】。

  • 17
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值