update so many files

pull/6/head
jackfrued 2018-04-27 00:00:22 +08:00
parent 8cc34921f7
commit 703a9c1616
134 changed files with 17342 additions and 0 deletions

27
Day07/avgscore.py 100644
View File

@ -0,0 +1,27 @@
"""
输入学生考试成绩计算平均分
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()

34
Day07/dict1.py 100644
View File

@ -0,0 +1,34 @@
"""
定义和使用字典
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()

33
Day07/dict2.py 100644
View File

@ -0,0 +1,33 @@
"""
字典的常用操作
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()

22
Day07/fibonacci.py 100644
View File

@ -0,0 +1,22 @@
"""
生成斐波拉切数列
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()

29
Day07/findmax.py 100644
View File

@ -0,0 +1,29 @@
"""
找出列表中最大或最小的元素
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()
# 想一想如果最大的元素有两个要找出第二大的又该怎么做

40
Day07/list1.py 100644
View File

@ -0,0 +1,40 @@
"""
定义和使用列表
- 用下标访问元素
- 添加元素
- 删除元素
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()

39
Day07/list2.py 100644
View File

@ -0,0 +1,39 @@
"""
列表常用操作
- 列表连接
- 获取长度
- 遍历列表
- 列表切片
- 列表排序
- 列表反转
- 查找元素
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()

47
Day07/list3.py 100644
View File

@ -0,0 +1,47 @@
"""
生成列表
- 用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()

50
Day07/lottery.py 100644
View File

@ -0,0 +1,50 @@
"""
双色球随机选号程序
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()

26
Day07/marquee.py 100644
View File

@ -0,0 +1,26 @@
"""
输入学生考试成绩计算平均分
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()

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 143 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

View File

@ -0,0 +1,30 @@
"""
学生考试成绩表
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()

39
Day07/set1.py 100644
View File

@ -0,0 +1,39 @@
"""
定义和使用集合
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()

42
Day07/set2.py 100644
View File

@ -0,0 +1,42 @@
"""
集合的常用操作
- 交集
- 并集
- 差集
- 子集
- 超集
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()

View File

@ -0,0 +1,53 @@
"""
井字棋游戏
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()

42
Day07/tuple.py 100644
View File

@ -0,0 +1,42 @@
"""
元组的定义和使用
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()

34
Day07/yanghui.py 100644
View File

@ -0,0 +1,34 @@
"""
输出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()

View File

@ -0,0 +1,637 @@
## 字符串和常用数据结构
### 使用字符串
第二次世界大战促使了现代电子计算机的诞生当初的想法很简单就是用计算机来计算导弹的弹道因此在计算机刚刚诞生的那个年代计算机处理的信息主要是数值而世界上的第一台电子计算机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将日常工作自动化的人来说还是不错的教材对代码做了一点点的调整。

18
Day08/access.py 100644
View File

@ -0,0 +1,18 @@
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()

44
Day08/circle.py 100644
View File

@ -0,0 +1,44 @@
"""
练习
修一个游泳池 半径(以米为单位)在程序运行时输入 游泳池外修一条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))

53
Day08/clock.py 100644
View File

@ -0,0 +1,53 @@
"""
定义和使用时钟类
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()

58
Day08/guess.py 100644
View File

@ -0,0 +1,58 @@
"""
面向对象版本的猜数字游戏
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'

27
Day08/hack.py 100644
View File

@ -0,0 +1,27 @@
"""
另一种创建类的方式
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()

45
Day08/rect.py 100644
View File

@ -0,0 +1,45 @@
"""
定义和使用矩形类
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())

Binary file not shown.

After

Width:  |  Height:  |  Size: 349 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 398 KiB

46
Day08/student.py 100644
View File

@ -0,0 +1,46 @@
"""
定义和使用学生类
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()

0
Day08/test.py 100644
View File

View File

@ -0,0 +1,252 @@
## 面向对象编程基础
活在当下的程序员应该都听过“面向对象编程”一词,也经常有人问能不能用一句话解释下什么是“面向对象编程”,我们先来看看比较正式的说法。
> 把一组数据结构和处理它们的方法组成对象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)一书,该书是讲解面向对象编程的经典著作,有兴趣的读者可以购买和阅读这本书来了解更多的面向对象的相关知识。

View File

@ -0,0 +1,73 @@
"""
对象之间的关联关系
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)

65
Day09/car1.py 100644
View File

@ -0,0 +1,65 @@
"""
属性的使用
- 访问器/修改器/删除器
- 使用__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中有很多类似的语法糖后面还会出现这样的东西

50
Day09/car2.py 100644
View File

@ -0,0 +1,50 @@
"""
属性的使用
- 使用已有方法定义访问器/修改器/删除器
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)

44
Day09/clock.py 100644
View File

@ -0,0 +1,44 @@
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()

View File

@ -0,0 +1,83 @@
"""
对象之间的依赖关系和运算符重载
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)

47
Day09/diamond.py 100644
View File

@ -0,0 +1,47 @@
"""
多重继承
- 菱形继承(钻石继承)
- 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()

77
Day09/employee.py 100644
View File

@ -0,0 +1,77 @@
"""
抽象类 / 方法重写 / 多态
实现一个工资结算系统 公司有三种类型的员工
- 部门经理固定月薪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()))

68
Day09/multi.py 100644
View File

