提交 703a9c16 编写于 作者: 骆昊的技术专栏's avatar 骆昊的技术专栏

update so many files

上级 8cc34921
"""
输入学生考试成绩计算平均分
Version: 0.1
Author: 骆昊
Date: 2018-03-06
"""
def main():
number = int(input('请输入学生人数: '))
names = [None] * number
scores = [None] * number
for index in range(len(names)):
names[index] = input('请输入第%d个学生的名字: ' % (index + 1))
scores[index] = float(input('请输入第%d个学生的成绩: ' % (index + 1)))
total = 0
for index in range(len(names)):
print('%s: %.1f分' % (names[index], scores[index]))
total += scores[index]
print('平均成绩是: %.1f分' % (total / number))
if __name__ == '__main__':
main()
"""
定义和使用字典
Version: 0.1
Author: 骆昊
Date: 2018-03-06
"""
def main():
scores = {'骆昊': 95, '白元芳': 78, '狄仁杰': 82}
print(scores['骆昊'])
print(scores['狄仁杰'])
for elem in scores:
print('%s\t--->\t%d' % (elem, scores[elem]))
scores['白元芳'] = 65
scores['诸葛王朗'] = 71
scores.update(冷面=67, 方启鹤=85)
print(scores)
if '武则天' in scores:
print(scores['武则天'])
print(scores.get('武则天'))
print(scores.get('武则天', 60))
print(scores.popitem())
print(scores.popitem())
print(scores.pop('骆昊', 100))
scores.clear()
print(scores)
if __name__ == '__main__':
main()
"""
字典的常用操作
Version: 0.1
Author: 骆昊
Date: 2018-03-06
"""
def main():
stu = {'name': '骆昊', 'age': 38, 'gender': True}
print(stu)
print(stu.keys())
print(stu.values())
print(stu.items())
for elem in stu.items():
print(elem)
print(elem[0], elem[1])
if 'age' in stu:
stu['age'] = 20
print(stu)
stu.setdefault('score', 60)
print(stu)
stu.setdefault('score', 100)
print(stu)
stu['score'] = 100
print(stu)
if __name__ == '__main__':
main()
"""
生成斐波拉切数列
Version: 0.1
Author: 骆昊
Date: 2018-03-06
"""
def main():
f = [1 , 1]
for i in range(2, 20):
f += [f[i - 1] + f[i - 2]]
# f.append(f[i - 1] + f[i - 2])
for val in f:
print(val, end=' ')
if __name__ == '__main__':
main()
"""
找出列表中最大或最小的元素
Version: 0.1
Author: 骆昊
Date: 2018-03-06
"""
def main():
fruits = ['grape', 'apple', 'strawberry', 'waxberry', 'pitaya']
# 直接使用内置的max和min函数找出列表中最大和最小元素
# print(max(fruits))
# print(min(fruits))
max_value = min_value = fruits[0]
for index in range(1, len(fruits)):
if fruits[index] > max_value:
max_value = fruits[index]
elif fruits[index] < min_value:
min_value = fruits[index]
print('Max:', max_value)
print('Min:', min_value)
if __name__ == '__main__':
main()
# 想一想如果最大的元素有两个要找出第二大的又该怎么做
"""
定义和使用列表
- 用下标访问元素
- 添加元素
- 删除元素
Version: 0.1
Author: 骆昊
Date: 2018-03-06
"""
def main():
fruits = ['grape', '@pple', 'strawberry', 'waxberry']
print(fruits)
# 通过下标访问元素
print(fruits[0])
print(fruits[1])
print(fruits[-1])
print(fruits[-2])
# print(fruits[-5]) # IndexError
# print(fruits[4]) # IndexError
fruits[1] = 'apple'
print(fruits)
# 添加元素
fruits.append('pitaya')
fruits.insert(0, 'banana')
print(fruits)
# 删除元素
del fruits[1]
fruits.pop()
fruits.pop(0)
fruits.remove('apple')
print(fruits)
if __name__ == '__main__':
main()
"""
列表常用操作
- 列表连接
- 获取长度
- 遍历列表
- 列表切片
- 列表排序
- 列表反转
- 查找元素
Version: 0.1
Author: 骆昊
Date: 2018-03-06
"""
def main():
fruits = ['grape', 'apple', 'strawberry', 'waxberry']
fruits += ['pitaya', 'pear', 'mango']
# 循环遍历列表元素
for fruit in fruits:
print(fruit.title(), end=' ')
print()
# 列表切片
fruits2 = fruits[1:4]
print(fruits2)
# fruit3 = fruits # 没有复制列表只创建了新的引用
fruits3 = fruits[:]
print(fruits3)
fruits4 = fruits[-3:-1]
print(fruits4)
fruits5 = fruits[::-1]
print(fruits5)
if __name__ == '__main__':
main()
"""
生成列表
- 用range创建数字列表
- 生成表达式
- 生成器
Version: 0.1
Author: 骆昊
Date: 2018-03-06
"""
# 生成Fibonacci序列的生成器
def fib(n):
a, b = 0, 1
for _ in range(n):
a, b = b, a + b
yield a
def main():
# 用range创建数值列表
list1 = list(range(1, 11))
print(list1)
# 生成表达式
list2 = [x * x for x in range(1, 11)]
print(list2)
list3 = [m + n for m in 'ABCDEFG' for n in '12345']
print(list3)
print(len(list3))
# 生成器(节省空间但生成下一个元素时需要花费时间)
gen = (m + n for m in 'ABCDEFG' for n in '12345')
print(gen)
for elem in gen:
print(elem, end=' ')
print()
gen = fib(20)
print(gen)
for elem in gen:
print(elem, end=' ')
print()
if __name__ == '__main__':
main()
"""
双色球随机选号程序
Version: 0.1
Author: 骆昊
Date: 2018-03-06
"""
from random import randrange, randint, sample
def display(balls):
"""
输出列表中的双色球号码
"""
for index, ball in enumerate(balls):
if index == len(balls) - 1:
print('|', end=' ')
print('%02d' % ball, end=' ')
print()
def random_select():
"""
随机选择一组号码
"""
red_balls = [x for x in range(1, 34)]
selected_balls = []
for _ in range(6):
index = randrange(len(red_balls))
selected_balls.append(red_balls[index])
del red_balls[index]
# 上面的for循环也可以写成下面这行代码
# sample函数是random模块下的函数
# selected_balls = sample(red_balls, 6)
selected_balls.sort()
selected_balls.append(randint(1, 16))
return selected_balls
def main():
n = int(input('机选几注: '))
for _ in range(n):
display(random_select())
if __name__ == '__main__':
main()
"""
输入学生考试成绩计算平均分
Version: 0.1
Author: 骆昊
Date: 2018-03-06
"""
import os
import time
def main():
str = 'Welcome to 1000 Phone Chengdu Campus '
while True:
print(str)
time.sleep(0.2)
str = str[1:] + str[0:1]
# for Windows use os.system('cls') instead
os.system('clear')
if __name__ == '__main__':
main()
"""
学生考试成绩表
Version: 0.1
Author: 骆昊
Date: 2018-03-06
"""
def main():
names = ['关羽', '张飞', '赵云', '马超', '黄忠']
subjs = ['语文', '数学', '英语']
scores = [[0] * 3] * 5
for row, name in enumerate(names):
print('请输入%s的成绩' % name)
for col, subj in enumerate(subjs):
scores[row][col] = float(input(subj + ': '))
print(scores)
# for row, name in enumerate(names):
# print('请输入%s的成绩' % name)
# scores[row] = [None] * len(subjs)
# for col, subj in enumerate(subjs):
# score = float(input(subj + ': '))
# scores[row][col] = score
# print(scores)
if __name__ == '__main__':
main()
"""
定义和使用集合
Version: 0.1
Author: 骆昊
Date: 2018-03-06
"""
def main():
set1 = {1, 2, 3, 3, 3, 2}
print(set1)
print('Length =', len(set1))
set2 = set(range(1, 10))
print(set2)
set1.add(4)
set1.add(5)
set2.update([11, 12])
print(set1)
print(set2)
set2.discard(5)
# remove的元素如果不存在会引发KeyError
if 4 in set2:
set2.remove(4)
print(set2)
# 遍历集合容器
for elem in set2:
print(elem ** 2, end=' ')
print()
# 将元组转换成集合
set3 = set((1, 2, 3, 3, 2, 1))
print(set3.pop())
print(set3)
if __name__ == '__main__':
main()
"""
集合的常用操作
- 交集
- 并集
- 差集
- 子集
- 超集
Version: 0.1
Author: 骆昊
Date: 2018-03-06
"""
def main():
set1 = set(range(1, 7))
print(set1)
set2 = set(range(2, 11, 2))
print(set2)
set3 = set(range(1, 5))
print(set1 & set2)
# print(set1.intersection(set2))
print(set1 | set2)
# print(set1.union(set2))
print(set1 - set2)
# print(set1.difference(set2))
print(set1 ^ set2)
# print(set1.symmetric_difference(set2))
print(set2 <= set1)
# print(set2.issubset(set1))
print(set3 <= set1)
# print(set3.issubset(set1))
print(set1 >= set2)
# print(set1.issuperset(set2))
print(set1 >= set3)
# print(set1.issuperset(set3))
if __name__ == '__main__':
main()
"""
井字棋游戏
Version: 0.1
Author: 骆昊
Date: 2018-03-06
"""
import os
def print_board(board):
print(board['TL'] + '|' + board['TM'] + '|' + board['TR'])
print('-+-+-')
print(board['ML'] + '|' + board['MM'] + '|' + board['MR'])
print('-+-+-')
print(board['BL'] + '|' + board['BM'] + '|' + board['BR'])
def main():
init_board = {
'TL': ' ', 'TM': ' ', 'TR': ' ',
'ML': ' ', 'MM': ' ', 'MR': ' ',
'BL': ' ', 'BM': ' ', 'BR': ' '
}
begin = True
while begin:
curr_board = init_board.copy()
begin = False
turn = 'x'
counter = 0
os.system('clear')
print_board(curr_board)
while counter < 9:
move = input('轮到%s走棋, 请输入位置: ' % turn)
if curr_board[move] == ' ':
counter += 1
curr_board[move] = turn
if turn == 'x':
turn = 'o'
else:
turn = 'x'
os.system('clear')
print_board(curr_board)
choice = input('再玩一局?(yes|no)')
begin = choice == 'yes'
if __name__ == '__main__':
main()
"""
元组的定义和使用
Version: 0.1
Author: 骆昊
Date: 2018-03-06
"""
def main():
# 定义元组
t = ('骆昊', 38, True, '四川成都')
print(t)
# 获取元组中的元素
print(t[0])
print(t[1])
print(t[2])
print(t[3])
# 遍历元组中的值
for member in t:
print(member)
# 重新给元组赋值
# t[0] = '王大锤' # TypeError
# 变量t重新引用了新的元组 原来的元组被垃圾回收
t = ('王大锤', 20, True, '云南昆明')
print(t)
# 元组和列表的转换
person = list(t)
print(person)
person[0] = '李小龙'
person[1] = 25
print(person)
fruits_list = ['apple', 'banana', 'orange']
fruits_tuple = tuple(fruits_list)
print(fruits_tuple)
print(fruits_tuple[1])
if __name__ == '__main__':
main()
\ No newline at end of file
"""
输出10行的杨辉三角 - 二项式的n次方展开系数
1
1 1
1 2 1
1 3 3 1
1 4 6 4 1
... ... ...
Version: 0.1
Author: 骆昊
Date: 2018-03-06
"""
def main():
num = int(input('Number of rows: '))
yh = [[]] * num
for row in range(len(yh)):
yh[row] = [None] * (row + 1)
for col in range(len(yh[row])):
if col == 0 or col == row:
yh[row][col] = 1
else:
yh[row][col] = yh[row - 1][col] + yh[row - 1][col - 1]
print(yh[row][col], end='\t')
print()
if __name__ == '__main__':
main()
## 字符串和常用数据结构
### 使用字符串
第二次世界大战促使了现代电子计算机的诞生,当初的想法很简单,就是用计算机来计算导弹的弹道,因此在计算机刚刚诞生的那个年代,计算机处理的信息主要是数值,而世界上的第一台电子计算机ENIAC每秒钟能够完成约5000次浮点运算。随着时间的推移,虽然对数值运算仍然是计算机日常工作中最为重要的事情之一,但是今天的计算机处理得更多的数据都是以文本信息的方式存在的,而Python表示文本信息的方式我们在很早以前就说过了,那就是字符串类型。所谓**字符串**,就是由零个或多个字符组成的有限序列,一般记为[$${\displaystyle s=a_{1}a_{2}\dots a_{n}(0\leq n \leq \infty)}$$](https://wikimedia.org/api/rest_v1/media/math/render/svg/e29bf631b090323edd6889f810e6cff29538b161)
我们可以通过下面的代码来了解字符串的使用。
```Python
def main():
str1 = 'hello, world!'
# 通过len函数计算字符串的长度
print(len(str1)) # 13
# 获得字符串首字母大写的拷贝
print(str1.capitalize()) # Hello, world!
# 获得字符串变大写后的拷贝
print(str1.upper()) # HELLO, WORLD!
# 从字符串中查找子串所在位置
print(str1.find('or')) # 8
print(str1.find('shit')) # -1
# 与find类似但找不到子串时会引发异常
# print(str1.index('or'))
# print(str1.index('shit'))
# 检查字符串是否以指定的字符串开头
print(str1.startswith('He')) # False
print(str1.startswith('hel')) # True
# 检查字符串是否以指定的字符串结尾
print(str1.endswith('!')) # True
# 将字符串以指定的宽度居中并在两侧填充指定的字符
print(str1.center(50, '*'))
# 将字符串以指定的宽度靠右放置左侧填充指定的字符
print(str1.rjust(50, ' '))
str2 = 'abc123456'
# 从字符串中取出指定位置的字符(下标运算)
print(str2[2]) # c
# 字符串切片(从指定的开始索引到指定的结束索引)
print(str2[2:5]) # c12
print(str2[2:]) # c123456
print(str2[2::2]) # c246
print(str2[::2]) # ac246
print(str2[::-1]) # 654321cba
print(str2[-3:-1]) # 45
# 检查字符串是否由数字构成
print(str2.isdigit()) # False
# 检查字符串是否以字母构成
print(str2.isalpha()) # False
# 检查字符串是否以数字和字母构成
print(str2.isalnum()) # True
str3 = ' jackfrued@126.com '
print(str3)
# 获得字符串修剪左右两侧空格的拷贝
print(str3.strip())
if __name__ == '__main__':
main()
```
除了字符串,Python还内置了多种类型的数据结构,如果要在程序中保存和操作数据,绝大多数时候可以利用现有的数据结构来实现,最常用的包括列表、元组、集合和字典。
### 使用列表
下面的代码演示了如何定义列表、使用下标访问列表元素以及添加和删除元素的操作。
```Python
def main():
list1 = [1, 3, 5, 7, 100]
print(list1)
list2 = ['hello'] * 5
print(list2)
# 计算列表长度(元素个数)
print(len(list1))
# 下标(索引)运算
print(list1[0])
print(list1[4])
# print(list1[5]) # IndexError: list index out of range
print(list1[-1])
print(list1[-3])
list1[2] = 300
print(list1)
# 添加元素
list1.append(200)
list1.insert(1, 400)
list1 += [1000, 2000]
print(list1)
print(len(list1))
# 删除元素
list1.remove(3)
if 1234 in list1:
list1.remove(1234)
del list1[0]
print(list1)
# 清空列表元素
list1.clear()
print(list1)
if __name__ == '__main__':
main()
```
和字符串一样,列表也可以做切片操作,通过切片操作我们可以实现对列表的复制或者将列表中的一部分取出来创建出新的列表,代码如下所示。
```Python
def main():
fruits = ['grape', 'apple', 'strawberry', 'waxberry']
fruits += ['pitaya', 'pear', 'mango']
# 循环遍历列表元素
for fruit in fruits:
print(fruit.title(), end=' ')
print()
# 列表切片
fruits2 = fruits[1:4]
print(fruits2)
# fruit3 = fruits # 没有复制列表只创建了新的引用
# 可以通过完整切片操作来复制列表
fruits3 = fruits[:]
print(fruits3)
fruits4 = fruits[-3:-1]
print(fruits4)
# 可以通过反向切片操作来获得倒转后的列表的拷贝
fruits5 = fruits[::-1]
print(fruits5)
if __name__ == '__main__':
main()
```
下面的代码实现了对列表的排序操作。
```Python
def main():
list1 = ['orange', 'apple', 'zoo', 'internationalization', 'blueberry']
list2 = sorted(list1)
# sorted函数返回列表排序后的拷贝不会修改传入的列表
# 函数的设计就应该像sorted函数一样尽可能不产生副作用
list3 = sorted(list1, reverse=True)
# 通过key关键字参数指定根据字符串长度进行排序而不是默认的字母表顺序
list4 = sorted(list1, key=len)
print(list1)
print(list2)
print(list3)
print(list4)
# 给列表对象发出排序消息直接在列表对象上进行排序
list1.sort(reverse=True)
print(list1)
if __name__ == '__main__':
main()
```
我们还可以使用列表的生成式语法来创建列表,代码如下所示。
```Python
import sys
def main():
f = [x for x in range(1, 10)]
print(f)
f = [x + y for x in 'ABCDE' for y in '1234567']
print(f)
# 用列表的生成表达式语法创建列表容器
# 用这种语法创建列表之后元素已经准备就绪所以需要耗费较多的内存空间
f = [x ** 2 for x in range(1, 1000)]
print(sys.getsizeof(f)) # 查看对象占用内存的字节数
print(f)
# 请注意下面的代码创建的不是一个列表而是一个生成器对象
# 通过生成器可以获取到数据但它不占用额外的空间存储数据
# 每次需要数据的时候就通过内部的运算得到数据(需要花费额外的时间)
f = (x ** 2 for x in range(1, 1000))
print(sys.getsizeof(f)) # 相比生成式生成器不占用存储数据的空间
print(f)
for val in f:
print(val)
if __name__ == '__main__':
main()
```
除了上面提到的生成器语法,Python中还有另外一种定义生成器的方式,就是通过`yield`关键字将一个普通函数改造成生成器函数。下面的代码演示了如何实现一个生成[斐波拉切数列](https://zh.wikipedia.org/wiki/%E6%96%90%E6%B3%A2%E9%82%A3%E5%A5%91%E6%95%B0%E5%88%97)的生成器。所谓斐波拉切数列可以通过下面[递归](https://zh.wikipedia.org/wiki/%E9%80%92%E5%BD%92)的方法来进行定义:
$${\displaystyle F_{0}=0}$$
$${\displaystyle F_{1}=1}$$
$${\displaystyle F_{n}=F_{n-1}+F_{n-2}}({n}\geq{2})$$
![](./res/fibonacci-blocks.png)
```Python
def fib(n):
a, b = 0, 1
for _ in range(n):
a, b = b, a + b
yield a
def main():
for val in fib(20):
print(val)
if __name__ == '__main__':
main()
```
### 使用元组
Python 的元组与列表类似,不同之处在于元组的元素不能修改,在前面的代码中我们已经不止一次使用过元组了。顾名思义,我们把多个元素组合到一起就形成了一个元组,所以它和列表一样可以保存多条数据。下面的代码演示了如何定义和使用元组。
```Python
def main():
# 定义元组
t = ('骆昊', 38, True, '四川成都')
print(t)
# 获取元组中的元素
print(t[0])
print(t[3])
# 遍历元组中的值
for member in t:
print(member)
# 重新给元组赋值
# t[0] = '王大锤' # TypeError
# 变量t重新引用了新的元组原来的元组将被垃圾回收
t = ('王大锤', 20, True, '云南昆明')
print(t)
# 将元组转换成列表
person = list(t)
print(person)
# 列表是可以修改它的元素的
person[0] = '李小龙'
person[1] = 25
print(person)
# 将列表转换成元组
fruits_list = ['apple', 'banana', 'orange']
fruits_tuple = tuple(fruits_list)
print(fruits_tuple)
if __name__ == '__main__':
main()
```
这里有一个非常值得探讨的问题,我们已经有了列表这种数据结构,为什么还需要元组这样的类型呢?
1. 元组中的元素是无法修改的,事实上我们在项目中尤其是[多线程](https://zh.wikipedia.org/zh-hans/%E5%A4%9A%E7%BA%BF%E7%A8%8B)环境(后面会讲到)中可能更喜欢使用的是那些不变对象(一方面因为对象状态不能修改,所以可以避免由此引起的不必要的程序错误,简单的说就是一个不变的对象要比可变的对象更加容易维护;另一方面因为没有任何一个线程能够修改不变对象的内部状态,一个不变对象自动就是线程安全的,这样就可以省掉处理同步化的开销。一个不变对象可以方便的被共享访问)。所以结论就是:如果不需要对元素进行添加、删除、修改的时候,可以考虑使用元组,当然如果一个方法要返回多个值,使用元组也是不错的选择。
2. 元组在创建时间和占用的空间上面都优于列表。我们可以使用sys模块的getsizeof函数来检查存储同样的元素的元组和列表各自占用了多少内存空间,这个很容易做到。我们也可以在ipython中使用魔法指令%timeit来分析创建同样内容的元组和列表所花费的时间,下图是我的macOS系统上测试的结果。
![](./res/ipython-timeit.png)
### 使用集合
Python中的集合跟数学上的集合是一致的,不允许有重复元素,而且可以进行交集、并集、差集等运算。
![](./res/python-set.png)
```Python
def main():
set1 = {1, 2, 3, 3, 3, 2}
print(set1)
print('Length =', len(set1))
set2 = set(range(1, 10))
print(set2)
set1.add(4)
set1.add(5)
set2.update([11, 12])
print(set1)
print(set2)
set2.discard(5)
# remove的元素如果不存在会引发KeyError
if 4 in set2:
set2.remove(4)
print(set2)
# 遍历集合容器
for elem in set2:
print(elem ** 2, end=' ')
print()
# 将元组转换成集合
set3 = set((1, 2, 3, 3, 2, 1))
print(set3.pop())
print(set3)
# 集合的交集、并集、差集、对称差运算
print(set1 & set2)
# print(set1.intersection(set2))
print(set1 | set2)
# print(set1.union(set2))
print(set1 - set2)
# print(set1.difference(set2))
print(set1 ^ set2)
# print(set1.symmetric_difference(set2))
# 判断子集和超集
print(set2 <= set1)
# print(set2.issubset(set1))
print(set3 <= set1)
# print(set3.issubset(set1))
print(set1 >= set2)
# print(set1.issuperset(set2))
print(set1 >= set3)
# print(set1.issuperset(set3))
if __name__ == '__main__':
main()
```
> **说明**:Python中允许通过一些特殊的方法来为某种类型或数据结构自定义运算符(后面的章节中会讲到),上面的代码中我们对集合进行运算的时候可以调用集合对象的方法,也可以直接使用对应的运算符,例如`&`运算符跟intersection方法的作用就是一样的,但是使用运算符让代码更加直观。
### 使用字典
字典是另一种可变容器模型,类似于我们生活中使用的字典,它可以存储任意类型对象,与列表、集合不同的是,字典的每个元素都是由一个键和一个值组成的“键值对”,键和值通过冒号分开。下面的代码演示了如何定义和使用字典。
```Python
def main():
scores = {'骆昊': 95, '白元芳': 78, '狄仁杰': 82}
# 通过键可以获取字典中对应的值
print(scores['骆昊'])
print(scores['狄仁杰'])
# 对字典进行遍历(遍历的其实是键再通过键取对应的值)
for elem in scores:
print('%s\t--->\t%d' % (elem, scores[elem]))
# 更新字典中的元素
scores['白元芳'] = 65
scores['诸葛王朗'] = 71
scores.update(冷面=67, 方启鹤=85)
print(scores)
if '武则天' in scores:
print(scores['武则天'])
print(scores.get('武则天'))
# get方法也是通过键获取对应的值但是可以设置默认值
print(scores.get('武则天', 60))
# 删除字典中的元素
print(scores.popitem())
print(scores.popitem())
print(scores.pop('骆昊', 100))
# 清空字典
scores.clear()
print(scores)
if __name__ == '__main__':
main()
```
### 练习
#### 练习1:在屏幕上显示跑马灯文字
```Python
import os
import time
def main():
content = '北京欢迎你为你开天辟地…………'
while True:
# 清理屏幕上的输出
os.system('cls') # os.system('clear')
print(content)
# 休眠200毫秒
time.sleep(0.2)
content = content[1:] + content[0]
if __name__ == '__main__':
main()
```
#### 练习2:设计一个函数产生指定长度的验证码,验证码由大小写字母和数字构成。
```Python
import random
def generate_code(code_len=4):
"""
生成指定长度的验证码
:param code_len: 验证码的长度(默认4个字符)
:return: 由大小写英文字母和数字构成的随机验证码
"""
all_chars = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
last_pos = len(all_chars) - 1
code = ''
for _ in range(code_len):
index = random.randint(0, last_pos)
code += all_chars[index]
return code
```
#### 练习3:设计一个函数返回给定文件名的后缀名。
```Python
def get_suffix(filename, has_dot=False):
"""
获取文件名的后缀名
:param filename: 文件名
:param has_dot: 返回的后缀名是否需要带点
:return: 文件的后缀名
"""
pos = filename.rfind('.')
if 0 < pos < len(filename) - 1:
index = pos if has_dot else pos + 1
return filename[index:]
else:
return ''
```
#### 练习4:设计一个函数返回传入的列表中最大和第二大的元素的值。
```Python
def max2(x):
m1, m2 = (x[0], x[1]) if x[0] > x[1] else (x[1], x[0])
for index in range(2, len(x)):
if x[index] > m1:
m2 = m1
m1 = x[index]
elif x[index] > m2:
m2 = x[index]
return m1, m2
```
#### 练习5:计算指定的年月日是这一年的第几天
```Python
def is_leap_year(year):
"""
判断指定的年份是不是闰年
:param year: 年份
:return: 闰年返回True平年返回False
"""
return year % 4 == 0 and year % 100 != 0 or year % 400 == 0
def which_day(year, month, date):
"""
计算传入的日期是这一年的第几天
:param year: 年
:param month: 月
:param date: 日
:return: 第几天
"""
days_of_month = [
[31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31],
[31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
][is_leap_year(year)]
total = 0
for index in range(month - 1):
total += days_of_month[index]
return total + date
def main():
print(which_day(1980, 11, 28))
print(which_day(1981, 12, 31))
print(which_day(2018, 1, 1))
print(which_day(2016, 3, 1))
if __name__ == '__main__':
main()
```
#### 练习6:打印[杨辉三角](https://zh.wikipedia.org/wiki/%E6%9D%A8%E8%BE%89%E4%B8%89%E8%A7%92%E5%BD%A2)。
```Python
def main():
num = int(input('Number of rows: '))
yh = [[]] * num
for row in range(len(yh)):
yh[row] = [None] * (row + 1)
for col in range(len(yh[row])):
if col == 0 or col == row:
yh[row][col] = 1
else:
yh[row][col] = yh[row - 1][col] + yh[row - 1][col - 1]
print(yh[row][col], end='\t')
print()
if __name__ == '__main__':
main()
```
### 综合案例
#### 案例1:双色球选号
```Python
from random import randrange, randint, sample
def display(balls):
"""
输出列表中的双色球号码
"""
for index, ball in enumerate(balls):
if index == len(balls) - 1:
print('|', end=' ')
print('%02d' % ball, end=' ')
print()
def random_select():
"""
随机选择一组号码
"""
red_balls = [x for x in range(1, 34)]
selected_balls = []
for _ in range(6):
index = randrange(len(red_balls))
selected_balls.append(red_balls[index])
del red_balls[index]
# 上面的for循环也可以写成下面这行代码
# sample函数是random模块下的函数
# selected_balls = sample(red_balls, 6)
selected_balls.sort()
selected_balls.append(randint(1, 16))
return selected_balls
def main():
n = int(input('机选几注: '))
for _ in range(n):
display(random_select())
if __name__ == '__main__':
main()
```
> **说明**:可以使用random模块的sample函数来实现从列表中选择不重复的n个元素。
#### 综合案例2:[约瑟夫环问题](https://zh.wikipedia.org/wiki/%E7%BA%A6%E7%91%9F%E5%A4%AB%E6%96%AF%E9%97%AE%E9%A2%98)
```Python
"""
《幸运的基督徒》
有15个基督徒和15个非基督徒在海上遇险,为了能让一部分人活下来不得不将其中15个人扔到海里面去,有个人想了个办法就是大家围成一个圈,由某个人开始从1报数,报到9的人就扔到海里面,他后面的人接着从1开始报数,报到9的人继续扔到海里面,直到扔掉15个人。由于上帝的保佑,15个基督徒都幸免于难,问这些人最开始是怎么站的,哪些位置是基督徒哪些位置是非基督徒。
"""
def main():
persons = [True] * 30
counter, index, number = 0, 0, 0
while counter < 15:
if persons[index]:
number += 1
if number == 9:
persons[index] = False
counter += 1
number = 0
index += 1
index %= 30
for person in persons:
print('基' if person else '非', end='')
if __name__ == '__main__':
main()
```
#### 综合案例3:[井字棋](https://zh.wikipedia.org/wiki/%E4%BA%95%E5%AD%97%E6%A3%8B)游戏
```Python
import os
def print_board(board):
print(board['TL'] + '|' + board['TM'] + '|' + board['TR'])
print('-+-+-')
print(board['ML'] + '|' + board['MM'] + '|' + board['MR'])
print('-+-+-')
print(board['BL'] + '|' + board['BM'] + '|' + board['BR'])
def main():
init_board = {
'TL': ' ', 'TM': ' ', 'TR': ' ',
'ML': ' ', 'MM': ' ', 'MR': ' ',
'BL': ' ', 'BM': ' ', 'BR': ' '
}
begin = True
while begin:
curr_board = init_board.copy()
begin = False
turn = 'x'
counter = 0
os.system('clear')
print_board(curr_board)
while counter < 9:
move = input('轮到%s走棋, 请输入位置: ' % turn)
if curr_board[move] == ' ':
counter += 1
curr_board[move] = turn
if turn == 'x':
turn = 'o'
else:
turn = 'x'
os.system('clear')
print_board(curr_board)
choice = input('再玩一局?(yes|no)')
begin = choice == 'yes'
if __name__ == '__main__':
main()
```
>**说明**:最后这个案例来自[《Python编程快速上手:让繁琐工作自动化》](https://item.jd.com/11943853.html)一书(这本书对有编程基础想迅速使用Python将日常工作自动化的人来说还是不错的教材),对代码做了一点点的调整。
\ No newline at end of file
class Test:
def __init__(self, foo):
self.__foo = foo
def __bar(self):
print(self.__foo)
print('__bar')
def main():
test = Test('hello')
test._Test__bar()
print(test._Test__foo)
if __name__ == "__main__":
main()
"""
练习
修一个游泳池 半径(以米为单位)在程序运行时输入 游泳池外修一条3米宽的过道
过道的外侧修一圈围墙 已知过道的造价为25元每平米 围墙的造价为32.5元每米
输出围墙和过道的总造价分别是多少钱(精确到小数点后2位)
Version: 0.1
Author: 骆昊
Date: 2018-03-08
"""
import math
class Circle(object):
def __init__(self, radius):
self._radius = radius
@property
def radius(self):
return self._radius
@radius.setter
def radius(self, radius):
self._radius = radius if radius > 0 else 0
@property
def perimeter(self):
return 2 * math.pi * self._radius
@property
def area(self):
return math.pi * self._radius * self._radius
if __name__ == '__main__':
radius = float(input('请输入游泳池的半径: '))
small = Circle(radius)
big = Circle(radius + 3)
print('围墙的造价为: ¥%.1f元' % (big.perimeter * 115))
print('过道的造价为: ¥%.1f元' % ((big.area - small.area) * 65))
"""
定义和使用时钟类
Version: 0.1
Author: 骆昊
Date: 2018-03-08
"""
import time
import os
class Clock(object):
# Python中的函数是没有重载的概念的
# 因为Python中函数的参数没有类型而且支持缺省参数和可变参数
# 用关键字参数让构造器可以传入任意多个参数来实现其他语言中的构造器重载
def __init__(self, **kw):
if 'hour' in kw and 'minute' in kw and 'second' in kw:
self._hour = kw['hour']
self._minute = kw['minute']
self._second = kw['second']
else:
tm = time.localtime(time.time())
self._hour = tm.tm_hour
self._minute = tm.tm_min
self._second = tm.tm_sec
def run(self):
self._second += 1
if self._second == 60:
self._second = 0
self._minute += 1
if self._minute == 60:
self._minute = 0
self._hour += 1
if self._hour == 24:
self._hour = 0
def show(self):
return '%02d:%02d:%02d' % (self._hour, self._minute, self._second)
if __name__ == '__main__':
# clock = Clock(hour=10, minute=5, second=58)
clock = Clock()
while True:
os.system('clear')
print(clock.show())
time.sleep(1)
clock.run()
"""
面向对象版本的猜数字游戏
Version: 0.1
Author: 骆昊
Date: 2018-03-08
"""
from random import randint
class GuessMachine(object):
def __init__(self):
self._answer = None
self._counter = None
self._hint = None
def reset(self):
self._answer = randint(1, 100)
self._counter = 0
self._hint = None
def guess(self, your_answer):
self._counter += 1
if your_answer > self._answer:
self._hint = '小一点'
elif your_answer < self._answer:
self._hint = '大一点'
else:
self._hint = '恭喜你猜对了'
return True
return False
@property
def counter(self):
return self._counter
@property
def hint(self):
return self._hint
if __name__ == '__main__':
gm = GuessMachine()
play_again = True
while play_again:
game_over = False
gm.reset()
while not game_over:
your_answer = int(input('请输入: '))
game_over = gm.guess(your_answer)
print(gm.hint)
if gm.counter > 7:
print('智商余额不足!')
play_again = input('再玩一次?(yes|no)') == 'yes'
"""
另一种创建类的方式
Version: 0.1
Author: 骆昊
Date: 2018-03-08
"""
def bar(self, name):
self._name = name
def foo(self, course_name):
print('%s正在学习%s.' % (self._name, course_name))
def main():
Student = type('Student', (object,), dict(__init__=bar, study=foo))
stu1 = Student('骆昊')
stu1.study('Python程序设计')
if __name__ == '__main__':
main()
"""
定义和使用矩形类
Version: 0.1
Author: 骆昊
Date: 2018-03-08
"""
class Rect(object):
"""矩形类"""
def __init__(self, width=0, height=0):
"""构造器"""
self.__width = width
self.__height = height
def perimeter(self):
"""计算周长"""
return (self.__width + self.__height) * 2
def area(self):
"""计算面积"""
return self.__width * self.__height
def __str__(self):
"""矩形对象的字符串表达式"""
return '矩形[%f,%f]' % (self.__width, self.__height)
def __del__(self):
"""析构器"""
print('销毁矩形对象')
if __name__ == '__main__':
rect1 = Rect()
print(rect1)
print(rect1.perimeter())
print(rect1.area())
rect2 = Rect(3.5, 4.5)
print(rect2)
print(rect2.perimeter())
print(rect2.area())
"""
定义和使用学生类
Version: 0.1
Author: 骆昊
Date: 2018-03-08
"""
def _foo():
print('test')
class Student(object):
# __init__是一个特殊方法用于在创建对象时进行初始化操作
# 通过这个方法我们可以为学生对象绑定name和age两个属性
def __init__(self, name, age):
self.name = name
self.age = age
def study(self, course_name):
print('%s正在学习%s.' % (self.name, course_name))
# PEP 8要求标识符的名字用全小写多个单词用下划线连接
# 但是很多程序员和公司更倾向于使用驼峰命名法(驼峰标识)
def watch_av(self):
if self.age < 18:
print('%s只能观看《熊出没》.' % self.name)
else:
print('%s正在观看岛国爱情动作片.' % self.name)
def main():
stu1 = Student('骆昊', 38)
stu1.study('Python程序设计')
stu1.watch_av()
stu2 = Student('王大锤', 15)
stu2.study('思想品德')
stu2.watch_av()
if __name__ == '__main__':
main()
\ No newline at end of file
## 面向对象编程基础
活在当下的程序员应该都听过“面向对象编程”一词,也经常有人问能不能用一句话解释下什么是“面向对象编程”,我们先来看看比较正式的说法。
> 把一组数据结构和处理它们的方法组成对象(object),把相同行为的对象归纳为类(class),通过类的封装(encapsulation)隐藏内部细节,通过继承(inheritance)实现类的特化(specialization)和泛化(generalization),通过多态(polymorphism)实现基于对象类型的动态分派。
这样一说是不是更不明白了。所以我们还是看看更通俗易懂的说法,下面这段内容来自于[知乎](https://www.zhihu.com/)
![](./res/oop-zhihu.png)
> **说明**:以上的内容来自于网络,不代表作者本人的观点和看法,与作者本人立场无关,相关责任不由作者承担。(终于有机会享受一下把这段话反过来说的乐趣了,乐得牙都快碎了。)
之前我们说过“程序是指令的集合”,我们在程序中书写的语句在执行时会变成一条或多条指令然后由CPU去执行。当然为了简化程序的设计,我们引入了函数的概念,把相对独立且经常重复使用的代码放置到函数中,在需要使用这些功能的时候只要调用函数即可;如果一个函数的功能过于复杂和臃肿,我们又可以进一步将函数继续切分为子函数来降低系统的复杂性。但是说了这么多,不知道大家是否发现,所谓编程就是程序员按照计算机的工作方式控制计算机完成各种任务。但是,计算机的工作方式与正常人类的思维模式是不同的,如果编程就必须得抛弃人类正常的思维方式去迎合计算机,编程的乐趣就少了很多,“每个人都应该学习编程”这样的豪言壮语就只能说说而已。当然,这些还不是最重要的,最重要的是当我们需要开发一个复杂的系统时,代码的复杂性会让开发和维护工作都变得举步维艰,所以在上世纪60年代末期,“[软件危机](https://zh.wikipedia.org/wiki/%E8%BD%AF%E4%BB%B6%E5%8D%B1%E6%9C%BA)”、“[软件工程](https://zh.wikipedia.org/wiki/%E8%BD%AF%E4%BB%B6%E5%B7%A5%E7%A8%8B)”等一系列的概念开始在行业中出现。
当然,程序员圈子内的人都知道,现实中并没有解决上面所说的这些问题的“[银弹](https://zh.wikipedia.org/wiki/%E6%B2%A1%E6%9C%89%E9%93%B6%E5%BC%B9)”,真正让软件开发者看到希望的是上世纪70年代诞生的[Smalltalk](https://zh.wikipedia.org/wiki/Smalltalk)编程语言中引入的面向对象的编程思想(面向对象编程的雏形可以追溯到更早期的[Simula](https://zh.wikipedia.org/wiki/Simula)语言)。按照这种编程理念,程序中的数据和操作数据的函数是一个逻辑上的整体,我们称之为“对象”,而我们解决问题的方式就是创建出需要的对象并向对象发出各种各样的消息,多个对象的协同工作最终可以让我们构造出复杂的系统来解决现实中的问题。
> **说明**:当然面向对象也不是解决软件开发中所有问题的最后的“银弹”,所以今天的高级程序设计语言几乎都提供了对多种编程范式的支持,Python也不例外。
### 类和对象
简单的说,类是对象的蓝图和模板,而对象是类的实例。这个解释虽然有点像用概念在解释概念,但是从这句话我们至少可以看出,类是抽象的概念,而对象是具体的东西。在面向对象编程的世界中,一切皆为对象,对象都有属性和行为,每个对象都是独一无二的,而且对象一定属于某个类(型)。当我们把一大堆拥有共同特征的对象的静态特征(属性)和动态特征(行为)都抽取出来后,就可以定义出一个叫做“类”的东西。
![](./res/object-feature.png)
### 定义类
在Python中可以使用`class`关键字定义类,然后在类中通过之前学习过的函数来定义方法,这样就可以将对象的动态特征描述出来,代码如下所示。
```Python
class Student(object):
# __init__是一个特殊方法用于在创建对象时进行初始化操作
# 通过这个方法我们可以为学生对象绑定name和age两个属性
def __init__(self, name, age):
self.name = name
self.age = age
def study(self, course_name):
print('%s正在学习%s.' % (self.name, course_name))
# PEP 8要求标识符的名字用全小写多个单词用下划线连接
# 但是很多程序员和公司更倾向于使用驼峰命名法(驼峰标识)
def watch_av(self):
if self.age < 18:
print('%s只能观看《熊出没》.' % self.name)
else:
print('%s正在观看岛国爱情动作片.' % self.name)
```
> **说明**:写在类中的函数,我们通常称之为(对象的)方法,这些方法就是对象可以接收的消息。
### 创建和使用对象
当我们定义好一个类之后,可以通过下面的方式来创建对象并给对象发消息。
```Python
def main():
# 创建学生对象并指定姓名和年龄
stu1 = Student('骆昊', 38)
# 给对象发study消息
stu1.study('Python程序设计')
# 给对象发watch_av消息
stu1.watch_av()
stu2 = Student('王大锤', 15)
stu2.study('思想品德')
stu2.watch_av()
if __name__ == '__main__':
main()
```
### 访问可见性问题
对于上面的代码,有C++、Java、C#等编程经验的程序员可能会问,我们给`Student`对象绑定的`name``age`属性到底具有怎样的访问权限(也称为可见性)。因为在很多面向对象编程语言中,我们通常会将对象的属性设置为私有的(private)或受保护的(protected),简单的说就是不允许外界访问,而对象的方法通常都是公开的(public),因为公开的方法就是对象能够接受的消息。在Python中,属性和方法的访问权限只有两种,也就是公开的和私有的,如果希望属性是私有的,在给属性命名时可以用两个下划线作为开头,下面的代码可以验证这一点。
```Python
class Test:
def __init__(self, foo):
self.__foo = foo
def __bar(self):
print(self.__foo)
print('__bar')
def main():
test = Test('hello')
# AttributeError: 'Test' object has no attribute '__bar'
test.__bar()
# AttributeError: 'Test' object has no attribute '__foo'
print(test.__foo)
if __name__ == "__main__":
main()
```
但是,Python并没有从语法上严格保证私有属性或方法的私密性,它只是给私有的属性和方法换了一个名字来“妨碍”对它们的访问,事实上如果你知道更换名字的规则仍然可以访问到它们,下面的代码就可以验证这一点。之所以这样设定,可以用这样一句名言加以解释,就是“We are all consenting adults here”。因为绝大多数程序员都认为开放比封闭要好,而且程序员要自己为自己的行为负责。
```Python
class Test:
def __init__(self, foo):
self.__foo = foo
def __bar(self):
print(self.__foo)
print('__bar')
def main():
test = Test('hello')
test._Test__bar()
print(test._Test__foo)
if __name__ == "__main__":
main()
```
在实际开发中,我们并不建议将属性设置为私有的,因为这会导致子类无法访问(后面会讲到)。所以大多数Python程序员会遵循一种命名惯例就是让属性名以单下划线开头来表示属性是受保护的,本类之外的代码在访问这样的属性时应该要保持慎重。这种做法并不是语法上的规则,单下划线开头的属性和方法外界仍然是可以访问的,所以更多的时候它是一种暗示或隐喻,关于这一点可以看看我的[《Python - 那些年我们踩过的那些坑》](http://blog.csdn.net/jackfrued/article/details/79521404)文章中的讲解。
### 面向对象的支柱
面向对象有三大支柱:封装、继承和多态。后面两个概念在下一个章节中进行详细的说明,这里我们先说一下什么是封装。我自己对封装的理解是“隐藏一切可以隐藏的实现细节,只向外界暴露(提供)简单的编程接口”。我们在类中定义的方法其实就是把数据和对数据的操作封装起来了,在我们创建了对象之后,只需要给对象发送一个消息(调用方法)就可以执行方法中的代码,也就是说我们只需要知道方法的名字和传入的参数(方法的外部视图),而不需要知道方法内部的实现细节(方法的内部视图)。
### 练习
#### 练习1:定义一个类描述数字时钟
```Python
class Clock(object):
"""
数字时钟
"""
def __init__(self, hour=0, minute=0, second=0):
"""
构造器
:param hour: 时
:param minute: 分
:param second: 秒
"""
self._hour = hour
self._minute = minute
self._second = second
def run(self):
"""走字"""
self._second += 1
if self._second == 60:
self._second = 0
self._minute += 1
if self._minute == 60:
self._minute = 0
self._hour += 1
if self._hour == 24:
self._hour = 0
def __str__(self):
"""显示时间"""
return '%02d:%02d:%02d' % \
(self._hour, self._minute, self._second)
def main():
clock = Clock(23, 59, 58)
while True:
print(clock)
sleep(1)
clock.run()
if __name__ == '__main__':
main()
```
#### 练习2:定义一个类描述平面上的点并提供移动点和计算到另一个点距离的方法。
```Python
from math import sqrt
class Point(object):
def __init__(self, x=0, y=0):
"""
构造器
:param x: 横坐标
:param y: 纵坐标
"""
self.x = x
self.y = y
def move_to(self, x, y):
"""
移动到指定位置
:param x: 新的横坐标
"param y: 新的纵坐标
"""
self.x = x
self.y = y
def move_by(self, dx, dy):
"""
移动指定的增量
:param dx: 横坐标的增量
"param dy: 纵坐标的增量
"""
self.x += dx
self.y += dy
def distance_to(self, other):
"""
计算与另一个点的距离
:param other: 另一个点
"""
dx = self.x - other.x
dy = self.y - other.y
return sqrt(dx ** 2 + dy ** 2)
def __str__(self):
return '(%s, %s)' % (str(self.x), str(self.y))
def main():
p1 = Point(3, 5)
p2 = Point()
print(p1)
print(p2)
p2.move_by(-1, 2)
print(p2)
print(p1.distance_to(p2))
if __name__ == '__main__':
main()
```
> **说明**:本章中的插图来自于Grady Booch等著作的[《面向对象分析与设计》](https://item.jd.com/20476561918.html)一书,该书是讲解面向对象编程的经典著作,有兴趣的读者可以购买和阅读这本书来了解更多的面向对象的相关知识。
\ No newline at end of file
"""
对象之间的关联关系
Version: 0.1
Author: 骆昊
Date: 2018-03-12
"""
from math import sqrt
class Point(object):
def __init__(self, x=0, y=0):
self._x = x
self._y = y
def move_to(self, x, y):
self._x = x
self._y = y
def move_by(self, dx, dy):
self._x += dx
self._y += dy
def distance_to(self, other):
dx = self._x - other._x
dy = self._y - other._y
return sqrt(dx ** 2 + dy ** 2)
def __str__(self):
return '(%s, %s)' % (str(self._x), str(self._y))
class Line(object):
def __init__(self, start=Point(0, 0), end=Point(0, 0)):
self._start = start
self._end = end
@property
def start(self):
return self._start
@start.setter
def start(self, start):
self._start = start
@property
def end(self):
return self.end
@end.setter
def end(self, end):
self._end = end
@property
def length(self):
return self._start.distance_to(self._end)
if __name__ == '__main__':
p1 = Point(3, 5)
print(p1)
p2 = Point(-2, -1.5)
print(p2)
line = Line(p1, p2)
print(line.length)
line.start.move_to(2, 1)
line.end = Point(1, 2)
print(line.length)
"""
属性的使用
- 访问器/修改器/删除器
- 使用__slots__对属性加以限制
Version: 0.1
Author: 骆昊
Date: 2018-03-12
"""
class Car(object):
__slots__ = ('_brand', '_max_speed')
def __init__(self, brand, max_speed):
self._brand = brand
self._max_speed = max_speed
@property
def brand(self):
return self._brand
@brand.setter
def brand(self, brand):
self._brand = brand
@brand.deleter
def brand(self):
del self._brand
@property
def max_speed(self):
return self._max_speed
@max_speed.setter
def max_speed(self, max_speed):
if max_speed < 0:
raise ValueError('Invalid max speed for car')
self._max_speed = max_speed
def __str__(self):
return 'Car: [品牌=%s, 最高时速=%d]' % (self._brand, self._max_speed)
car = Car('QQ', 120)
print(car)
# ValueError
# car.max_speed = -100
car.max_speed = 320
car.brand = "Benz"
# 使用__slots__属性限制后下面的代码将产生异常
# car.current_speed = 80
print(car)
# 如果提供了删除器可以执行下面的代码
# del car.brand
# 属性的实现
print(Car.brand)
print(Car.brand.fget)
print(Car.brand.fset)
print(Car.brand.fdel)
# 通过上面的代码帮助学生理解之前提到的包装器的概念
# Python中有很多类似的语法糖后面还会出现这样的东西
"""
属性的使用
- 使用已有方法定义访问器/修改器/删除器
Version: 0.1
Author: 骆昊
Date: 2018-03-12
"""
class Car(object):
def __init__(self, brand, max_speed):
self.set_brand(brand)
self.set_max_speed(max_speed)
def get_brand(self):
return self._brand
def set_brand(self, brand):
self._brand = brand
def get_max_speed(self):
return self._max_speed
def set_max_speed(self, max_speed):
if max_speed < 0:
raise ValueError('Invalid max speed for car')
self._max_speed = max_speed
def __str__(self):
return 'Car: [品牌=%s, 最高时速=%d]' % (self._brand, self._max_speed)
# 用已有的修改器和访问器定义属性
brand = property(get_brand, set_brand)
max_speed = property(get_max_speed, set_max_speed)
car = Car('QQ', 120)
print(car)
# ValueError
# car.max_speed = -100
car.max_speed = 320
car.brand = "Benz"
print(car)
print(Car.brand)
print(Car.brand.fget)
print(Car.brand.fset)
from time import time, localtime, sleep
class Clock(object):
"""数字时钟"""
def __init__(self, hour=0, minute=0, second=0):
self._hour = hour
self._minute = minute
self._second = second
@classmethod
def now(cls):
ctime = localtime(time())
return cls(ctime.tm_hour, ctime.tm_min, ctime.tm_sec)
def run(self):
"""走字"""
self._second += 1
if self._second == 60:
self._second = 0
self._minute += 1
if self._minute == 60:
self._minute = 0
self._hour += 1
if self._hour == 24:
self._hour = 0
def show(self):
"""显示时间"""
return '%02d:%02d:%02d' % \
(self._hour, self._minute, self._second)
def main():
clock = Clock.now()
while True:
print(clock.show())
sleep(1)
clock.run()
if __name__ == '__main__':
main()
"""
对象之间的依赖关系和运算符重载
Version: 0.1
Author: 骆昊
Date: 2018-03-12
"""
class Car(object):
def __init__(self, brand, max_speed):
self._brand = brand
self._max_speed = max_speed
self._current_speed = 0
@property
def brand(self):
return self._brand
def accelerate(self, delta):
self._current_speed += delta
if self._current_speed > self._max_speed:
self._current_speed = self._max_speed
def brake(self):
self._current_speed = 0
def __str__(self):
return '%s当前时速%d' % (self._brand, self._current_speed)
class Student(object):
def __init__(self, name, age):
self._name = name
self._age = age
@property
def name(self):
return self._name
# 学生和车之间存在依赖关系 - 学生使用了汽车
def drive(self, car):
print('%s驾驶着%s欢快的行驶在去西天的路上' % (self._name, car._brand))
car.accelerate(30)
print(car)
car.accelerate(50)
print(car)
car.accelerate(50)
print(car)
def study(self, course_name):
print('%s正在学习%s.' % (self._name, course_name))
def watch_av(self):
if self._age < 18:
print('%s只能观看《熊出没》.' % self._name)
else:
print('%s正在观看岛国爱情动作片.' % self._name)
# 重载大于(>)运算符
def __gt__(self, other):
return self._age > other._age
# 重载小于(<)运算符
def __lt__(self, other):
return self._age < other._age
if __name__ == '__main__':
stu1 = Student('骆昊', 38)
stu1.study('Python程序设计')
stu1.watch_av()
stu2 = Student('王大锤', 15)
stu2.study('思想品德')
stu2.watch_av()
car = Car('QQ', 120)
stu2.drive(car)
print(stu1 > stu2)
print(stu1 < stu2)
"""
多重继承
- 菱形继承(钻石继承)
- C3算法(替代DFS的算法)
Version: 0.1
Author: 骆昊
Date: 2018-03-12
"""
class A(object):
def foo(self):
print('foo of A')
class B(A):
pass
class C(A):
def foo(self):
print('foo fo C')
class D(B, C):
pass
class E(D):
def foo(self):
print('foo in E')
super().foo()
super(B, self).foo()
super(C, self).foo()
if __name__ == '__main__':
d = D()
d.foo()
e = E()
e.foo()
"""
抽象类 / 方法重写 / 多态
实现一个工资结算系统 公司有三种类型的员工
- 部门经理固定月薪12000元/月
- 程序员按本月工作小时数每小时100元
- 销售员1500元/月的底薪加上本月销售额5%的提成
输入员工的信息 输出每位员工的月薪信息
Version: 0.1
Author: 骆昊
Date: 2018-03-12
"""
from abc import ABCMeta, abstractmethod
class Employee(object, metaclass=ABCMeta):
def __init__(self, name):
self._name = name
@property
def name(self):
return self._name
@abstractmethod
def get_salary(self):
pass
class Manager(Employee):
# 想一想: 如果不定义构造方法会怎么样
def __init__(self, name):
# 想一想: 如果不调用父类构造器会怎么样
super().__init__(name)
def get_salary(self):
return 12000
class Programmer(Employee):
def __init__(self, name):
super().__init__(name)
def set_working_hour(self, working_hour):
self._working_hour = working_hour
def get_salary(self):
return 100 * self._working_hour
class Salesman(Employee):
def __init__(self, name):
super().__init__(name)
def set_sales(self, sales):
self._sales = sales
def get_salary(self):
return 1500 + self._sales * 0.05
if __name__ == '__main__':
emps = [Manager('武则天'), Programmer('狄仁杰'), Salesman('白元芳')]
for emp in emps:
if isinstance(emp, Programmer):
working_hour = int(input('请输入%s本月工作时间: ' % emp.name))
emp.set_working_hour(working_hour)
elif isinstance(emp, Salesman):
sales = float(input('请输入%s本月销售额: ' % emp.name))
emp.set_sales(sales)
print('%s本月月薪为: ¥%.2f元' % (emp.name, emp.get_salary()))
"""
多重继承
- 通过多重继承可以给一个类的对象具备多方面的能力
- 这样在设计类的时候可以避免设计太多层次的复杂的继承关系
Version: 0.1
Author: 骆昊
Date: 2018-03-12
"""
class Father(object):
def __init__(self, name):
self._name = name
def gamble(self):
print('%s在打麻将.' % self._name)
def eat(self):
print('%s在大吃大喝.' % self._name)
class Monk(object):
def __init__(self, name):
self._name = name
def eat(self):
print('%s在吃斋.' % self._name)
def chant(self):
print('%s在念经.' % self._name)
class Musician(object):
def __init__(self, name):
self._name = name
def eat(self):
print('%s在细嚼慢咽.' % self._name)
def play_piano(self):
print('%s在弹钢琴.' % self._name)
# 试一试下面的代码看看有什么区别
# class Son(Monk, Father, Musician):
# class Son(Musician, Father, Monk):
class Son(Father, Monk, Musician):
def __init__(self, name):
Father.__init__(self, name)
Monk.__init__(self, name)
Musician.__init__(self, name)
son = Son('王大锤')
son.gamble()
# 调用继承自Father的eat方法
son.eat()
son.chant()
son.play_piano()
from abc import ABCMeta, abstractmethod
class Pet(object, metaclass=ABCMeta):
def __init__(self, nickname):
self._nickname = nickname
@abstractmethod
def make_voice(self):
pass
class Dog(Pet):
def make_voice(self):
print('%s: 汪汪汪...' % self._nickname)
class Cat(Pet):
def make_voice(self):
print('%s: 喵...喵...' % self._nickname)
def main():
pets = [Dog('旺财'), Cat('凯蒂'), Dog('大黄')]
for pet in pets:
pet.make_voice()
if __name__ == '__main__':
main()
"""
运算符重载 - 自定义分数类
Version: 0.1
Author: 骆昊
Date: 2018-03-12
"""
from math import gcd
class Rational(object):
def __init__(self, num, den=1):
if den == 0:
raise ValueError('分母不能为0')
self._num = num
self._den = den
self.normalize()
def simplify(self):
x = abs(self._num)
y = abs(self._den)
factor = gcd(x, y)
if factor > 1:
self._num //= factor
self._den //= factor
return self
def normalize(self):
if self._den < 0:
self._den = -self._den
self._num = -self._num
return self
def __add__(self, other):
new_num = self._num * other._den + other._num * self._den
new_den = self._den * other._den
return Rational(new_num, new_den).simplify().normalize()
def __sub__(self, other):
new_num = self._num * other._den - other._num * self._den
new_den = self._den * other._den
return Rational(new_num, new_den).simplify().normalize()
def __mul__(self, other):
new_num = self._num * other._num
new_den = self._den * other._den
return Rational(new_num, new_den).simplify().normalize()
def __truediv__(self, other):
new_num = self._num * other._den
new_den = self._den * other._num
return Rational(new_num, new_den).simplify().normalize()
def __str__(self):
if self._num == 0:
return '0'
elif self._den == 1:
return str(self._num)
else:
return '(%d/%d)' % (self._num, self._den)
if __name__ == '__main__':
r1 = Rational(2, 3)
print(r1)
r2 = Rational(6, -8)
print(r2)
print(r2.simplify())
print('%s + %s = %s' % (r1, r2, r1 + r2))
print('%s - %s = %s' % (r1, r2, r1 - r2))
print('%s * %s = %s' % (r1, r2, r1 * r2))
print('%s / %s = %s' % (r1, r2, r1 / r2))
此差异已折叠。
"""
继承的应用
- 抽象类
- 抽象方法
- 方法重写
- 多态
Version: 0.1
Author: 骆昊
Date: 2018-03-12
"""
from abc import ABCMeta, abstractmethod
from math import pi
class Shape(object, metaclass=ABCMeta):
@abstractmethod
def perimeter(self):
pass
@abstractmethod
def area(self):
pass
class Circle(Shape):
def __init__(self, radius):
self._radius = radius
def perimeter(self):
return 2 * pi * self._radius
def area(self):
return pi * self._radius ** 2
def __str__(self):
return '我是一个圆'
class Rect(Shape):
def __init__(self, width, height):
self._width = width
self._height = height
def perimeter(self):
return 2 * (self._width + self._height)
def area(self):
return self._width * self._height
def __str__(self):
return '我是一个矩形'
if __name__ == '__main__':
shapes = [Circle(5), Circle(3.2), Rect(3.2, 6.3)]
for shape in shapes:
print(shape)
print('周长:', shape.perimeter())
print('面积:', shape.area())
"""
实例方法和类方法的应用
Version: 0.1
Author: 骆昊
Date: 2018-03-12
"""
from math import sqrt
class Triangle(object):
def __init__(self, a, b, c):
self._a = a
self._b = b
self._c = c
# 静态方法
@staticmethod
def is_valid(a, b, c):
return a + b > c and b + c > a and c + a > b
# 实例方法
def perimeter(self):
return self._a + self._b + self._c
# 实例方法
def area(self):
p = self.perimeter() / 2
return sqrt(p * (p - self._a) * (p - self._b) * (p - self._c))
if __name__ == '__main__':
# 用字符串的split方法将字符串拆分成一个列表
# 再通过map函数对列表中的每个字符串进行映射处理成小数
a, b, c = map(float, input('请输入三条边: ').split())
# 先判断给定长度的三条边能否构成三角形
# 如果能才创建三角形对象
if Triangle.is_valid(a, b, c):
tri = Triangle(a, b, c)
print('周长:', tri.perimeter())
print('面积:', tri.area())
# 如果传入对象作为方法参数也可以通过类调用实例方法
# print('周长:', Triangle.perimeter(tri))
# print('面积:', Triangle.area(tri))
# 看看下面的代码就知道其实二者本质上是一致的
# print(type(tri.perimeter))
# print(type(Triangle.perimeter))
else:
print('不能构成三角形.')
此差异已折叠。
from enum import Enum, unique
from math import sqrt
from random import randint
import pygame
@unique
class Color(Enum):
"""颜色"""
RED = (255, 0, 0)
GREEN = (0, 255, 0)
BLUE = (0, 0, 255)
BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
GRAY = (242, 242, 242)
@staticmethod
def random_color():
"""获得随机颜色"""
r = randint(0, 255)
g = randint(0, 255)
b = randint(0, 255)
return (r, g, b)
class Ball(object):
"""球"""
def __init__(self, x, y, radius, sx, sy, color=Color.RED):
"""初始化方法"""
self.x = x
self.y = y
self.radius = radius
self.sx = sx
self.sy = sy
self.color = color
self.alive = True
def move(self, screen):
"""移动"""
self.x += self.sx
self.y += self.sy
if self.x - self.radius <= 0 or self.x + self.radius >= screen.get_width():
self.sx = -self.sx
if self.y - self.radius <= 0 or self.y + self.radius >= screen.get_height():
self.sy = -self.sy
def eat(self, other):
"""吃其他球"""
if self.alive and other.alive and self != other:
dx, dy = self.x - other.x, self.y - other.y
distance = sqrt(dx ** 2 + dy ** 2)
if distance < self.radius + other.radius \
and self.radius > other.radius:
other.alive = False
self.radius = self.radius + int(other.radius * 0.146)
def draw(self, screen):
"""在窗口上绘制球"""
pygame.draw.circle(screen, self.color,
(self.x, self.y), self.radius, 0)
def main():
# 定义用来装所有球的容器
balls = []
# 初始化导入的pygame中的模块
pygame.init()
# 初始化用于显示的窗口并设置窗口尺寸
screen = pygame.display.set_mode((800, 600))
print(screen.get_width())
print(screen.get_height())
# 设置当前窗口的标题
pygame.display.set_caption('大球吃小球')
# 定义变量来表示小球在屏幕上的位置
x, y = 50, 50
running = True
# 开启一个事件循环处理发生的事件
while running:
# 从消息队列中获取事件并对事件进行处理
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
if event.type == pygame.MOUSEBUTTONDOWN and event.button == 1:
x, y = event.pos
radius = randint(10, 100)
sx, sy = randint(-10, 10), randint(-10, 10)
color = Color.random_color()
ball = Ball(x, y, radius, sx, sy, color)
balls.append(ball)
screen.fill((255, 255, 255))
for ball in balls:
if ball.alive:
ball.draw(screen)
else:
balls.remove(ball)
pygame.display.flip()
# 每隔50毫秒就改变小球的位置再刷新窗口
pygame.time.delay(50)
for ball in balls:
ball.move(screen)
for other in balls:
ball.eat(other)
if __name__ == '__main__':
main()
"""
使用tkinter创建GUI
- 顶层窗口
- 控件
- 布局
- 事件回调
Version: 0.1
Author: 骆昊
Date: 2018-03-14
"""
import tkinter
import tkinter.messagebox
def main():
flag = True
# 修改标签上的文字
def change_label_text():
nonlocal flag
flag = not flag
color, msg = ('red', 'Hello, world!')\
if flag else ('blue', 'Goodbye, world!')
label.config(text=msg, fg=color)
# 确认退出
def confirm_to_quit():
if tkinter.messagebox.askokcancel('温馨提示', '确定要退出吗?'):
top.quit()
# 创建顶层窗口
top = tkinter.Tk()
# 设置窗口大小
top.geometry('240x160')
# 设置窗口标题
top.title('小游戏')
# 创建标签对象
label = tkinter.Label(top, text='Hello, world!', font='Arial -32', fg='red')
label.pack(expand=1)
# 创建一个装按钮的容器
panel = tkinter.Frame(top)
# 创建按钮对象
button1 = tkinter.Button(panel, text='修改', command=change_label_text)
button1.pack(side='left')
button2 = tkinter.Button(panel, text='退出', command=confirm_to_quit)
button2.pack(side='right')
panel.pack(side='bottom')
# 开启主事件循环
tkinter.mainloop()
if __name__ == '__main__':
main()
"""
使用tkinter创建GUI
- 使用画布绘图
- 处理鼠标事件
Version: 0.1
Author: 骆昊
Date: 2018-03-14
"""
import tkinter
def mouse_evt_handler(evt=None):
row = round((evt.y - 20) / 40)
col = round((evt.x - 20) / 40)
pos_x = 40 * col
pos_y = 40 * row
canvas.create_oval(pos_x, pos_y, 40 + pos_x, 40 + pos_y, fill='black')
top = tkinter.Tk()
# 设置窗口尺寸
top.geometry('620x620')
# 设置窗口标题
top.title('五子棋')
# 设置窗口大小不可改变
top.resizable(False, False)
# 设置窗口置顶
top.wm_attributes('-topmost', 1)
canvas = tkinter.Canvas(top, width=600, height=600, bd=0, highlightthickness=0)
canvas.bind('<Button-1>', mouse_evt_handler)
canvas.create_rectangle(0, 0, 600, 600, fill='yellow', outline='white')
for index in range(15):
canvas.create_line(20, 20 + 40 * index, 580, 20 + 40 * index, fill='black')
canvas.create_line(20 + 40 * index, 20, 20 + 40 * index, 580, fill='black')
canvas.create_rectangle(15, 15, 585, 585, outline='black', width=4)
canvas.pack()
tkinter.mainloop()
# 请思考如何用面向对象的编程思想对上面的代码进行封装
"""
使用tkinter创建GUI
- 在窗口上制作动画
Version: 0.1
Author: 骆昊
Date: 2018-03-14
"""
import tkinter
import time
# 播放动画效果的函数
def play_animation():
canvas.move(oval, 2, 2)
canvas.update()
top.after(50, play_animation)
x = 10
y = 10
top = tkinter.Tk()
top.geometry('600x600')
top.title('动画效果')
top.resizable(False, False)
top.wm_attributes('-topmost', 1)
canvas = tkinter.Canvas(top, width=600, height=600, bd=0, highlightthickness=0)
canvas.create_rectangle(0, 0, 600, 600, fill='gray')
oval = canvas.create_oval(10, 10, 60, 60, fill='red')
canvas.pack()
top.update()
play_animation()
tkinter.mainloop()
# 请思考如何让小球碰到屏幕的边界就弹回
# 请思考如何用面向对象的编程思想对上面的代码进行封装
"""
用turtle模块绘图
这是一个非常有趣的模块 它模拟一只乌龟在窗口上爬行的方式来进行绘图
Version: 0.1
Author: 骆昊
Date: 2018-03-14
"""
import turtle
turtle.pensize(3)
turtle.penup()
turtle.goto(-180, 150)
turtle.pencolor('red')
turtle.fillcolor('yellow')
turtle.pendown()
turtle.begin_fill()
for _ in range(36):
turtle.forward(200)
turtle.right(170)
turtle.end_fill()
turtle.mainloop()
## 图形用户界面和游戏开发
### 基于tkinter模块的GUI
GUI是图形用户界面的缩写,图形化的用户界面对使用过计算机的人来说应该都不陌生,在此也无需进行赘述。Python默认的GUI开发模块是tkinter(在Python 3以前的版本中名为Tkinter),从这个名字就可以看出它是基于Tk的,Tk是一个工具包,最初是为Tcl设计的,后来被移植到很多其他的脚本语言中,它提供了跨平台的GUI控件。当然Tk并不是最新和最好的选择,也没有功能特别强大的GUI控件,事实上,开发GUI应用并不是Python最擅长的工作,如果真的需要使用Python开发GUI应用,wxPython、PyQt、PyGTK等模块都是不错的选择。
基本上使用tkinter来开发GUI应用需要以下5个步骤:
1. 导入tkinter模块中我们需要的东西。
2. 创建一个顶层窗口对象并用它来承载整个GUI应用。
3. 在顶层窗口对象上添加GUI组件。
4. 通过代码将这些GUI组件的功能组织起来。
5. 进入主事件循环(main loop)。
下面的代码演示了如何使用tkinter做一个简单的GUI应用。
```Python
import tkinter
import tkinter.messagebox
def main():
flag = True
# 修改标签上的文字
def change_label_text():
nonlocal flag
flag = not flag
color, msg = ('red', 'Hello, world!')\
if flag else ('blue', 'Goodbye, world!')
label.config(text=msg, fg=color)
# 确认退出
def confirm_to_quit():
if tkinter.messagebox.askokcancel('温馨提示', '确定要退出吗?'):
top.quit()
# 创建顶层窗口
top = tkinter.Tk()
# 设置窗口大小
top.geometry('240x160')
# 设置窗口标题
top.title('小游戏')
# 创建标签对象并添加到顶层窗口
label = tkinter.Label(top, text='Hello, world!', font='Arial -32', fg='red')
label.pack(expand=1)
# 创建一个装按钮的容器
panel = tkinter.Frame(top)
# 创建按钮对象 指定添加到哪个容器中 通过command参数绑定事件回调函数
button1 = tkinter.Button(panel, text='修改', command=change_label_text)
button1.pack(side='left')
button2 = tkinter.Button(panel, text='退出', command=confirm_to_quit)
button2.pack(side='right')
panel.pack(side='bottom')
# 开启主事件循环
tkinter.mainloop()
if __name__ == '__main__':
main()
```
需要说明的是,GUI应用通常是事件驱动式的,之所以要进入主事件循环就是要监听鼠标、键盘等各种事件的发生并执行对应的代码对事件进行处理,因为事件会持续的发生,所以需要这样的一个循环一直运行着等待下一个事件的发生。另一方面,Tk为控件的摆放提供了三种布局管理器,通过布局管理器可以对控件进行定位,这三种布局管理器分别是:Placer(开发者提供控件的大小和摆放位置)、Packer(自动将控件填充到合适的位置)和Grid(基于网格坐标来摆放控件),此处不进行赘述。
### 使用Pygame进行游戏开发
Pygame是一个开源的Python模块,专门用于多媒体应用(如电子游戏)的开发,其中包含对图像、声音、视频、事件、碰撞等的支持。Pygame建立在[SDL](https://zh.wikipedia.org/wiki/SDL)的基础上,SDL是一套跨平台的多媒体开发库,用C语言实现,被广泛的应用于游戏、模拟器、播放器等的开发。而Pygame让游戏开发者不再被底层语言束缚,可以更多的关注游戏的功能和逻辑。
下面我们来完成一个简单的小游戏,游戏的名字叫“大球吃小球”,当然完成这个游戏并不是重点,学会使用Pygame也不是重点,最重要的我们要在这个过程中体会如何使用前面讲解的面向对象程序设计,学会用这种编程思想去解决现实中的问题。
#### 制作游戏窗口
```Python
import pygame
def main():
# 初始化导入的pygame中的模块
pygame.init()
# 初始化用于显示的窗口并设置窗口尺寸
screen = pygame.display.set_mode((800, 600))
# 设置当前窗口的标题
pygame.display.set_caption('大球吃小球')
running = True
# 开启一个事件循环处理发生的事件
while running:
# 从消息队列中获取事件并对事件进行处理
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
if __name__ == '__main__':
main()
```
#### 在窗口中绘图
可以通过pygame中draw模块的函数在窗口上绘图,可以绘制的图形包括:线条、矩形、多边形、圆、椭圆、圆弧等。需要说明的是,屏幕坐标系是将屏幕左上角设置为坐标原点`(0, 0)`,向右是x轴的正向,向下是y轴的正向,在表示位置或者设置尺寸的时候,我们默认的单位都是[像素](https://zh.wikipedia.org/wiki/%E5%83%8F%E7%B4%A0)。所谓像素就是屏幕上的一个点,你可以用浏览图片的软件试着将一张图片放大若干倍,就可以看到这些点。pygame中表示颜色用的是色光[三原色](https://zh.wikipedia.org/wiki/%E5%8E%9F%E8%89%B2)表示法,即通过一个元组或列表来指定颜色的RGB值,每个值都在0~255之间,因为是每种原色都用一个8位(bit)的值来表示,三种颜色相当于一共由24位构成,这也就是常说的“24位颜色表示法”。
```Python
import pygame
def main():
# 初始化导入的pygame中的模块
pygame.init()
# 初始化用于显示的窗口并设置窗口尺寸
screen = pygame.display.set_mode((800, 600))
# 设置当前窗口的标题
pygame.display.set_caption('大球吃小球')
# 设置窗口的背景色(颜色是由红绿蓝三原色构成的元组)
screen.fill((242, 242, 242))
# 绘制一个圆(参数分别是: 屏幕, 颜色, 圆心位置, 半径, 0表示填充圆)
pygame.draw.circle(screen, (255, 0, 0,), (100, 100), 30, 0)
# 刷新当前窗口(渲染窗口将绘制的图像呈现出来)
pygame.display.flip()
running = True
# 开启一个事件循环处理发生的事件
while running:
# 从消息队列中获取事件并对事件进行处理
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
if __name__ == '__main__':
main()
```
####加载图像
如果需要直接加载图像到窗口上,可以使用pygame中image模块的函数来加载图像,再通过之前获得的窗口对象的`blit`方法渲染图像,代码如下所示。
```Python
import pygame
def main():
# 初始化导入的pygame中的模块
pygame.init()
# 初始化用于显示的窗口并设置窗口尺寸
screen = pygame.display.set_mode((800, 600))
# 设置当前窗口的标题
pygame.display.set_caption('大球吃小球')
# 设置窗口的背景色(颜色是由红绿蓝三原色构成的元组)
screen.fill((255, 255, 255))
# 通过指定的文件名加载图像
ball_image = pygame.image.load('./res/ball.png')
# 在窗口上渲染图像
screen.blit(ball_image, (50, 50))
# 刷新当前窗口(渲染窗口将绘制的图像呈现出来)
pygame.display.flip()
running = True
# 开启一个事件循环处理发生的事件
while running:
# 从消息队列中获取事件并对事件进行处理
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
if __name__ == '__main__':
main()
```
####实现动画效果
说到[动画](https://zh.wikipedia.org/wiki/%E5%8A%A8%E7%94%BB)这个词大家都不会陌生,事实上要实现动画效果,本身的原理也非常简单,就是将不连续的图片连续的播放,只要每秒钟达到了一定的帧数,那么就可以做出比较流畅的动画效果。如果要让上面代码中的小球动起来,可以将小球的位置用变量来表示,并在循环中修改小球的位置再刷新整个窗口即可。
```Python
import pygame
def main():
# 初始化导入的pygame中的模块
pygame.init()
# 初始化用于显示的窗口并设置窗口尺寸
screen = pygame.display.set_mode((800, 600))
# 设置当前窗口的标题
pygame.display.set_caption('大球吃小球')
# 定义变量来表示小球在屏幕上的位置
x, y = 50, 50
running = True
# 开启一个事件循环处理发生的事件
while running:
# 从消息队列中获取事件并对事件进行处理
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
screen.fill((255, 255, 255))
pygame.draw.circle(screen, (255, 0, 0,), (x, y), 30, 0)
pygame.display.flip()
# 每隔50毫秒就改变小球的位置再刷新窗口
pygame.time.delay(50)
x, y = x + 5, y + 5
if __name__ == '__main__':
main()
```
#### 碰撞检测
通常一个游戏中会有很多对象出现,而这些对象之间的“碰撞”在所难免,比如炮弹击中了飞机、箱子撞到了地面等。碰撞检测在绝大多数的游戏中都是一个必须得处理的至关重要的问题,pygame的sprite(动画精灵)模块就提供了对碰撞检测的支持,这里我们暂时不介绍sprite模块提供的功能,因为要检测两个小球有没有碰撞其实非常简单,只需要检查球心的距离有没有小于两个球的半径之和。为了制造出更多的小球,我们可以通过对鼠标事件的处理,在点击鼠标的位置创建颜色、大小和移动速度都随机的小球,当然要做到这一点,我们可以把之前学习到的面向对象的知识应用起来。
```Python
from enum import Enum, unique
from math import sqrt
from random import randint
import pygame
@unique
class Color(Enum):
"""颜色"""
RED = (255, 0, 0)
GREEN = (0, 255, 0)
BLUE = (0, 0, 255)
BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
GRAY = (242, 242, 242)
@staticmethod
def random_color():
"""获得随机颜色"""
r = randint(0, 255)
g = randint(0, 255)
b = randint(0, 255)
return (r, g, b)
class Ball(object):
"""球"""
def __init__(self, x, y, radius, sx, sy, color=Color.RED):
"""初始化方法"""
self.x = x
self.y = y
self.radius = radius
self.sx = sx
self.sy = sy
self.color = color
self.alive = True
def move(self, screen):
"""移动"""
self.x += self.sx
self.y += self.sy
if self.x - self.radius <= 0 or \
self.x + self.radius >= screen.get_width():
self.sx = -self.sx
if self.y - self.radius <= 0 or \
self.y + self.radius >= screen.get_height():
self.sy = -self.sy
def eat(self, other):
"""吃其他球"""
if self.alive and other.alive and self != other:
dx, dy = self.x - other.x, self.y - other.y
distance = sqrt(dx ** 2 + dy ** 2)
if distance < self.radius + other.radius \
and self.radius > other.radius:
other.alive = False
a self.radius = self.radius + int(other.radius * 0.146)
def draw(self, screen):
"""在窗口上绘制球"""
pygame.draw.circle(screen, self.color,
(self.x, self.y), self.radius, 0)
```
#### 事件处理
可以在事件循环中对鼠标事件进行处理,通过事件对象的`type`属性可以判定事件类型,再通过`pos`属性就可以获得鼠标点击的位置。如果要处理键盘事件也是在这个地方,做法与处理鼠标事件类似。
```Python
def main():
# 定义用来装所有球的容器
balls = []
# 初始化导入的pygame中的模块
pygame.init()
# 初始化用于显示的窗口并设置窗口尺寸
screen = pygame.display.set_mode((800, 600))
# 设置当前窗口的标题
pygame.display.set_caption('大球吃小球')
running = True
# 开启一个事件循环处理发生的事件
while running:
# 从消息队列中获取事件并对事件进行处理
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
# 处理鼠标事件的代码
if event.type == pygame.MOUSEBUTTONDOWN and event.button == 1:
# 获得点击鼠标的位置
x, y = event.pos
radius = randint(10, 100)
sx, sy = randint(-10, 10), randint(-10, 10)
color = Color.random_color()
# 在点击鼠标的位置创建一个球(大小、速度和颜色随机)
ball = Ball(x, y, radius, sx, sy, color)
# 将球添加到列表容器中
balls.append(ball)
screen.fill((255, 255, 255))
# 取出容器中的球 如果没被吃掉就绘制 被吃掉了就移除
for ball in balls:
if ball.alive:
ball.draw(screen)
else:
balls.remove(ball)
pygame.display.flip()
# 每隔50毫秒就改变球的位置再刷新窗口
pygame.time.delay(50)
for ball in balls:
ball.move(screen)
# 检查球有没有吃到其他的球
for other in balls:
ball.eat(other)
if __name__ == '__main__':
main()
```
上面的两段代码合在一起,我们就完成了“大球吃小球”的游戏(如下图所示),准确的说它算不上一个游戏,但是做一个小游戏的基本知识我们已经通过这个例子告诉大家了,有了这些知识已经可以开始你的小游戏开发之旅了。其实上面的代码中还有很多值得改进的地方,比如刷新窗口以及让球移动起来的代码并不应该放在事件循环中,等学习了多线程的知识后,用一个后台线程来处理这些事可能是更好的选择。如果希望获得更好的用户体验,我们还可以在游戏中加入背景音乐以及在球与球发生碰撞时播放音效,利用pygame的mixer和music模块,我们可以很容易的做到这一点,大家可以自行了解这方面的知识。事实上,想了解更多的关于pygame的知识,最好的教程是[pygame的官方网站](https://www.pygame.org/news),如果英语没毛病就可以赶紧去看看啦。 如果想开发[3D游戏](https://zh.wikipedia.org/wiki/3D%E6%B8%B8%E6%88%8F),pygame就显得力不从心了,对3D游戏开发如果有兴趣的读者不妨看看[Panda3D](https://www.panda3d.org/)
"""
读取CSV文件
Version: 0.1
Author: 骆昊
Date: 2018-03-13
"""
import csv
filename = 'example.csv'
try:
with open(filename) as f:
reader = csv.reader(f)
data = list(reader)
except FileNotFoundError:
print('无法打开文件:', filename)
else:
for item in data:
print('%-30s%-20s%-10s' % (item[0], item[1], item[2]))
"""
写入CSV文件
Version: 0.1
Author: 骆昊
Date: 2018-03-13
"""
import csv
class Teacher(object):
def __init__(self, name, age, title):
self.__name = name
self.__age = age
self.__title = title
self.__index = -1
@property
def name(self):
return self.__name
@property
def age(self):
return self.__age
@property
def title(self):
return self.__title
filename = 'teacher.csv'
teachers = [Teacher('骆昊', 38, '叫兽'), Teacher('狄仁杰', 25, '砖家')]
try:
with open(filename, 'w') as f:
writer = csv.writer(f)
for teacher in teachers:
writer.writerow([teacher.name, teacher.age, teacher.title])
except BaseException as e:
print('无法写入文件:', filename)
else:
print('保存数据完成!')
"""
异常机制 - 处理程序在运行时可能发生的状态
Version: 0.1
Author: 骆昊
Date: 2018-03-13
"""
input_again = True
while input_again:
try:
a = int(input('a = '))
b = int(input('b = '))
print('%d / %d = %f' % (a, b, a / b))
input_again = False
except ValueError:
print('请输入整数')
except ZeroDivisionError:
print('除数不能为0')
# 处理异常让代码不因异常而崩溃是一方面
# 更重要的是可以通过对异常的处理让代码从异常中恢复过来
"""
异常机制 - 处理程序在运行时可能发生的状态
Version: 0.1
Author: 骆昊
Date: 2018-03-13
"""
input_again = True
while input_again:
try:
a = int(input('a = '))
b = int(input('b = '))
print('%d / %d = %f' % (a, b, a / b))
input_again = False
except (ValueError, ZeroDivisionError) as msg:
print(msg)
"""
异常机制 - 处理程序在运行时可能发生的状态
Version: 0.1
Author: 骆昊
Date: 2018-03-13
"""
import time
import sys
filename = input('请输入文件名: ')
try:
with open(filename) as f:
lines = f.readlines()
except FileNotFoundError as msg:
print('无法打开文件:', filename)
print(msg)
except UnicodeDecodeError as msg:
print('非文本文件无法解码')
sys.exit()
else:
for line in lines:
print(line.rstrip())
time.sleep(0.5)
finally:
# 此处最适合做善后工作
print('不管发生什么我都会执行')
"""
引发异常和异常栈
Version: 0.1
Author: 骆昊
Date: 2018-03-13
"""
def f1():
raise AssertionError('发生异常')
def f2():
f1()
def f3():
f2()
f3()
4/5/2014 13:34,Apples,73
4/5/2014 3:41,Cherries,85
4/6/2014 12:46,Pears,14
4/8/2014 8:59,Oranges,52
4/10/2014 2:07,Apples,152
4/10/2014 18:10,Bananas,23
4/10/2014 2:40,Strawberries,98
"""
从文本文件中读取数据
Version: 0.1
Author: 骆昊
Date: 2018-03-13
"""
import time
def main():
# 一次性读取整个文件内容
with open('致橡树.txt', 'r', encoding='utf-8') as f:
print(f.read())
# 通过for-in循环逐行读取
with open('致橡树.txt', mode='r') as f:
for line in f:
print(line, end='')
time.sleep(0.5)
print()
# 读取文件按行读取到列表中
with open('致橡树.txt') as f:
lines = f.readlines()
print(lines)
if __name__ == '__main__':
main()
"""
读取圆周率文件判断其中是否包含自己的生日
Version: 0.1
Author: 骆昊
Date: 2018-03-13
"""
birth = input('请输入你的生日: ')
with open('pi_million_digits.txt') as f:
lines = f.readlines()
pi_string = ''
for line in lines:
pi_string += line.strip()
if birth in pi_string:
print('Bingo!!!')
"""
写文本文件
将100以内的素数写入到文件中
Version: 0.1
Author: 骆昊
Date: 2018-03-13
"""
from math import sqrt
def is_prime(n):
for factor in range(2, int(sqrt(n)) + 1):
if n % factor == 0:
return False
return True
# 试一试有什么不一样
# with open('prime.txt', 'a') as f:
with open('prime.txt', 'w') as f:
for num in range(2, 100):
if is_prime(num):
f.write(str(num) + '\n')
print('写入完成!')
"""
读写二进制文件
Version: 0.1
Author: 骆昊
Date: 2018-03-13
"""
import base64
with open('mm.jpg', 'rb') as f:
data = f.read()
# print(type(data))
# print(data)
print('字节数:', len(data))
# 将图片处理成BASE-64编码
print(base64.b64encode(data))
with open('girl.jpg', 'wb') as f:
f.write(data)
print('写入完成!')
"""
读取JSON数据
Version: 0.1
Author: 骆昊
Date: 2018-03-13
"""
import json
import csv2
json_str = '{"name": "骆昊", "age": 38, "title": "叫兽"}'
result = json.loads(json_str)
print(result)
print(type(result))
print(result['name'])
print(result['age'])
# 把转换得到的字典作为关键字参数传入Teacher的构造器
teacher = csv2.Teacher(**result)
print(teacher)
print(teacher.name)
print(teacher.age)
print(teacher.title)
# 请思考如何将下面JSON格式的天气数据转换成对象并获取我们需要的信息
# 稍后我们会讲解如何通过网络API获取我们需要的JSON格式的数据
"""
{
"wendu": "29",
"ganmao": "各项气象条件适宜,发生感冒机率较低。但请避免长期处于空调房间中,以防感冒。",
"forecast": [
{
"fengxiang": "南风",
"fengli": "3-4级",
"high": "高温 32℃",
"type": "多云",
"low": "低温 17℃",
"date": "16日星期二"
},
{
"fengxiang": "南风",
"fengli": "微风级",
"high": "高温 34℃",
"type": "晴",
"low": "低温 19℃",
"date": "17日星期三"
},
{
"fengxiang": "南风",
"fengli": "微风级",
"high": "高温 35℃",
"type": "晴",
"low": "低温 22℃",
"date": "18日星期四"
},
{
"fengxiang": "南风",
"fengli": "微风级",
"high": "高温 35℃",
"type": "多云",
"low": "低温 22℃",
"date": "19日星期五"
},
{
"fengxiang": "南风",
"fengli": "3-4级",
"high": "高温 34℃",
"type": "晴",
"low": "低温 21℃",
"date": "20日星期六"
}
],
"yesterday": {
"fl": "微风",
"fx": "南风",
"high": "高温 28℃",
"type": "晴",
"low": "低温 15℃",
"date": "15日星期一"
},
"aqi": "72",
"city": "北京"
}
"""
"""
写入JSON文件
Version: 0.1
Author: 骆昊
Date: 2018-03-13
"""
import json
teacher_dict = {'name': '白元芳', 'age': 25, 'title': '讲师'}
json_str = json.dumps(teacher_dict)
print(json_str)
print(type(json_str))
fruits_list = ['apple', 'orange', 'strawberry', 'banana', 'pitaya']
json_str = json.dumps(fruits_list)
print(json_str)
print(type(json_str))
此差异已折叠。
骆昊,38,叫兽
狄仁杰,25,砖家
## 文件和异常
在实际开发中,常常需要对程序中的数据进行[持久化](https://baike.baidu.com/item/%E6%95%B0%E6%8D%AE%E6%8C%81%E4%B9%85%E5%8C%96)操作,而实现数据持久化最直接简单的方式就是将数据保存到文件中。说到“文件”这个词,可能需要先科普一下关于[文件系统](https://zh.wikipedia.org/wiki/%E6%96%87%E4%BB%B6%E7%B3%BB%E7%BB%9F)的知识,对于这个概念,维基百科上给出了很好的诠释,这里不再浪费笔墨。
在Python中实现文件的读写操作其实非常简单,通过Python内置的`open`函数,我们可以指定文件名、操作模式、编码信息等来获得操作文件的对象,接下来就可以对文件进行读写操作了。这里所说的操作模式是指要打开什么样的文件(字符文件还是二进制文件)以及做什么样的操作(读、写还是追加),具体的如下表所示。
| 操作模式 | 具体含义 |
| -------- | -------------------------------- |
| `'r'` | 读取 (默认) |
| `'w'` | 写入(会先截断之前的内容) |
| `'x'` | 写入,如果文件已经存在会产生异常 |
| `'a'` | 追加,将内容写入到已有文件的末尾 |
| `'b'` | 二进制模式 |
| `'t'` | 文本模式(默认) |
| `'+'` | 更新(既可以读又可以写) |
下面这张图来自于[菜鸟教程](http://www.runoob.com)网站,它展示了如果根据应用程序的需要来设置操作模式。
![](./res/file-open-mode.png)
### 读写文本文件
读取文本文件时,需要在使用`open`函数时指定好带路径的文件名(可以使用相对路径或绝对路径)并将文件模式设置为`'r'`(如果不指定,默认值也是`'r'`),然后通过`encoding`参数指定编码(如果不指定,默认值是None,那么在读取文件时使用的是操作系统默认的编码),如果不能保证保存文件时使用的编码方式与encoding参数指定的编码方式是一致的,那么就可能因无法解码字符而导致读取失败。下面的例子演示了如何读取一个纯文本文件。
```Python
def main():
f = open('致橡树.txt', 'r', encoding='utf-8')
print(f.read())
f.close()
if __name__ == '__main__':
main()
```
请注意上面的代码,如果`open`函数指定的文件并不存在或者无法打开,那么将引发异常状况导致程序崩溃。为了让代码有一定的健壮性和容错性,我们可以使用Python的异常机制对可能在运行时发生状况的代码进行适当的处理,如下所示。
```Python
def main():
f = None
try:
f = open('致橡树.txt', 'r', encoding='utf-8')
print(f.read())
except FileNotFoundError:
print('无法打开指定的文件!')
except LookupError:
print('指定了未知的编码!')
except UnicodeDecodeError:
print('读取文件时解码错误!')
finally:
if f:
f.close()
if __name__ == '__main__':
main()
```
在Python中,我们可以将那些在运行时可能会出现状况的代码放在`try`代码块中,在`try`代码块的后面可以跟上一个或多个`except`来捕获可能出现的异常状况。例如在上面读取文件的过程中,文件找不到会引发`FileNotFoundError`,指定了未知的编码会引发`LookupError`,而如果读取文件时无法按指定方式解码会引发`UnicodeDecodeError`,我们在`try`后面跟上了三个`except`分别处理这三种不同的异常状况。最后我们使用`finally`代码块来关闭打开的文件,释放掉程序中获取的外部资源,由于`finally`块的代码不论程序正常还是异常都会执行到(甚至是调用了`sys`模块的`exit`函数退出Python环境,`finally`块都会被执行,因为`exit`函数实质上是引发了`SystemExit`异常),因此我们通常把`finally`块称为“总是执行代码块”,它最适合用来做释放外部资源的操作。如果不愿意在`finally`代码块中关闭文件对象释放资源,也可以使用上下文语法,通过`with`关键字指定文件对象的上下文环境并在离开上下文环境时自动释放文件资源,代码如下所示。
```Python
def main():
try:
with open('致橡树.txt', 'r', encoding='utf-8') as f:
print(f.read())
except FileNotFoundError:
print('无法打开指定的文件!')
except LookupError:
print('指定了未知的编码!')
except UnicodeDecodeError:
print('读取文件时解码错误!')
if __name__ == '__main__':
main()
```
除了使用文件对象的`read`方法读取文件之外,还可以使用`for-in`循环逐行读取或者用`readlines`方法将文件按行读取到一个列表容器中,代码如下所示。
```Python
import time
def main():
# 一次性读取整个文件内容
with open('致橡树.txt', 'r', encoding='utf-8') as f:
print(f.read())
# 通过for-in循环逐行读取
with open('致橡树.txt', mode='r') as f:
for line in f:
print(line, end='')
time.sleep(0.5)
print()
# 读取文件按行读取到列表中
with open('致橡树.txt') as f:
lines = f.readlines()
print(lines)
if __name__ == '__main__':
main()
```
要将文本信息写入文件文件也非常简单,在使用`open`函数时指定好文件名并将文件模式设置为`'w'`即可。注意如果需要对文件内容进行追加式写入,应该将模式设置为`'a'`。如果要写入的文件不存在会自动创建文件而不是引发异常。下面的例子演示了如何将1~9999直接的素数分别写入三个文件中(1~99之间的素数保存在a.txt中,100~999之间的素数保存在b.txt中,1000~9999之间的素数保存在c.txt中)。
```Python
from math import sqrt
def is_prime(n):
"""判断素数的函数"""
assert n > 0
for factor in range(2, int(sqrt(n)) + 1):
if n % factor == 0:
return False
return True if n != 1 else False
def main():
filenames = ('a.txt', 'b.txt', 'c.txt')
fs_list = []
try:
for filename in filenames:
fs_list.append(open(filename, 'w', encoding='utf-8'))
for number in range(1, 10000):
if is_prime(number):
if number < 100:
fs_list[0].write(str(number) + '\n')
elif number < 1000:
fs_list[1].write(str(number) + '\n')
else:
fs_list[2].write(str(number) + '\n')
except IOError as ex:
print(ex)
print('写文件时发生错误!')
finally:
for fs in fs_list:
fs.close()
print('操作完成!')
if __name__ == '__main__':
main()
```
### 读写二进制文件
知道了如何读写文本文件要读写二进制文件也就很简单了,下面的代码实现了复制图片文件的功能。
```Python
def main():
try:
with open('guido.jpg', 'rb') as fs1:
data = fs1.read()
print(type(data)) # <class 'bytes'>
with open('吉多.jpg', 'wb') as fs2:
fs2.write(data)
except FileNotFoundError as e:
print('指定的文件无法打开.')
except IOError as e:
print('读写文件时出现错误.')
print('程序执行结束.')
if __name__ == '__main__':
main()
```
### 读写JSON文件
通过上面的讲解,我们已经知道如何将文本数据和二进制数据保存到文件中,那么这里还有一个问题,如果希望把一个列表或者一个字典中的数据保存到文件中又该怎么做呢?答案是将数据以JSON格式进行保存。JSON是“JavaScript Object Notation”的缩写,它本来是JavaScript语言中创建对象的一种字面量语法,现在已经被广泛的应用于跨平台跨语言的数据交换,原因很简单,因为JSON也是纯文本,任何系统任何编程语言处理纯文本都是没有问题的。目前JSON基本上已经取代了XML作为异构系统间交换数据的事实标准。关于JSON的知识,更多的可以参考[JSON的官方网站](http://json.org),从这个网站也可以了解到每种语言处理JSON数据格式可以使用的工具或三方库,下面是一个JSON的简单例子。
```JSON
{
'name': '骆昊',
'age': 38,
'qq': 957658,
'friends': ['王大锤', '白元芳'],
'cars': [
{'brand': 'BYD', 'max_speed': 180},
{'brand': 'Audi', 'max_speed': 280},
{'brand': 'Benz', 'max_speed': 320}
]
}
```
可能大家已经注意到了,上面的JSON跟Python中的字典其实是一样一样的,事实上JSON的数据类型和Python的数据类型是很容易找到对应关系的,如下面两张表所示。
| JSON | Python |
| ------------------- | ------------ |
| object | dict |
| array | list |
| string | str |
| number (int / real) | int / float |
| true / false | True / False |
| null | None |
| Python | JSON |
| -------------------------------------- | ------------ |
| dict | object |
| list, tuple | array |
| str | string |
| int, float, int- & float-derived Enums | number |
| True / False | true / false |
| None | null |
我们使用Python中的json模块就可以将字典或列表以JSON格式保存到文件中,代码如下所示。
```Python
import json
def main():
mydict = {
'name': '骆昊',
'age': 38,
'qq': 957658,
'friends': ['王大锤', '白元芳'],
'cars': [
{'brand': 'BYD', 'max_speed': 180},
{'brand': 'Audi', 'max_speed': 280},
{'brand': 'Benz', 'max_speed': 320}
]
}
try:
with open('data.json', 'w', encoding='utf-8') as fs:
json.dump(mydict, fs)
except IOError as e:
print(e)
print('保存数据完成!')
if __name__ == '__main__':
main()
```
json模块主要有四个比较重要的函数,分别是:
- dump - 将Python对象按照JSON格式序列化到文件中
- dumps - 将Python对象处理成JSON格式的字符串
- load - 将文件中的JSON数据反序列化成对象
- loads - 将字符串的内容反序列化成Python对象
这里出现了两个概念,一个叫序列化,一个叫反序列化。自由的百科全书[维基百科](https://zh.wikipedia.org/)上对这两个概念是这样解释的:“序列化(serialization)在计算机科学的数据处理中,是指将数据结构或对象状态转换为可以存储或传输的形式,这样在需要的时候能够恢复到原先的状态,而且通过序列化的数据重新获取字节时,可以利用这些字节来产生原始对象的副本(拷贝)。与这个过程相反的动作,即从一系列字节中提取数据结构的操作,就是反序列化(deserialization)”。
目前绝大多数网络数据服务(或称之为网络API)都是基于[HTTP协议](https://zh.wikipedia.org/wiki/%E8%B6%85%E6%96%87%E6%9C%AC%E4%BC%A0%E8%BE%93%E5%8D%8F%E8%AE%AE)提供JSON格式的数据,关于HTTP协议的相关知识,可以看看阮一峰老师的[《HTTP协议入门》](http://www.ruanyifeng.com/blog/2016/08/http.html),如果想了解国内的网络数据服务,可以看看[聚合数据](https://www.juhe.cn/)[阿凡达数据](http://www.avatardata.cn/)等网站,国外的可以看看[{API}Search](http://apis.io/)网站。下面的例子演示了如何使用requests模块(封装得足够好的第三方网络访问模块)访问网络API获取国内新闻,如何通过json模块解析JSON数据并显示新闻标题,这个例子使用了[天行数据](https://www.tianapi.com/)提供的国内新闻数据接口,其中的APIKey需要自己到该网站申请。
```Python
import requests
import json
def main():
resp = requests.get('http://api.tianapi.com/guonei/?key=APIKey&num=10')
data_model = json.loads(resp.text)
for news in data_model['newslist']:
print(news['title'])
if __name__ == '__main__':
main()
```
在Python中要实现序列化和反序列化除了使用json模块之外,还可以使用pickle和shelve模块,但是这两个模块是使用特有的序列化协议来序列化数据,因此序列化后的数据只能被Python识别。关于这两个模块的相关知识可以自己看看网络上的资料。另外,如果要了解更多的关于Python异常机制的知识,可以看看segmentfault上面的文章[《总结:Python中的异常处理》](https://segmentfault.com/a/1190000007736783),这篇文章不仅介绍了Python中异常机制的使用,还总结了一系列的最佳实践,很值得一读。
\ No newline at end of file
我如果爱你
绝不学攀援的凌霄花
借你的高枝炫耀自己
我如果爱你
绝不学痴情的鸟儿
为绿荫重复单调的歌曲
"""
字符串常用操作
Version: 0.1
Author: 骆昊
Date: 2018-03-19
"""
import pyperclip
# 转义字符
print('My brother\'s name is \'007\'')
# 原始字符串
print(r'My brother\'s name is \'007\'')
str = 'hello123world'
print('he' in str)
print('her' in str)
# 字符串是否只包含字母
print(str.isalpha())
# 字符串是否只包含字母和数字
print(str.isalnum())
# 字符串是否只包含数字
print(str.isdecimal())
print(str[0:5].isalpha())
print(str[5:8].isdecimal())
list = ['床前明月光', '疑是地上霜', '举头望明月', '低头思故乡']
print('-'.join(list))
sentence = 'You go your way I will go mine'
words_list = sentence.split()
print(words_list)
email = ' jackfrued@126.com '
print(email)
print(email.strip())
print(email.lstrip())
# 将文本放入系统剪切板中
pyperclip.copy('老虎不发猫你当我病危呀')
# 从系统剪切板获得文本
# print(pyperclip.paste())
"""
字符串常用操作 - 实现字符串倒转的方法
Version: 0.1
Author: 骆昊
Date: 2018-03-19
"""
from io import StringIO
def reverse_str1(str):
return str[::-1]
def reverse_str2(str):
if len(str) <= 1:
return str
return reverse_str2(str[1:]) + str[0:1]
def reverse_str3(str):
# StringIO对象是Python中的可变字符串
# 不应该使用不变字符串做字符串连接操作 因为会产生很多无用字符串对象
rstr = StringIO()
str_len = len(str)
for index in range(str_len - 1, -1, -1):
rstr.write(str[index])
return rstr.getvalue()
def reverse_str4(str):
return ''.join(str[index] for index in range(len(str) - 1, -1, -1))
def reverse_str5(str):
# 将字符串处理成列表
str_list = list(str)
str_len = len(str)
# 使用zip函数将两个序列合并成一个产生元组的迭代器
# 每次正好可以取到一前一后两个下标来实现元素的交换
for i, j in zip(range(str_len // 2), range(str_len - 1, str_len // 2, -1)):
str_list[i], str_list[j] = str_list[j], str_list[i]
# 将列表元素连接成字符串
return ''.join(str_list)
if __name__ == '__main__':
str = 'I love Python'
print(reverse_str1(str))
print(str)
print(reverse_str2(str))
print(str)
print(reverse_str3(str))
print(str)
print(reverse_str4(str))
print(str)
print(reverse_str5(str))
print(str)
# 提醒学生注意这是一个面试题: 写出你能想到的实现字符串倒转的代码
"""
验证输入用户名和QQ号是否有效并给出对应的提示信息
要求:
用户名必须由字母、数字或下划线构成且长度在6~20个字符之间
QQ号是5~12的数字且首位不能为0
"""
import re
def main():
username = input('请输入用户名: ')
qq = input('请输入QQ号: ')
m1 = re.match(r'^[0-9a-zA-Z_]{6,20}$', username)
if not m1:
print('请输入有效的用户名.')
m2 = re.match(r'^[1-9]\d{4,11}$', qq)
if not m2:
print('请输入有效的QQ号.')
if m1 and m2:
print('你输入的信息是有效的!')
if __name__ == '__main__':
main()
import re
def main():
# 创建正则表达式对象 使用了前瞻和回顾来保证手机号前后不应该出现数字
pattern = re.compile(r'(?<=\D)(1[38]\d{9}|14[57]\d{8}|15[0-35-9]\d{8}|17[678]\d{8})(?=\D)')
sentence = '''
重要的事情说8130123456789遍,我的手机号是13512346789这个靓号,
不是15600998765,也是110或119,王大锤的手机号才是15600998765。
'''
# 查找所有匹配并保存到一个列表中
mylist = re.findall(pattern, sentence)
print(mylist)
print('--------华丽的分隔线--------')
# 通过迭代器取出匹配对象并获得匹配的内容
for temp in pattern.finditer(sentence):
print(temp.group())
print('--------华丽的分隔线--------')
# 通过search函数指定搜索位置找出所有匹配
m = pattern.search(sentence)
while m:
print(m.group())
m = pattern.search(sentence, m.end())
if __name__ == '__main__':
main()
import re
def main():
sentence = '你丫是傻叉吗? 我操你大爷的. Fuck you.'
purified = re.sub('[操肏艹草曹]|fuck|shit|傻[比屄逼叉缺吊屌]|煞笔',
'*', sentence, flags=re.IGNORECASE)
print(purified)
if __name__ == '__main__':
main()
## 使用正则表达式
### 正则表达式相关知识
在编写处理字符串的程序或网页时,经常会有查找符合某些复杂规则的字符串的需要,正则表达式就是用于描述这些规则的工具,换句话说正则表达式是一种工具,它定义了字符串的匹配模式(如何检查一个字符串是否有跟某种模式匹配的部分或者从一个字符串中将与模式匹配的部分提取出来或者替换掉)。如果你在Windows操作系统中使用过文件查找并且在指定文件名时使用过通配符(\*和?),那么正则表达式也是与之类似的用来进行文本匹配的工具,只不过比起通配符正则表达式更强大,它能更精确地描述你的需求(当然你付出的代价是书写一个正则表达式比打出一个通配符要复杂得多,要知道任何给你带来好处的东西都是有代价的,就如同学习一门编程语言一样),比如你可以编写一个正则表达式,用来查找所有以0开头,后面跟着2-3个数字,然后是一个连字号“-”,最后是7或8位数字的字符串(像028-12345678或0813-7654321),这不就是国内的座机号码吗。最初计算机是为了做数学运算而诞生的,处理的信息基本上都是数值,而今天我们在日常工作中处理的信息基本上都是文本数据,我们希望计算机能够识别和处理符合某些模式的文本,正则表达式就显得非常重要了。今天几乎所有的编程语言都提供了对正则表达式操作的支持,Python通过标准库中的re模块来支持正则表达式操作。
我们可以考虑下面一个问题:我们从某个地方(可能是一个文本文件,也可能是网络上的一则新闻)获得了一个字符串,希望在字符串中找出手机号和座机号。当然我们可以设定手机号是11位的数字(注意并不是随机的11位数字,因为你没有见过“25012345678”这样的手机号吧)而座机号跟上一段中描述的模式相同,如果不使用正则表达式要完成这个任务就会很麻烦。
关于正则表达式的相关知识,大家可以阅读一篇非常有名的博客叫[《正则表达式30分钟入门教程》](https://deerchao.net/tutorials/regex/regex.htm),读完这篇文章后你就可以看懂下面的表格,这是我们对正则表达式中的一些基本符号进行的扼要总结。
| 符号 | 解释 | 示例 | 说明 |
| ------------------ | ----------------------------------------- | ---------------- | -------------------------------------------------- |
| . | 匹配任意字符 | b.t | 可以匹配bat / but / b#t / b1t等 |
| \\w | 匹配字母/数字/下划线 | b\\wt | 可以匹配bat / b1t / b_t等<br>但不能匹配b#t |
| \\s | 匹配空白字符(包括\r\n\t等) | love\\syou | 可以匹配love you |
| \\d | 匹配数字 | \\d\\d | 可以匹配01 / 23 / 99等 |
| \\b | 匹配单词的边界 | \\bThe\\b | |
| ^ | 匹配字符串的开始 | ^The | 可以匹配The开头的字符串 |
| $ | 匹配字符串的结束 | .exe$ | 可以匹配.exe结尾的字符串 |
| \\W | 匹配非字母/数字/下划线 | b\\Wt | 可以匹配b#t / b@t等<br>但不能匹配but / b1t / b_t等 |
| \\S | 匹配非空白字符 | love\\Syou | 可以匹配love#you等<br>但不能匹配love you |
| \\D | 匹配非数字 | \\d\\D | 可以匹配9a / 3# / 0F等 |
| \\B | 匹配非单词边界 | \\Bio\\B | |
| [] | 匹配来自字符集的任意单一字符 | [aeiou] | 可以匹配任一元音字母字符 |
| [^] | 匹配不在字符集中的任意单一字符 | [^aeiou] | 可以匹配任一非元音字母字符 |
| * | 匹配0次或多次 | \\w* | |
| + | 匹配1次或多次 | \\w+ | |
| ? | 匹配0次或1次 | \\w? | |
| {N} | 匹配N次 | \\w{3} | |
| {M,} | 匹配至少M次 | \\w{3,} | |
| {M,N} | 匹配至少M次至多N次 | \\w{3,6} | |
| \| | 分支 | foo\|bar | 可以匹配foo或者bar |
| (?#) | 注释 | | |
| (exp) | 匹配exp并捕获到自动命名的组中 | | |
| (?&lt;name&gt;exp) | 匹配exp并捕获到名为name的组中 | | |
| (?:exp) | 匹配exp但是不捕获匹配的文本 | | |
| (?=exp) | 匹配exp前面的位置 | \\b\\w+(?=ing) | 可以匹配I'm dancing中的danc |
| (?<=exp) | 匹配exp后面的位置 | (?<=\\bdanc)\\w+\\b | 可以匹配I love dancing and reading中的第一个ing |
| (?!exp) | 匹配后面不是exp的位置 | | |
| (?<!exp) | 匹配前面不是exp的位置 | | |
| *? | 重复任意次,但尽可能少重复 | a.\*b<br>a.\*?b | 将正则表达式应用于aabab,前者会匹配整个字符串aabab,后者会匹配aab和ab两个字符串 |
| +? | 重复1次或多次,但尽可能少重复 | | |
| ?? | 重复0次或1次,但尽可能少重复 | | |
| {M,N}? | 重复M到N次,但尽可能少重复 | | |
| {M,}? | 重复M次以上,但尽可能少重复 | | |
> **说明:**如果需要匹配的字符是正则表达式中的特殊字符,那么可以使用\\进行转义处理,例如想匹配小数点可以写成\\.就可以了,因为直接写.会匹配任意字符;同理,想匹配圆括号必须写成\\(和\\),否则圆括号被视为正则表达式中的分组。
### Python对正则表达式的支持
Python提供了re模块来支持正则表达式相关操作,下面是re模块中的核心函数。
| 函数 | 说明 |
| -------------------------------------------- | ------------------------------------------------------------ |
| compile(pattern, flags=0) | 编译正则表达式返回正则表达式对象 |
| match(pattern, string, flags=0) | 用正则表达式匹配字符串 成功返回匹配对象 否则返回None |
| search(pattern, string, flags=0) | 搜索字符串中第一次出现正则表达式的模式 成功返回匹配对象 否则返回None |
| split(pattern, string, maxsplit=0, flags=0) | 用正则表达式指定的模式分隔符拆分字符串 返回列表 |
| sub(pattern, repl, string, count=0, flags=0) | 用指定的字符串替换原字符串中与正则表达式匹配的模式 可以用count指定替换的次数 |
| fullmatch(pattern, string, flags=0) | match函数的完全匹配(从字符串开头到结尾)版本 |
| findall(pattern, string, flags=0) | 查找字符串所有与正则表达式匹配的模式 返回字符串的列表 |
| finditer(pattern, string, flags=0) | 查找字符串所有与正则表达式匹配的模式 返回一个迭代器 |
| purge() | 清除隐式编译的正则表达式的缓存 |
| re.I / re.IGNORECASE | 忽略大小写匹配标记 |
| re.M / re.MULTILINE | 多行匹配标记 |
> **说明:**上面提到的re模块中的这些函数,实际开发中也可以用正则表达式对象的方法替代对这些函数的使用,如果一个正则表达式需要重复的使用,那么先通过compile函数编译正则表达式并创建出正则表达式对象无疑是更为明智的选择。
下面我们通过一系列的例子来告诉大家在Python中如何使用正则表达式。
#### 例子1:验证输入用户名和QQ号是否有效并给出对应的提示信息。
```Python
"""
验证输入用户名和QQ号是否有效并给出对应的提示信息
要求:
用户名必须由字母、数字或下划线构成且长度在6~20个字符之间
QQ号是5~12的数字且首位不能为0
"""
import re
def main():
username = input('请输入用户名: ')
qq = input('请输入QQ号: ')
# match函数的第一个参数是正则表达式字符串或正则表达式对象
# 第二个参数是要跟正则表达式做匹配的字符串对象
m1 = re.match(r'^[0-9a-zA-Z_]{6,20}$', username)
if not m1:
print('请输入有效的用户名.')
m2 = re.match(r'^[1-9]\d{4,11}$', qq)
if not m2:
print('请输入有效的QQ号.')
if m1 and m2:
print('你输入的信息是有效的!')
if __name__ == '__main__':
main()
```
> **提示**:上面在书写正则表达式时使用了“原始字符串”的写法(在字符串前面加上了r),所谓“原始字符串”就是字符串中的每个字符都是它原始的意义,说得更直接一点就是字符串中没有所谓的转义字符啦。因为正则表达式中有很多元字符和需要进行转义的地方,如果不使用原始字符串就需要将反斜杠写作\\\\,例如表示数字的\\d得书写成\\\\d,这样不仅写起来不方便,阅读的时候也会很吃力。
#### 例子2:从一段文字中提取出国内手机号码。
下面这张图是截止到2017年底,国内三家运营商推出的手机号段。
![](./res/tel-start-number.png)
```Python
import re
def main():
# 创建正则表达式对象 使用了前瞻和回顾来保证手机号前后不应该出现数字
pattern = re.compile(r'(?<=\D)1[34578]\d{9}(?=\D)')
sentence = '''
重要的事情说8130123456789遍,我的手机号是13512346789这个靓号,
不是15600998765,也是110或119,王大锤的手机号才是15600998765。
'''
# 查找所有匹配并保存到一个列表中
mylist = re.findall(pattern, sentence)
print(mylist)
print('--------华丽的分隔线--------')
# 通过迭代器取出匹配对象并获得匹配的内容
for temp in pattern.finditer(sentence):
print(temp.group())
print('--------华丽的分隔线--------')
# 通过search函数指定搜索位置找出所有匹配
m = pattern.search(sentence)
while m:
print(m.group())
m = pattern.search(sentence, m.end())
if __name__ == '__main__':
main()
```
> **说明**:上面匹配国内手机号的正则表达式并不够好,因为像14开头的号码只有145或147,而上面的正则表达式并没有考虑这种情况,要匹配国内手机号,更好的正则表达式的写法是:`(?<=\D)(1[38]\d{9}|14[57]\d{8}|15[0-35-9]\d{8}|17[678]\d{8})(?=\D)`,国内最近好像有19和16开头的手机号了,但是这个暂时不在我们考虑之列。
#### 例子3:替换字符串中的不良内容
```Python
import re
def main():
sentence = '你丫是傻叉吗? 我操你大爷的. Fuck you.'
purified = re.sub('[操肏艹草曹]|fuck|shit|傻[比屄逼叉缺吊屌]|煞笔',
'*', sentence, flags=re.IGNORECASE)
print(purified) # 你丫是*吗? 我*你大爷的. * you.
if __name__ == '__main__':
main()
```
> **说明**:re模块的正则表达式相关函数中都有一个flags参数,它代表了正则表达式的匹配标记,可以通过该标记来指定匹配时是否忽略大小写、是否进行多行匹配、是否显示调试信息等。如果需要为flags参数指定多个值,可以使用[按位或运算符](http://www.runoob.com/python/python-operators.html#ysf5)进行叠加,如`flags=re.I | re.M`。
#### 例子4:拆分长字符串
```Python
import re
def main():
poem = '窗前明月光,疑是地上霜。举头望明月,低头思故乡。'
sentence_list = re.split(r'[,。, .]', poem)
while '' in sentence_list:
sentence_list.remove('')
print(sentence_list) # ['窗前明月光', '疑是地上霜', '举头望明月', '低头思故乡']
if __name__ == '__main__':
main()
```
### 后话
如果要从事爬虫类应用的开发,那么正则表达式一定是一个非常好的助手,因为它可以帮助我们迅速的从网页代码中发现某种我们指定的模式并提取出我们需要的信息,当然对于初学者来收,要编写一个正确的适当的正则表达式可能并不是一件容易的事情(当然有些常用的正则表达式可以直接在网上找找),所以实际开发爬虫应用的时候,有很多人会选择[Beautiful Soup](https://www.crummy.com/software/BeautifulSoup/)[Lxml](http://lxml.de/)来进行匹配和信息的提取,前者简单方便但是性能较差,后者既好用性能也好,但是安装稍嫌麻烦,这些内容我们会在后期的爬虫专题中为大家介绍。
\ No newline at end of file
"""
异步I/O操作 - asyncio模块
Version: 0.1
Author: 骆昊
Date: 2018-03-21
"""
import asyncio
import threading
# import time
@asyncio.coroutine
def hello():
print('%s: hello, world!' % threading.current_thread())
# 休眠不会阻塞主线程因为使用了异步I/O操作
# 注意有yield from才会等待休眠操作执行完成
yield from asyncio.sleep(2)
# asyncio.sleep(1)
# time.sleep(1)
print('%s: goodbye, world!' % threading.current_thread())
loop = asyncio.get_event_loop()
tasks = [hello(), hello()]
# 等待两个异步I/O操作执行结束
loop.run_until_complete(asyncio.wait(tasks))
print('game over!')
loop.close()
"""
异步I/O操作 - async和await
Version: 0.1
Author: 骆昊
Date: 2018-03-21
"""
import asyncio
import threading
# 通过async修饰的函数不再是普通函数而是一个协程
# 注意async和await将在Python 3.7中作为关键字出现
async def hello():
print('%s: hello, world!' % threading.current_thread())
await asyncio.sleep(2)
print('%s: goodbye, world!' % threading.current_thread())
loop = asyncio.get_event_loop()
tasks = [hello(), hello()]
# 等待两个异步I/O操作执行结束
loop.run_until_complete(asyncio.wait(tasks))
loop.close()
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册