@ -0,0 +1,68 @@
"""
多重继承
- 通过多重继承可以给一个类的对象具备多方面的能力
- 这样在设计类的时候可以避免设计太多层次的复杂的继承关系
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()

33
Day09/pet.py 100644
View File

@ -0,0 +1,33 @@
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()

76
Day09/rational.py 100644
View File

@ -0,0 +1,76 @@
"""
运算符重载 - 自定义分数类
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))

Binary file not shown.

After

Width:  |  Height:  |  Size: 93 KiB

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

66
Day09/shape.py 100644
View File

@ -0,0 +1,66 @@
"""
继承的应用
- 抽象类
- 抽象方法
- 方法重写
- 多态
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())

53
Day09/triangle.py 100644
View File

@ -0,0 +1,53 @@
"""
实例方法和类方法的应用
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('不能构成三角形.')

View File

@ -0,0 +1,766 @@
## 面向对象进阶
在前面的章节我们已经了解了面向对象的入门知识知道了如何定义类如何创建对象以及如何给对象发消息。为了能够更好的使用面向对象编程思想进行程序开发我们还需要对Python中的面向对象编程进行更为深入的了解。
### @property装饰器
之前我们讨论过Python中属性和方法访问权限的问题虽然我们不建议将属性设置为私有的但是如果直接将属性暴露给外界也是有问题的比如我们没有办法检查赋给属性的值是否有效。我们之前的建议是将属性命名以单下划线开头通过这种方式来暗示属性是受保护的不建议外界直接访问那么如果想访问属性可以通过属性的getter访问器和setter修改器方法进行对应的操作。如果要做到这点就可以考虑使用@property包装器来包装getter和setter方法使得对属性的访问既安全又方便代码如下所示。
```Python
class Person(object):
def __init__(self, name, age):
self._name = name
self._age = age
# 访问器 - getter方法
@property
def name(self):
return self._name
# 访问器 - getter方法
@property
def age(self):
return self._age
# 修改器 - setter方法
@age.setter
def age(self, age):
self._age = age
def play(self):
if self._age <= 16:
print('%s正在玩飞行棋.' % self._name)
else:
print('%s正在玩斗地主.' % self._name)
def main():
person = Person('王大锤', 12)
person.play()
person.age = 22
person.play()
# person.name = '白元芳' # AttributeError: can't set attribute
if __name__ == '__main__':
main()
```
### \_\_slots\_\_魔法
我们讲到这里不知道大家是否已经意识到Python是一门[动态语言](https://zh.wikipedia.org/wiki/%E5%8A%A8%E6%80%81%E8%AF%AD%E8%A8%80)。通常,动态语言允许我们在程序运行时给对象绑定新的属性或方法,当然也可以对已经绑定的属性和方法进行解绑定。但是如果我们需要限定自定义类型的对象只能绑定某些属性,可以通过在类中定义\_\_slots\_\_变量来进行限定。需要注意的是\_\_slots\_\_的限定只对当前类的对象生效对子类并不起任何作用。
```Python
class Person(object):
# 限定Person对象只能绑定_name, _age和_gender属性
__slots__ = ('_name', '_age', '_gender')
def __init__(self, name, age):
self._name = name
self._age = age
@property
def name(self):
return self._name
@property
def age(self):
return self._age
@age.setter
def age(self, age):
self._age = age
def play(self):
if self._age <= 16:
print('%s正在玩飞行棋.' % self._name)
else:
print('%s正在玩斗地主.' % self._name)
def main():
person = Person('王大锤', 22)
person.play()
person._gender = '男'
# AttributeError: 'Person' object has no attribute '_is_gay'
# person._is_gay = True
```
### 静态方法和类方法
之前,我们在类中定义的方法都是对象方法,也就是说这些方法都是发送给对象的消息。实际上,我们写在类中的方法并不需要都是对象方法,例如我们定义一个“三角形”类,通过传入三条边长来构造三角形,并提供计算周长和面积的方法,但是传入的三条边长未必能构造出三角形对象,因此我们可以先写一个方法来验证三条边长是否可以构成三角形,这个方法很显然就不是对象方法,因为在调用这个方法时三角形对象尚未创建出来(因为都不知道三条边能不能构成三角形),所以这个方法是属于三角形类而并不属于三角形对象的。我们可以使用静态方法来解决这类问题,代码如下所示。
```Python
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 a + c > b
def perimeter(self):
return self._a + self._b + self._c
def area(self):
half = self.perimeter() / 2
return sqrt(half * (half - self._a) *
(half - self._b) * (half - self._c))
def main():
a, b, c = 3, 4, 5
# 静态方法和类方法都是通过给类发消息来调用的
if Triangle.is_valid(a, b, c):
t = Triangle(a, b, c)
print(t.perimeter())
# 也可以通过给类发消息来调用对象方法但是要传入接收消息的对象作为参数
# print(Triangle.perimeter(t))
print(t.area())
# print(Triangle.area(t))
else:
print('无法构成三角形.')
if __name__ == '__main__':
main()
```
和静态方法比较类似Python还可以在类中定义类方法类方法的第一个参数约定名为cls它代表的是当前类相关的信息的对象类本身也是一个对象有的地方也称之为类的元数据对象通过这个参数我们可以获取和类相关的信息并且可以创建出类的对象代码如下所示。
```Python
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()
```
### 类之间的关系
简单的说类和类之间的关系有三种is-a、has-a和use-a关系。
- is-a关系也叫继承或泛化比如学生和人的关系、手机和电子产品的关系都属于继承关系。
- has-a关系通常称之为关联比如部门和员工的关系汽车和引擎的关系都属于关联关系关联关系如果是整体和部分的关联那么我们称之为聚合关系如果整体进一步负责了部分的生命周期整体和部分是不可分割的同时同在也同时消亡那么这种就是最强的关联关系我们称之为合成关系。
- use-a关系通常称之为依赖比如司机有一个驾驶的行为方法其中的参数使用到了汽车那么司机和汽车的关系就是依赖关系。
我们可以使用一种叫做[UML](https://zh.wikipedia.org/wiki/%E7%BB%9F%E4%B8%80%E5%BB%BA%E6%A8%A1%E8%AF%AD%E8%A8%80)统一建模语言的东西来进行面向对象建模其中一项重要的工作就是把类和类之间的关系用标准化的图形符号描述出来。关于UML我们在这里不做详细的介绍有兴趣的读者可以自行阅读[《UML面向对象设计基础》](https://e.jd.com/30392949.html)一书。
![](./res/uml-components.png)
![](./res/uml-example.png)
利用类之间的这些关系,我们可以在已有类的基础上来完成某些操作,也可以在已有类的基础上创建新的类,这些都是实现代码复用的重要手段。复用现有的代码不仅可以减少开发的工作量,也有利于代码的管理和维护,这是我们在日常工作中都会使用到的技术手段。
### 继承和多态
刚才我们提到了,可以在已有类的基础上创建新类,这其中的一种做法就是让一个类从另一个类那里将属性和方法直接继承下来,从而减少重复代码的编写。提供继承信息的我们称之为父类,也叫超类或基类;得到继承信息的我们称之为子类,也叫派生类或衍生类。子类除了继承父类提供的属性和方法,还可以定义自己特有的属性和方法,所以子类比父类拥有的更多的能力,在实际开发中,我们经常会用子类对象去替换掉一个父类对象,这是面向对象编程中一个常见的行为,对应的原则称之为[里氏替换原则](https://zh.wikipedia.org/wiki/%E9%87%8C%E6%B0%8F%E6%9B%BF%E6%8D%A2%E5%8E%9F%E5%88%99)。下面我们先看一个继承的例子。
```Python
class Person(object):
"""人"""
def __init__(self, name, age):
self._name = name
self._age = age
@property
def name(self):
return self._name
@property
def age(self):
return self._age
@age.setter
def age(self, age):
self._age = age
def play(self):
print('%s正在愉快的玩耍.' % self._name)
def watch_av(self):
if self._age >= 18:
print('%s正在观看爱情动作片.' % self._name)
else:
print('%s只能观看《熊出没》.' % self._name)
class Student(Person):
"""学生"""
def __init__(self, name, age, grade):
super().__init__(name, age)
self._grade = grade
@property
def grade(self):
return self._grade
@grade.setter
def grade(self, grade):
self._grade = grade
def study(self, course):
print('%s的%s正在学习%s.' % (self._grade, self._name, course))
class Teacher(Person):
"""老师"""
def __init__(self, name, age, title):
super().__init__(name, age)
self._title = title
@property
def title(self):
return self._title
@title.setter
def title(self, title):
self._title = title
def teach(self, course):
print('%s%s正在讲%s.' % (self._name, self._title, course))
def main():
stu = Student('王大锤', 15, '初三')
stu.study('数学')
stu.watch_av()
t = Teacher('骆昊', 38, '老叫兽')
t.teach('Python程序设计')
t.watch_av()
if __name__ == '__main__':
main()
```
子类在继承了父类的方法后可以对父类已有的方法给出新的实现版本这个动作称之为方法重写override。通过方法重写我们可以让父类的同一个行为在子类中拥有不同的实现版本当我们调用这个经过子类重写的方法时不同的子类对象会表现出不同的行为这个就是多态poly-morphism
```Python
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()
```
在上面的代码中,我们将`Pet`类处理成了一个抽象类所谓抽象类就是不能够创建对象的类这种类的存在就是专门为了让其他类去继承它。Python从语法层面并没有像Java或C#那样提供对抽象类的支持,但是我们可以通过`abc`模块的`ABCMeta`元类和`abstractmethod`包装器来达到抽象类的效果,如果一个类中存在抽象方法那么这个类就不能够实例化(创建对象)。上面的代码中,`Dog`和`Cat`两个子类分别对`Pet`类中的`make_voice`抽象方法进行了重写并给出了不同的实现版本,当我们在`main`函数中调用该方法时,这个方法就表现出了多态行为(同样的方法做了不同的事情)。
### 综合案例
#### 案例1奥特曼打小怪兽
```Python
from abc import ABCMeta, abstractmethod
from random import randint, randrange
class Fighter(object, metaclass=ABCMeta):
"""战斗者"""
# 通过__slots__魔法限定对象可以绑定的成员变量
__slots__ = ('_name', '_hp')
def __init__(self, name, hp):
"""
初始化方法
:param name: 名字
:param hp: 生命值
"""
self._name = name
self._hp = hp
@property
def name(self):
return self._name
@property
def hp(self):
return self._hp
@hp.setter
def hp(self, hp):
self._hp = hp if hp >= 0 else 0
@property
def alive(self):
return self._hp > 0
@abstractmethod
def attack(self, other):
"""
攻击
:param other: 被攻击的对象
"""
pass
class Ultraman(Fighter):
"""奥特曼"""
__slots__ = ('_name', '_hp', '_mp')
def __init__(self, name, hp, mp):
"""
初始化方法
:param name: 名字
:param hp: 生命值
:param mp: 魔法值
"""
super().__init__(name, hp)
self._mp = mp
def attack(self, other):
other.hp -= randint(15, 25)
def huge_attack(self, other):
"""
究极必杀技(打掉对方至少50点或四分之三的血)
:param other: 被攻击的对象
:return: 使用成功返回True否则返回False
"""
if self._mp >= 50:
self._mp -= 50
injury = other.hp * 3 // 4
injury = injury if injury >= 50 else 50
other.hp -= injury
return True
else:
self.attack(other)
return False
def magic_attack(self, others):
"""
魔法攻击
:param others: 被攻击的群体
:return: 使用魔法成功返回True否则返回False
"""
if self._mp >= 20:
self._mp -= 20
for temp in others:
if temp.alive:
temp.hp -= randint(10, 15)
return True
else:
return False
def resume(self):
"""恢复魔法值"""
incr_point = randint(1, 10)
self._mp += incr_point
return incr_point
def __str__(self):
return '~~~%s奥特曼~~~\n' % self._name + \
'生命值: %d\n' % self._hp + \
'魔法值: %d\n' % self._mp
class Monster(Fighter):
"""小怪兽"""
__slots__ = ('_name', '_hp')
def attack(self, other):
other.hp -= randint(10, 20)
def __str__(self):
return '~~~%s小怪兽~~~\n' % self._name + \
'生命值: %d\n' % self._hp
def is_any_alive(monsters):
"""判断有没有小怪兽是活着的"""
for monster in monsters:
if monster.alive > 0:
return True
return False
def select_alive_one(monsters):
"""选中一只活着的小怪兽"""
monsters_len = len(monsters)
while True:
index = randrange(monsters_len)
monster = monsters[index]
if monster.alive > 0:
return monster
def display_info(ultraman, monsters):
"""显示奥特曼和小怪兽的信息"""
print(ultraman)
for monster in monsters:
print(monster, end='')
def main():
u = Ultraman('骆昊', 1000, 120)
m1 = Monster('舒小玲', 250)
m2 = Monster('白元芳', 500)
m3 = Monster('王大锤', 750)
ms = [m1, m2, m3]
fight_round = 1
while u.alive and is_any_alive(ms):
print('========第%02d回合========' % fight_round)
m = select_alive_one(ms) # 选中一只小怪兽
skill = randint(1, 10) # 通过随机数选择使用哪种技能
if skill <= 6: # 60%的概率使用普通攻击
print('%s使用普通攻击打了%s.' % (u.name, m.name))
u.attack(m)
print('%s的魔法值恢复了%d点.' % (u.name, u.resume()))
elif skill <= 9: # 30%的概率使用魔法攻击(可能因魔法值不足而失败)
if u.magic_attack(ms):
print('%s使用了魔法攻击.' % u.name)
else:
print('%s使用魔法失败.' % u.name)
else: # 10%的概率使用究极必杀技(如果魔法值不足则使用普通攻击)
if u.huge_attack(m):
print('%s使用究极必杀技虐了%s.' % (u.name, m.name))
else:
print('%s使用普通攻击打了%s.' % (u.name, m.name))
print('%s的魔法值恢复了%d点.' % (u.name, u.resume()))
if m.alive > 0: # 如果选中的小怪兽没有死就回击奥特曼
print('%s回击了%s.' % (m.name, u.name))
m.attack(u)
display_info(u, ms) # 每个回合结束后显示奥特曼和小怪兽的信息
fight_round += 1
print('\n========战斗结束!========\n')
if u.alive > 0:
print('%s奥特曼胜利!' % u.name)
else:
print('小怪兽胜利!')
if __name__ == '__main__':
main()
```
#### 案例2扑克游戏
```Python
from random import randrange
class Card(object):
"""一张牌"""
def __init__(self, suite, face):
self._suite = suite
self._face = face
@property
def face(self):
return self._face
@property
def suite(self):
return self._suite
def __str__(self):
all_suites = ('♠', '♥', '♣', '♦')
if self._face == 1:
face_str = 'A'
elif self._face == 11:
face_str = 'J'
elif self._face == 12:
face_str = 'Q'
elif self._face == 13:
face_str = 'K'
else:
face_str = str(self._face)
return '%s%s' % (all_suites[self._suite], face_str)
class Poker(object):
"""一副牌"""
def __init__(self):
self._cards = []
self._current = 0
for suite in range(4):
for face in range(1, 14):
card = Card(suite, face)
self._cards.append(card)
@property
def cards(self):
return self._cards
def shuffle(self):
"""洗牌(随机乱序)"""
self._current = 0
cards_len = len(self._cards)
for index in range(cards_len):
pos = randrange(cards_len)
self._cards[index], self._cards[pos] = \
self._cards[pos], self._cards[index]
@property
def next(self):
"""发牌"""
card = self._cards[self._current]
self._current += 1
return card
@property
def has_next(self):
"""还有没有牌"""
return self._current < len(self._cards)
class Player(object):
"""玩家"""
def __init__(self, name):
self._name = name
self._cards_on_hand = []
@property
def name(self):
return self._name
@property
def cards_on_hand(self):
return self._cards_on_hand
def get(self, card):
"""摸牌"""
self._cards_on_hand.append(card)
def arrange(self, card_key):
"""玩家整理手上的牌"""
self._cards_on_hand.sort(key=card_key)
# 排序规则-先根据花色再根据点数排序
def get_key(card):
return (card.suite, card.face)
def main():
p = Poker()
p.shuffle()
players = [Player('东邪'), Player('西毒'), Player('南帝'), Player('北丐')]
for _ in range(13):
for player in players:
player.get(p.next)
for player in players:
print(player.name + ':', end=' ')
player.arrange(get_key)
for card in player.cards_on_hand:
print(card, end=' ')
print()
if __name__ == '__main__':
main()
```
>**说明**大家可以自己尝试在上面代码的基础上写一个简单的扑克游戏例如21点(Black Jack),游戏的规则可以自己在网上找一找。
#### 案例3工资结算系统
```Python
"""
某公司有三种类型的员工 分别是部门经理、程序员和销售员
需要设计一个工资结算系统 根据提供的员工信息来计算月薪
部门经理的月薪是每月固定15000元
程序员的月薪按本月工作时间计算 每小时150元
销售员的月薪是1200元的底薪加上销售额5%的提成
"""
from abc import ABCMeta, abstractmethod
class Employee(object, metaclass=ABCMeta):
"""员工"""
def __init__(self, name):
"""
初始化方法
:param name: 姓名
"""
self._name = name
@property
def name(self):
return self._name
@abstractmethod
def get_salary(self):
"""
获得月薪
:return: 月薪
"""
pass
class Manager(Employee):
"""部门经理"""
def get_salary(self):
return 15000.0
class Programmer(Employee):
"""程序员"""
def __init__(self, name, working_hour=0):
super().__init__(name)
self._working_hour = working_hour
@property
def working_hour(self):
return self._working_hour
@working_hour.setter
def working_hour(self, working_hour):
self._working_hour = working_hour if working_hour > 0 else 0
def get_salary(self):
return 150.0 * self._working_hour
class Salesman(Employee):
"""销售员"""
def __init__(self, name, sales=0):
super().__init__(name)
self._sales = sales
@property
def sales(self):
return self._sales
@sales.setter
def sales(self, sales):
self._sales = sales if sales > 0 else 0
def get_salary(self):
return 1200.0 + self._sales * 0.05
def main():
emps = [
Manager('刘备'), Programmer('诸葛亮'),
Manager('曹操'), Salesman('荀彧'),
Salesman('吕布'), Programmer('张辽'),
Programmer('赵云')
]
for emp in emps:
if isinstance(emp, Programmer):
emp.working_hour = int(input('请输入%s本月工作时间: ' % emp.name))
elif isinstance(emp, Salesman):
emp.sales = float(input('请输入%s本月销售额: ' % emp.name))
# 同样是接收get_salary这个消息但是不同的员工表现出了不同的行为(多态)
print('%s本月工资为: ¥%s元' %
(emp.name, emp.get_salary()))
if __name__ == '__main__':
main()
```

109
Day10/ball.py 100644
View File

@ -0,0 +1,109 @@
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()

57
Day10/gui1.py 100644
View File

@ -0,0 +1,57 @@
"""
使用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()

43
Day10/gui2.py 100644
View File

@ -0,0 +1,43 @@
"""
使用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()
# 请思考如何用面向对象的编程思想对上面的代码进行封装

39
Day10/gui3.py 100644
View File

@ -0,0 +1,39 @@
"""
使用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()
# 请思考如何让小球碰到屏幕的边界就弹回
# 请思考如何用面向对象的编程思想对上面的代码进行封装

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

BIN
Day10/res/ball.png 100644

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

25
Day10/turtle1.py 100644
View File

@ -0,0 +1,25 @@
"""
用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()

View File

@ -0,0 +1,338 @@
## 图形用户界面和游戏开发
### 基于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/)。

0
Day11/.py 100644
View File

23
Day11/csv1.py 100644
View File

@ -0,0 +1,23 @@
"""
读取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]))

46
Day11/csv2.py 100644
View File

@ -0,0 +1,46 @@
"""
写入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('保存数据完成!')

23
Day11/ex1.py 100644
View File

@ -0,0 +1,23 @@
"""
异常机制 - 处理程序在运行时可能发生的状态
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')
# 处理异常让代码不因异常而崩溃是一方面
# 更重要的是可以通过对异常的处理让代码从异常中恢复过来

19
Day11/ex2.py 100644
View File

@ -0,0 +1,19 @@
"""
异常机制 - 处理程序在运行时可能发生的状态
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)

30
Day11/ex3.py 100644
View File

@ -0,0 +1,30 @@
"""
异常机制 - 处理程序在运行时可能发生的状态
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('不管发生什么我都会执行')

24
Day11/ex4.py 100644
View File

@ -0,0 +1,24 @@
"""
引发异常和异常栈
Version: 0.1
Author: 骆昊
Date: 2018-03-13
"""
def f1():
raise AssertionError('发生异常')
def f2():
f1()
def f3():
f2()
f3()

View File

@ -0,0 +1,7 @@
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
1 4/5/2014 13:34 Apples 73
2 4/5/2014 3:41 Cherries 85
3 4/6/2014 12:46 Pears 14
4 4/8/2014 8:59 Oranges 52
5 4/10/2014 2:07 Apples 152
6 4/10/2014 18:10 Bananas 23
7 4/10/2014 2:40 Strawberries 98

33
Day11/file1.py 100644
View File

@ -0,0 +1,33 @@
"""
从文本文件中读取数据
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()

18
Day11/file2.py 100644
View File

@ -0,0 +1,18 @@
"""
读取圆周率文件判断其中是否包含自己的生日
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!!!')

28
Day11/file3.py 100644
View File

@ -0,0 +1,28 @@
"""
写文本文件
将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('写入完成!')

23
Day11/file4.py 100644
View File

@ -0,0 +1,23 @@
"""
读写二进制文件
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('写入完成!')

87
Day11/json1.py 100644
View File

@ -0,0 +1,87 @@
"""
读取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": "北京"
}
"""

20
Day11/json2.py 100644
View File

@ -0,0 +1,20 @@
"""
写入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))

BIN
Day11/mm.jpg 100644

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

View File

@ -0,0 +1,2 @@
骆昊,38,叫兽
狄仁杰,25,砖家
1 骆昊 38 叫兽
2 狄仁杰 25 砖家

View File

@ -0,0 +1,274 @@
## 文件和异常
在实际开发中,常常需要对程序中的数据进行[持久化](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中异常机制的使用还总结了一系列的最佳实践很值得一读。

View File

@ -0,0 +1,7 @@
我如果爱你
绝不学攀援的凌霄花
借你的高枝炫耀自己
我如果爱你
绝不学痴情的鸟儿
为绿荫重复单调的歌曲

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

44
Day12/str1.py 100644
View File

@ -0,0 +1,44 @@
"""
字符串常用操作
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())

62
Day12/str2.py 100644
View File

@ -0,0 +1,62 @@
"""
字符串常用操作 - 实现字符串倒转的方法
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)
# 提醒学生注意这是一个面试题: 写出你能想到的实现字符串倒转的代码

29
Day12/test3.py 100644
View File

@ -0,0 +1,29 @@
"""
验证输入用户名和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()

27
Day12/test4.py 100644
View File

@ -0,0 +1,27 @@
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()

12
Day12/test5.py 100644
View File

@ -0,0 +1,12 @@
import re
def main():
sentence = '你丫是傻叉吗? 我操你大爷的. Fuck you.'
purified = re.sub('[操肏艹草曹]|fuck|shit|傻[比屄逼叉缺吊屌]|煞笔',
'*', sentence, flags=re.IGNORECASE)
print(purified)
if __name__ == '__main__':
main()

View File

@ -0,0 +1,189 @@
## 使用正则表达式
### 正则表达式相关知识
在编写处理字符串的程序或网页时经常会有查找符合某些复杂规则的字符串的需要正则表达式就是用于描述这些规则的工具换句话说正则表达式是一种工具它定义了字符串的匹配模式如何检查一个字符串是否有跟某种模式匹配的部分或者从一个字符串中将与模式匹配的部分提取出来或者替换掉。如果你在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/)来进行匹配和信息的提取,前者简单方便但是性能较差,后者既好用性能也好,但是安装稍嫌麻烦,这些内容我们会在后期的爬虫专题中为大家介绍。

32
Day13/asyncio1.py 100644
View File

@ -0,0 +1,32 @@
"""
异步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()

27
Day13/asyncio2.py 100644
View File

@ -0,0 +1,27 @@
"""
异步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()

38
Day13/asyncio3.py 100644
View File

@ -0,0 +1,38 @@
"""
异步I/O操作 - asyncio模块
Version: 0.1
Author: 骆昊
Date: 2018-03-21
"""
import asyncio
async def wget(host):
print('wget %s...' % host)
connect = asyncio.open_connection(host, 80)
# 异步方式等待连接结果
reader, writer = await connect
header = 'GET / HTTP/1.0\r\nHost: %s\r\n\r\n' % host
writer.write(header.encode('utf-8'))
# 异步I/O方式执行写操作
await writer.drain()
while True:
# 异步I/O方式执行读操作
line = await reader.readline()
if line == b'\r\n':
break
print('%s header > %s' % (host, line.decode('utf-8').rstrip()))
writer.close()
loop = asyncio.get_event_loop()
# 通过生成式语法创建一个装了三个协程的列表
hosts_list = ['www.sina.com.cn', 'www.sohu.com', 'www.163.com']
tasks = [wget(host) for host in hosts_list]
# 下面的方法将异步I/O操作放入EventLoop直到执行完毕
loop.run_until_complete(asyncio.wait(tasks))
loop.close()

View File

@ -0,0 +1,42 @@
"""
使用协程 - 模拟快递中心派发快递
Version: 0.1
Author: 骆昊
Date: 2018-03-21
"""
from time import sleep
from random import random
def build_deliver_man(man_id):
total = 0
while True:
total += 1
print('%d号快递员准备接今天的第%d单.' % (man_id, total))
pkg = yield
print('%d号快递员收到编号为%s的包裹.' % (man_id, pkg))
sleep(random() * 3)
def package_center(deliver_man, max_per_day):
num = 1
deliver_man.send(None)
# next(deliver_man)
while num <= max_per_day:
package_id = 'PKG-%d' % num
deliver_man.send(package_id)
num += 1
sleep(0.1)
deliver_man.close()
print('今天的包裹派送完毕!')
dm = build_deliver_man(1)
package_center(dm, 10)
# 两个函数虽然没有调用关系但是创建快递员的函数作为一个协程协助了快递中心函数完成任务
# 想一想如果有多个快递员的时候应该如何处理

View File

@ -0,0 +1,44 @@
"""
使用协程 - 查看协程的状态
Version: 0.1
Author: 骆昊
Date: 2018-03-21
"""
from time import sleep
from inspect import getgeneratorstate
def build_deliver_man(man_id):
total = 0
while True:
total += 1
print('%d号快递员准备接今天的第%d单.' % (man_id, total))
pkg = yield
print('%d号快递员收到编号为%s的包裹.' % (man_id, pkg))
sleep(0.5)
def package_center(deliver_man, max_per_day):
num = 1
# 创建状态(GEN_CREATED) - 等待开始执行
print(getgeneratorstate(deliver_man))
deliver_man.send(None)
# 挂起状态(GEN_SUSPENDED) - 在yield表达式处暂停
print(getgeneratorstate(deliver_man))
# next(deliver_man)
while num <= max_per_day:
package_id = 'PKG-%d' % num
deliver_man.send(package_id)
num += 1
deliver_man.close()
# 结束状态(GEN_CLOSED) - 执行完毕
print(getgeneratorstate(deliver_man))
print('今天的包裹派送完毕!')
dm = build_deliver_man(1)
package_center(dm, 10)

View File

@ -0,0 +1,25 @@
"""
生成器 - 生成器语法
Version: 0.1
Author: 骆昊
Date: 2018-03-21
"""
seq = [x * x for x in range(10)]
print(seq)
gen = (x * x for x in range(10))
print(gen)
for x in gen:
print(x)
num = 10
gen = (x ** y for x, y in zip(range(1, num), range(num - 1, 0, -1)))
print(gen)
n = 1
while n < num:
print(next(gen))
n += 1

View File

@ -0,0 +1,21 @@
"""
生成器 - 使用yield关键字
Version: 0.1
Author: 骆昊
Date: 2018-03-21
"""
def fib(num):
n, a, b = 0, 0, 1
while n < num:
yield b
a, b = b, a + b
n += 1
for x in fib(20):
print(x)

View File

@ -0,0 +1,36 @@
"""
使用Process类创建多个进程
Version: 0.1
Author: 骆昊
Date: 2018-03-20
"""
# 通过下面程序的执行结果可以证实 父进程在创建子进程时复制了进程及其数据结构
# 每个进程都有自己独立的内存空间 所以进程之间共享数据只能通过IPC的方式
from multiprocessing import Process, Queue
from time import sleep
def sub_task(string, q):
number = q.get()
while number:
print('%d: %s' % (number, string))
sleep(0.001)
number = q.get()
def main():
q = Queue(10)
for number in range(1, 11):
q.put(number)
Process(target=sub_task, args=('Ping', q)).start()
Process(target=sub_task, args=('Pong', q)).start()
if __name__ == '__main__':
main()

View File

@ -0,0 +1,35 @@
"""
实现进程间的通信
Version: 0.1
Author: 骆昊
Date: 2018-03-20
"""
import multiprocessing
import os
def sub_task(queue):
print('子进程进程号:', os.getpid())
counter = 0
while counter < 1000:
queue.put('Pong')
counter += 1
if __name__ == '__main__':
print('当前进程号:', os.getpid())
queue = multiprocessing.Queue()
p = multiprocessing.Process(target=sub_task, args=(queue,))
p.start()
counter = 0
while counter < 1000:
queue.put('Ping')
counter += 1
p.join()
print('子任务已经完成.')
for _ in range(2000):
print(queue.get(), end='')

View File

@ -0,0 +1,29 @@
"""
创建进程调用其他程序
Version: 0.1
Author: 骆昊
Date: 2018-03-20
"""
import subprocess
import sys
def main():
# 通过sys.argv获取命令行参数
if len(sys.argv) > 1:
# 第一个命令行参数是程序本身所以从第二个开始取
for index in range(1, len(sys.argv)):
try:
# 通过subprocess模块的call函数启动子进程
status = subprocess.call(sys.argv[index])
except FileNotFoundError:
print('不能执行%s命令' % sys.argv[index])
else:
print('请使用命令行参数指定要执行的进程')
if __name__ == '__main__':
main()

View File

@ -0,0 +1,16 @@
from time import time
def main():
total = 0
number_list = [x for x in range(1, 100000001)]
start = time()
for number in number_list:
total += number
print(total)
end = time()
print('Execution time: %.3fs' % (end - start))
if __name__ == '__main__':
main()

View File

@ -0,0 +1,44 @@
"""
使用多线程的情况 - 模拟多个下载任务
Version: 0.1
Author: 骆昊
Date: 2018-03-20
"""
from random import randint
from time import time, sleep
import atexit
import _thread
def download_task(filename):
print('开始下载%s...' % filename)
time_to_download = randint(5, 10)
print('剩余时间%d秒.' % time_to_download)
sleep(time_to_download)
print('%s下载完成!' % filename)
def shutdown_hook(start):
end = time()
print('总共耗费了%.3f秒.' % (end - start))
def main():
start = time()
# 将多个下载任务放到多个线程中执行
thread1 = _thread.start_new_thread(download_task, ('Python从入门到住院.pdf',))
thread2 = _thread.start_new_thread(download_task, ('Peking Hot.avi',))
# 注册关机钩子在程序执行结束前计算执行时间
atexit.register(shutdown_hook, start)
if __name__ == '__main__':
main()
# 执行这里的代码会引发致命错误(不要被这个词吓到) 因为主线程结束后下载线程再想执行就会出问题
# 需要说明一下 由于_thread模块属于比较底层的线程操作而且不支持守护线程的概念
# 在实际开发中会有诸多不便 因此我们推荐使用threading模块提供的高级操作进行多线程编程

View File

@ -0,0 +1,36 @@
"""
使用多线程的情况 - 模拟多个下载任务
Version: 0.1
Author: 骆昊
Date: 2018-03-20
"""
from random import randint
from threading import Thread
from time import time, sleep
def download_task(filename):
print('开始下载%s...' % filename)
time_to_download = randint(5, 10)
sleep(time_to_download)
print('%s下载完成! 耗费了%d' % (filename, time_to_download))
def main():
start = time()
thread1 = Thread(target=download_task, args=('Python从入门到住院.pdf',))
thread1.start()
thread2 = Thread(target=download_task, args=('Peking Hot.avi',))
thread2.start()
thread1.join()
thread2.join()
end = time()
print('总共耗费了%.3f' % (end - start))
if __name__ == '__main__':
main()

View File

@ -0,0 +1,47 @@
"""
使用多线程的情况 - 模拟多个下载任务
Version: 0.1
Author: 骆昊
Date: 2018-03-20
"""
from random import randint
from time import time, sleep
import threading
class DownloadTask(threading.Thread):
def __init__(self, filename):
super().__init__()
self._filename = filename
def run(self):
print('开始下载%s...' % self._filename)
time_to_download = randint(5, 10)
print('剩余时间%d秒.' % time_to_download)
sleep(time_to_download)
print('%s下载完成!' % self._filename)
def main():
start = time()
# 将多个下载任务放到多个线程中执行
# 通过自定义的线程类创建线程对象 线程启动后会回调执行run方法
thread1 = DownloadTask('Python从入门到住院.pdf')
thread1.start()
thread2 = DownloadTask('Peking Hot.avi')
thread2.start()
thread1.join()
thread2.join()
end = time()
print('总共耗费了%.3f' % (end - start))
if __name__ == '__main__':
main()
# 请注意通过threading.Thread创建的线程默认是非守护线程

View File

@ -0,0 +1,53 @@
"""
使用多线程的情况 - 耗时间的任务在独立的线程中执行
Version: 0.1
Author: 骆昊
Date: 2018-03-20
"""
import time
import tkinter
import tkinter.messagebox
from threading import Thread
def main():
class DownloadTaskHandler(Thread):
def run(self):
# 模拟下载任务需要花费10秒钟时间
time.sleep(10)
tkinter.messagebox.showinfo('提示', '下载完成!')
# 启用下载按钮
button1.config(state=tkinter.NORMAL)
def download():
# 禁用下载按钮
button1.config(state=tkinter.DISABLED)
# 通过daemon参数将线程设置为守护线程(主程序退出就不再保留执行)
DownloadTaskHandler(daemon=True).start()
def show_about():
tkinter.messagebox.showinfo('关于', '作者: 骆昊(v1.0)')
top = tkinter.Tk()
top.title('单线程')
top.geometry('200x150')
top.wm_attributes('-topmost', 1)
panel = tkinter.Frame(top)
button1 = tkinter.Button(panel, text='下载', command=download)
button1.pack(side='left')
button2 = tkinter.Button(panel, text='关于', command=show_about)
button2.pack(side='right')
panel.pack(side='bottom')
tkinter.mainloop()
if __name__ == '__main__':
main()

View File

@ -0,0 +1,63 @@
"""
多个线程共享数据 - 没有锁的情况
Version: 0.1
Author: 骆昊
Date: 2018-03-20
"""
from time import sleep
from threading import Thread, Lock
class Account(object):
def __init__(self):
self._balance = 0
self._lock = Lock()
def deposit(self, money):
# 先获取锁才能执行后续的代码
self._lock.acquire()
try:
new_balance = self._balance + money
sleep(0.01)
self._balance = new_balance
finally:
# 这段代码放在finally中保证释放锁的操作一定要执行
self._lock.release()
@property
def balance(self):
return self._balance
class AddMoneyThread(Thread):
def __init__(self, account, money):
super().__init__()
self._account = account
self._money = money
def run(self):
self._account.deposit(self._money)
def main():
account = Account()
threads = []
# 创建100个存款的线程向同一个账户中存钱
for _ in range(100):
t = AddMoneyThread(account, 1)
threads.append(t)
t.start()
# 等所有存款的线程都执行完毕∫
for t in threads:
t.join()
print('账户余额为: ¥%d' % account.balance)
if __name__ == '__main__':
main()

View File

@ -0,0 +1,46 @@
"""
多个线程共享数据 - 有锁的情况
Version: 0.1
Author: 骆昊
Date: 2018-03-20
"""
import time
import threading
class Account(object):
def __init__(self):
self._balance = 0
self._lock = threading.Lock()
def deposit(self, money):
# 获得锁后代码才能继续执行
self._lock.acquire()
try:
new_balance = self._balance + money
time.sleep(0.01)
self._balance = new_balance
finally:
# 操作完成后一定要记着释放锁
self._lock.release()
@property
def balance(self):
return self._balance
if __name__ == '__main__':
account = Account()
# 创建100个存款的线程向同一个账户中存钱
for _ in range(100):
threading.Thread(target=account.deposit, args=(1,)).start()
# 等所有存款的线程都执行完毕
time.sleep(2)
print('账户余额为: ¥%d' % account.balance)
# 想一想结果为什么不是我们期望的100元

Binary file not shown.

After

Width:  |  Height:  |  Size: 186 KiB

Some files were not shown because too many files have changed in this diff Show More