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

更新了文档和静态资源

上级 ea9adc68
"""
查找 - 顺序查找和二分查找
算法:解决问题的方法(步骤)
评价一个算法的好坏主要有两个指标:渐近时间复杂度和渐近空间复杂度,通常一个算法很难做到时间复杂度和空间复杂度都很低(因为时间和空间是不可调和的矛盾)
表示渐近时间复杂度通常使用大O标记
O(c):常量时间复杂度 - 哈希存储 / 布隆过滤器
O(log_2 n):对数时间复杂度 - 折半查找
O(n):线性时间复杂度 - 顺序查找
O(n * log_2 n):- 对数线性时间复杂度 - 高级排序算法(归并排序、快速排序)
O(n ** 2):平方时间复杂度 - 简单排序算法(冒泡排序、选择排序、插入排序)
O(n ** 3):立方时间复杂度 - Floyd算法 / 矩阵乘法运算
也称为多项式时间复杂度
O(2 ** n):几何级数时间复杂度 - 汉诺塔
O(3 ** n):几何级数时间复杂度
也称为指数时间复杂度
O(n!):阶乘时间复杂度 - 旅行经销商问题 - NP
"""
from math import log2, factorial
from matplotlib import pyplot
import numpy
def seq_search(items: list, elem) -> int:
"""顺序查找"""
for index, item in enumerate(items):
if elem == item:
return index
return -1
def bin_search(items, elem):
"""二分查找"""
start, end = 0, len(items) - 1
while start <= end:
mid = (start + end) // 2
if elem > items[mid]:
start = mid + 1
elif elem < items[mid]:
end = mid - 1
else:
return mid
return -1
def main():
"""主函数(程序入口)"""
num = 6
styles = ['r-.', 'g-*', 'b-o', 'y-x', 'c-^', 'm-+', 'k-d']
legends = ['对数', '线性', '线性对数', '平方', '立方', '几何级数', '阶乘']
x_data = [x for x in range(1, num + 1)]
y_data1 = [log2(y) for y in range(1, num + 1)]
y_data2 = [y for y in range(1, num + 1)]
y_data3 = [y * log2(y) for y in range(1, num + 1)]
y_data4 = [y ** 2 for y in range(1, num + 1)]
y_data5 = [y ** 3 for y in range(1, num + 1)]
y_data6 = [3 ** y for y in range(1, num + 1)]
y_data7 = [factorial(y) for y in range(1, num + 1)]
y_datas = [y_data1, y_data2, y_data3, y_data4, y_data5, y_data6, y_data7]
for index, y_data in enumerate(y_datas):
pyplot.plot(x_data, y_data, styles[index])
pyplot.legend(legends)
pyplot.xticks(numpy.arange(1, 7, step=1))
pyplot.yticks(numpy.arange(0, 751, step=50))
pyplot.show()
if __name__ == '__main__':
main()
"""
排序 - 冒泡排序、选择排序、归并排序、快速排序
冒泡排序 - O(n ** 2):两两比较,大的下沉
35, 97, 12, 68, 55, 73, 81, 40
35, 12, 68, 55, 73, 81, 40, [97]
12, 35, 55, 68, 73, 40, [81]
12, 35, 55, 68, 40, [73]
...
选择排序 - O(n ** 2):每次从剩下元素中选择最小
-----------------------------------------
归并排序 - O(n * log_2 n) - 高级排序算法
35, 97, 12, 68, 55, 73, 81, 40
[35, 97, 12, 68], [55, 73, 81, 40]
[35, 97], [12, 68], [55, 73], [81, 40]
[35], [97], [12], [68], [55], [73], [81], [40]
[35, 97], [12, 68], [55, 73], [40, 81]
[12, 35, 68, 97], [40, 55, 73, 81]
[12, 35, 40, 55, 68, 73, 81, 97]
-----------------------------------------
快速排序 - 以枢轴为界将列表中的元素划分为两个部分,左边都比枢轴小,右边都比枢轴大
35, 97, 12, 68, 55, 73, 81, 40
35, 12, [40], 68, 55, 73, 81, 97
[12], 35, [40], 68, 55, 73, 81, [97]
[12], 35, [40], 55, [68], 73, 81, [97]
[12], 35, [40], 55, [68], 73, [81], [97]
"""
class Person(object):
"""人"""
def __init__(self, name, age):
self.name = name
self.age = age
# def __gt__(self, other):
# return self.name > other.name
def __str__(self):
return f'{self.name}: {self.age}'
def __repr__(self):
return self.__str__()
def select_sort(origin_items, comp=lambda x, y: x < y):
"""简单选择排序"""
items = origin_items[:]
for i in range(len(items) - 1):
min_index = i
for j in range(i + 1, len(items)):
if comp(items[j], items[min_index]):
min_index = j
items[i], items[min_index] = items[min_index], items[i]
return items
# 函数的设计要尽量做到无副作用(不影响调用者)
# 9 1 2 3 4 5 6 7 8
# 9 2 3 4 5 6 7 8 1
# *前面的参数叫位置参数,传参时只需要对号入座即可
# *后面的参数叫命名关键字参数,传参时必须给出参数名和参数值
# *args - 可变参数 - 元组
# **kwargs - 关键字参数 - 字典
def bubble_sort(origin_items, *, comp=lambda x, y: x > y):
"""冒泡排序"""
items = origin_items[:]
for i in range(1, len(items)):
swapped = False
for j in range(i - 1, len(items) - i):
if comp(items[j], items[j + 1]):
items[j], items[j + 1] = items[j + 1], items[j]
swapped = True
if swapped:
swapped = False
for j in range(len(items) - i - 1, i - 1, -1):
if comp(items[j - 1], items[j]):
items[j], items[j - 1] = items[j - 1], items[j]
swapped = True
if not swapped:
break
return items
def merge_sort(items, comp=lambda x, y: x <= y):
"""归并排序"""
if len(items) < 2:
return items[:]
mid = len(items) // 2
left = merge_sort(items[:mid], comp)
right = merge_sort(items[mid:], comp)
return merge(left, right, comp)
def merge(items1, items2, comp=lambda x, y: x <= y):
"""合并(将两个有序列表合并成一个新的有序列表)"""
items = []
index1, index2 = 0, 0
while index1 < len(items1) and index2 < len(items2):
if comp(items1[index1], items2[index2]):
items.append(items1[index1])
index1 += 1
else:
items.append(items2[index2])
index2 += 1
items += items1[index1:]
items += items2[index2:]
return items
def quick_sort(origin_items, comp=lambda x, y: x <= y):
"""快速排序"""
items = origin_items[:]
_quick_sort(items, 0, len(items) - 1, comp)
return items
def _quick_sort(items, start, end, comp):
"""递归调用划分和排序"""
if start < end:
pos = _partition(items, start, end, comp)
_quick_sort(items, start, pos - 1, comp)
_quick_sort(items, pos + 1, end, comp)
def _partition(items, start, end, comp):
"""划分"""
pivot = items[end]
i = start - 1
for j in range(start, end):
if comp(items[j], pivot):
i += 1
items[i], items[j] = items[j], items[i]
items[i + 1], items[end] = items[end], items[i + 1]
return i + 1
def main():
"""主函数"""
items = [35, 97, 12, 68, 55, 73, 81, 40]
# print(bubble_sort(items))
# print(select_sort(items))
# print(merge_sort(items))
print(quick_sort(items))
items2 = [
Person('Wang', 25), Person('Luo', 39),
Person('Zhang', 50), Person('He', 20)
]
# print(bubble_sort(items2, comp=lambda p1, p2: p1.age > p2.age))
# print(select_sort(items2, comp=lambda p1, p2: p1.name < p2.name))
# print(merge_sort(items2, comp=lambda p1, p2: p1.age <= p2.age))
print(quick_sort(items2, comp=lambda p1, p2: p1.age <= p2.age))
items3 = ['apple', 'orange', 'watermelon', 'durian', 'pear']
# print(bubble_sort(items3))
# print(bubble_sort(items3, comp=lambda x, y: len(x) > len(y)))
# print(merge_sort(items3))
print(merge_sort(items3))
if __name__ == '__main__':
main()
"""
函数递归调用 - 函数直接或者间接的调用了自身
1. 收敛条件
2. 递归公式
n! = n * (n-1)!
f(n) = f(n-1) + f(n-2)
1 1 2 3 5 8 13 21 34 55 ...
"""
from contextlib import contextmanager
from time import perf_counter
def fac(num):
"""求阶乘"""
assert num >= 0
if num in (0, 1):
return 1
return num * fac(num - 1)
def fib2(num):
"""普通函数"""
a, b = 1, 1
for _ in range(num - 1):
a, b = b, a + b
return a
def fib3(num):
"""生成器"""
a, b = 0, 1
for _ in range(num):
a, b = b, a + b
yield a
# 动态规划 - 保存可能进行重复运算的中间结果(空间换时间)
def fib(num, results={}):
"""斐波拉切数"""
assert num > 0
if num in (1, 2):
return 1
try:
return results[num]
except KeyError:
results[num] = fib(num - 1) + fib(num - 2)
return results[num]
@contextmanager
def timer():
try:
start = perf_counter()
yield
finally:
end = perf_counter()
print(f'{end - start}秒')
def main():
"""主函数"""
# for val in fib3(20):
# print(val)
# gen = fib3(20)
# for _ in range(10):
# print(next(gen))
for num in range(1, 121):
with timer():
print(f'{num}: {fib(num)}')
# print(fac(5))
# print(fac(-5))
if __name__ == '__main__':
main()
"""
贪婪法:在对问题求解时,总是做出在当前看来是最好的选择,
不追求最优解,快速找到满意解。
"""
class Thing(object):
"""物品"""
def __init__(self, name, price, weight):
self.name = name
self.price = price
self.weight = weight
@property
def value(self):
"""价格重量比"""
return self.price / self.weight
def input_thing():
"""输入物品信息"""
name_str, price_str, weight_str = input().split()
return name_str, int(price_str), int(weight_str)
def main():
"""主函数"""
max_weight, num_of_things = map(int, input().split())
all_things = []
for _ in range(num_of_things):
all_things.append(Thing(*input_thing()))
all_things.sort(key=lambda x: x.value, reverse=True)
total_weight = 0
total_price = 0
for thing in all_things:
if total_weight + thing.weight <= max_weight:
print(f'小偷拿走了{thing.name}')
total_weight += thing.weight
total_price += thing.price
print(f'总价值: {total_price}美元')
if __name__ == '__main__':
main()
"""
递归回溯法:叫称为试探法,按选优条件向前搜索,当搜索到某一步,
发现原先选择并不优或达不到目标时,就退回一步重新选择。
经典问题:骑士巡逻
"""
import os
import sys
import time
SIZE = 5
total = 0
def print_board(board):
# os.system('clear')
for row in board:
for col in row:
print(str(col).center(4), end='')
print()
def patrol(board, row, col, step=1):
if row >= 0 and row < SIZE and \
col >= 0 and col < SIZE and \
board[row][col] == 0:
board[row][col] = step
if step == SIZE * SIZE:
global total
total += 1
print(f'第{total}种走法: ')
print_board(board)
patrol(board, row - 2, col - 1, step + 1)
patrol(board, row - 1, col - 2, step + 1)
patrol(board, row + 1, col - 2, step + 1)
patrol(board, row + 2, col - 1, step + 1)
patrol(board, row + 2, col + 1, step + 1)
patrol(board, row + 1, col + 2, step + 1)
patrol(board, row - 1, col + 2, step + 1)
patrol(board, row - 2, col + 1, step + 1)
board[row][col] = 0
def main():
board = [[0] * SIZE for _ in range(SIZE)]
patrol(board, SIZE - 1, SIZE - 1)
if __name__ == '__main__':
main()
"""
编码和解码 - BASE64
0-9A-Za-z+/
1100 0101 1001 0011 0111 0110
00110001 00011001 00001101 00110110
base64
b64encode / b64decode
-------------------------------------
序列化和反序列化
序列化 - 将对象变成字节序列(bytes)或者字符序列(str) - 串行化/腌咸菜
反序列化 - 把字节序列或者字符序列还原成对象
Python标准库对序列化的支持:
json - 字符形式的序列化
pickle - 字节形式的序列化
dumps / loads
"""
import base64
import json
import redis
from example02 import Person
class PersonJsonEncoder(json.JSONEncoder):
def default(self, o):
return o.__dict__
def main():
cli = redis.StrictRedis(host='120.77.222.217', port=6379,
password='123123')
data = base64.b64decode(cli.get('guido'))
with open('guido2.jpg', 'wb') as file_stream:
file_stream.write(data)
# with open('guido.jpg', 'rb') as file_stream:
# result = base64.b64encode(file_stream.read())
# cli.set('guido', result)
# persons = [
# Person('骆昊', 39), Person('王大锤', 18),
# Person('白元芳', 25), Person('狄仁杰', 37)
# ]
# persons = json.loads(cli.get('persons'))
# print(persons)
# cli.set('persons', json.dumps(persons, cls=PersonJsonEncoder))
if __name__ == '__main__':
main()
"""
哈希摘要 - 数字签名/指纹 - 单向哈希函数(没有反函数不可逆)
应用领域:
1. 数据库中的用户敏感信息保存成哈希摘要
2. 给数据生成签名验证数据没有被恶意篡改
3. 云存储服务的秒传功能(去重功能)
"""
class StreamHasher():
"""摘要生成器"""
def __init__(self, algorithm='md5', size=4096):
"""初始化方法
@params:
algorithm - 哈希摘要算法
size - 每次读取数据的大小
"""
self.size = size
cls = getattr(__import__('hashlib'), algorithm.lower())
self.hasher = cls()
def digest(self, file_stream):
"""生成十六进制的摘要字符串"""
# data = file_stream.read(self.size)
# while data:
# self.hasher.update(data)
# data = file_stream.read(self.size)
for data in iter(lambda: file_stream.read(self.size), b''):
self.hasher.update(data)
return self.hasher.hexdigest()
def __call__(self, file_stream):
return self.digest(file_stream)
def main():
"""主函数"""
hasher1 = StreamHasher()
hasher2 = StreamHasher('sha1')
hasher3 = StreamHasher('sha256')
with open('Python-3.7.2.tar.xz', 'rb') as file_stream:
print(hasher1.digest(file_stream))
file_stream.seek(0, 0)
print(hasher2.digest(file_stream))
file_stream.seek(0, 0)
print(hasher3(file_stream))
if __name__ == '__main__':
main()
"""
加密和解密
对称加密 - 加密和解密是同一个密钥 - DES / AES
非对称加密 - 加密和解密是不同的密钥 - RSA
pip install pycrypto
"""
import base64
from hashlib import md5
from Crypto.Cipher import AES
from Crypto import Random
from Crypto.PublicKey import RSA
# # AES加密的密钥(长度32个字节)
# key = md5(b'1qaz2wsx').hexdigest()
# # AES加密的初始向量(随机生成)
# iv = Random.new().read(AES.block_size)
def main():
"""主函数"""
# 生成密钥对
key_pair = RSA.generate(1024)
# 导入公钥
pub_key = RSA.importKey(key_pair.publickey().exportKey())
# 导入私钥
pri_key = RSA.importKey(key_pair.exportKey())
message1 = 'hello, world!'
# 加密数据
data = pub_key.encrypt(message1.encode(), None)
# 对加密数据进行BASE64编码
message2 = base64.b64encode(data[0])
print(message2)
# 对加密数据进行BASE64解码
data = base64.b64decode(message2)
# 解密数据
message3 = pri_key.decrypt(data)
print(message3.decode())
# # AES - 对称加密
# str1 = '我爱你们!'
# cipher = AES.new(key, AES.MODE_CFB, iv)
# # 加密
# str2 = cipher.encrypt(str1)
# print(str2)
# # 解密
# cipher = AES.new(key, AES.MODE_CFB, iv)
# str3 = cipher.decrypt(str2)
# print(str3.decode())
if __name__ == '__main__':
main()
"""
装饰器 - 装饰器中放置的通常都是横切关注(cross-concern)功能
所谓横切关注功能就是很多地方都会用到但跟正常业务又逻辑没有必然联系的功能
装饰器实际上是实现了设计模式中的代理模式 - AOP(面向切面编程)
"""
from functools import wraps
from random import randint
from time import time, sleep
import pymysql
def record(output):
def decorate(func):
@wraps(func)
def wrapper(*args, **kwargs):
start = time()
ret_value = func(*args, **kwargs)
output(func.__name__, time() - start)
return ret_value
return wrapper
return decorate
def output_to_console(fname, duration):
print('%s: %.3f秒' % (fname, duration))
def output_to_file(fname, duration):
with open('log.txt', 'a') as file_stream:
file_stream.write('%s: %.3f秒\n' % (fname, duration))
def output_to_db(fname, duration):
con = pymysql.connect(host='localhost', port=3306,
database='test', charset='utf8',
user='root', password='123456',
autocommit=True)
try:
with con.cursor() as cursor:
cursor.execute('insert into tb_record values (default, %s, %s)',
(fname, '%.3f' % duration))
finally:
con.close()
@record(output_to_console)
def random_delay(min, max):
sleep(randint(min, max))
def main():
for _ in range(3):
# print(random_delay.__name__)
random_delay(3, 5)
# for _ in range(3):
# # 取消掉装饰器
# random_delay.__wrapped__(3, 5)
if __name__ == '__main__':
main()
"""
装饰类的装饰器 - 单例模式 - 一个类只能创建出唯一的对象
上下文语法:
__enter__ / __exit__
"""
import threading
from functools import wraps
def singleton(cls):
"""单例装饰器"""
instances = {}
lock = threading.Lock()
@wraps(cls)
def wrapper(*args, **kwargs):
if cls not in instances:
with lock:
if cls not in instances:
instances[cls] = cls(*args, **kwargs)
return instances[cls]
return wrapper
@singleton
class President():
def __init__(self, name, country):
self.name = name
self.country = country
def __str__(self):
return f'{self.country}: {self.name}'
def main():
print(President.__name__)
p1 = President('特朗普', '美国')
p2 = President('奥巴马', '美国')
print(p1 == p2)
print(p1)
print(p2)
if __name__ == '__main__':
main()
"""
变量的作用域以及Python搜索变量的顺序
LEGB: Local --> Embedded --> Global --> Built-in
global - 声明或定义全局变量(要么直接使用现有的全局作用域的变量,要么定义一个变量放到全局作用域)
nonlocal - 声明使用嵌套作用域的变量(如果嵌套作用域没有对应的变量直接报错)
"""
x = 100
def foo():
global x
x = 200
def bar():
x = 300
print(x)
bar()
print(x)
foo()
print(x)
"""
面向对象的三大支柱:封装、继承、多态
面向对象的设计原则:SOLID原则
面向对象的设计模式:GoF设计模式(单例、工厂、代理、策略、迭代器)
月薪结算系统 - 部门经理每月15000 程序员每小时200 销售员1800底薪加销售额5%提成
"""
from abc import ABCMeta, abstractmethod
class Employee(metaclass=ABCMeta):
"""员工(抽象类)"""
def __init__(self, name):
self.name = name
@abstractmethod
def get_salary(self):
"""结算月薪(抽象方法)"""
pass
class Manager(Employee):
"""部门经理"""
def get_salary(self):
return 15000.0
class Programmer(Employee):
"""程序员"""
def __init__(self, name, working_hour=0):
self.working_hour = working_hour
super().__init__(name)
def get_salary(self):
return 200.0 * self.working_hour
class Salesman(Employee):
"""销售员"""
def __init__(self, name, sales=0.0):
self.sales = sales
super().__init__(name)
def get_salary(self):
return 1800.0 + self.sales * 0.05
class EmployeeFactory():
"""创建员工的工厂(工厂模式 - 通过工厂实现对象使用者和对象之间的解耦合)"""
@staticmethod
def create(emp_type, *args, **kwargs):
"""创建员工"""
emp_type = emp_type.upper()
emp = None
if emp_type == 'M':
emp = Manager(*args, **kwargs)
elif emp_type == 'P':
emp = Programmer(*args, **kwargs)
elif emp_type == 'S':
emp = Salesman(*args, **kwargs)
return emp
from example12 import EmployeeFactory
def main():
"""主函数"""
emps = [
EmployeeFactory.create('M', '曹操'),
EmployeeFactory.create('P', '荀彧', 120),
EmployeeFactory.create('P', '郭嘉', 85),
EmployeeFactory.create('S', '典韦', 123000),
]
for emp in emps:
print('%s: %.2f元' % (emp.name, emp.get_salary()))
if __name__ == '__main__':
main()
"""
面向对象
枚举 - 一个变量的值只有有限个选择,最适合的类型就是枚举
通过枚举我们可以定义符号常量,符号常量优于字面常量
"""
from enum import Enum, unique
import random
@unique
class Suite(Enum):
"""花色(枚举)"""
SPADE = 0
HEART = 1
CLUB = 2
DIAMOND = 3
def __lt__(self, other):
return self.value < other.value
class Card():
"""牌"""
def __init__(self, suite, face):
self.suite = suite
self.face = face
def __repr__(self):
return self.__str__()
def __str__(self):
suites = ('♠️', '♥️', '♣️', '♦️')
faces = ('', 'A', '2', '3', '4', '5', '6',
'7', '8', '9', '10', 'J', 'Q', 'K')
return f'{suites[self.suite.value]} {faces[self.face]}'
class Poker():
"""扑克"""
def __init__(self):
self.index = 0
self.cards = [Card(suite, face)
for suite in Suite
for face in range(1, 14)]
def shuffle(self):
"""洗牌"""
self.index = 0
random.shuffle(self.cards)
def deal(self):
"""发牌"""
card = self.cards[self.index]
self.index += 1
return card
@property
def has_more(self):
"""是否有更多的牌"""
return self.index < len(self.cards)
class Player():
"""玩家"""
def __init__(self, name):
self.name = name
self.cards = []
def get_card(self, card):
"""摸牌"""
self.cards.append(card)
def arrange(self):
"""整理手上的牌"""
self.cards.sort(key=lambda card: (card.suite, card.face))
def main():
"""主函数"""
poker = Poker()
poker.shuffle()
players = [
Player('东邪'), Player('西毒'),
Player('南帝'), Player('北丐')
]
while poker.has_more:
for player in players:
player.get_card(poker.deal())
for player in players:
player.arrange()
print(player.name, end=': ')
print(player.cards)
if __name__ == '__main__':
main()
"""
迭代器 - __iter__ / __next__
itertools - 生成可迭代序列的工具模块
"""
import itertools
from math import sqrt
def is_prime(num):
"""判断素数"""
for factor in range(2, int(sqrt(num)) + 1):
if num % factor == 0:
return False
return True
class PrimeIter(object):
"""素数迭代器"""
def __init__(self, min_value, max_value):
assert 2 <= min_value <= max_value
self.min_value = min_value - 1
self.max_value = max_value
def __iter__(self):
return self
def __next__(self):
self.min_value += 1
while self.min_value <= self.max_value:
if is_prime(self.min_value):
return self.min_value
self.min_value += 1
raise StopIteration()
class FibIter(object):
"""斐波那契数迭代器"""
def __init__(self, num):
self.num = num
self.a, self.b = 0, 1
self.idx = 0
def __iter__(self):
return self
def __next__(self):
if self.idx < self.num:
self.a, self.b = self.b, self.a + self.b
self.idx += 1
return self.a
raise StopIteration()
def main():
# for val in itertools.permutations('ABCD'):
# print(val)
# for val in itertools.combinations('ABCDE', 3):
# print(val)
# for val in itertools.product('黑红梅方', range(1, 14)):
# print(val)
# fib_iter = FibIter(20)
# print('===>', next(fib_iter))
# print('===>', next(fib_iter))
# for val in fib_iter:
# print(val)
prime_iter = PrimeIter(2, 100000)
for val in prime_iter:
print(val)
if __name__ == '__main__':
main()
"""
魔术方法
如果要把自定义对象放到set或者用作dict的键
那么必须要重写__hash__和__eq__两个魔术方法
前者用来计算对象的哈希码,后者用来判断两个对象是否相同
哈希码不同的对象一定是不同的对象,但哈希码相同未必是相同的对象(哈希码冲撞)
所以在哈希码相同的时候还要通过__eq__来判定对象是否相同
"""
class Student():
__slots__ = ('stuid', 'name', 'gender')
def __init__(self, stuid, name):
self.stuid = stuid
self.name = name
def __hash__(self):
return hash(self.stuid) + hash(self.name)
def __eq__(self, other):
return self.stuid == other.stuid and \
self.name == other.name
def __str__(self):
return f'{self.stuid}: {self.name}'
def __repr__(self):
return self.__str__()
class School():
def __init__(self, name):
self.name = name
self.students = {}
def __setitem__(self, key, student):
self.students[key] = student
def __getitem__(self, key):
return self.students[key]
def main():
# students = set()
# students.add(Student(1001, '王大锤'))
# students.add(Student(1001, '王大锤'))
# students.add(Student(1001, '白元芳'))
# print(len(students))
# print(students)
stu = Student(1234, '骆昊')
stu.gender = 'Male'
# stu.birth = '1980-11-28'
print(stu.name, stu.birth)
school = School('千锋教育')
school[1001] = Student(1001, '王大锤')
school[1002] = Student(1002, '白元芳')
school[1003] = Student(1003, '白洁')
print(school[1002])
print(school[1003])
if __name__ == '__main__':
main()
"""
多重继承 - 一个类有两个或者两个以上的父类
MRO - 方法解析顺序 - Method Resolution Order
当出现菱形继承(钻石继承)的时候,子类到底继承哪个父类的方法
Python 2.x - 深度优先搜索
Python 3.x - C3算法 - 类似于广度优先搜索
"""
class A():
def say_hello(self):
print('Hello, A')
class B(A):
pass
class C(A):
def say_hello(self):
print('Hello, C')
class D(B, C):
pass
class SetOnceMappingMixin():
"""自定义混入类"""
__slots__ = ()
def __setitem__(self, key, value):
if key in self:
raise KeyError(str(key) + ' already set')
return super().__setitem__(key, value)
class SetOnceDict(SetOnceMappingMixin, dict):
"""自定义字典"""
pass
def main():
print(D.mro())
# print(D.__mro__)
D().say_hello()
print(SetOnceDict.__mro__)
my_dict= SetOnceDict()
my_dict['username'] = 'jackfrued'
my_dict['username'] = 'hellokitty'
if __name__ == '__main__':
main()
"""
元 - meta
元数据 - 描述数据的数据 - metadata
元类 - 描述类的类 - metaclass - 继承自type
"""
import threading
class SingletonMeta(type):
"""自定义元类"""
def __init__(cls, *args, **kwargs):
cls.__instance = None
cls.lock = threading.Lock()
super().__init__(*args, **kwargs)
def __call__(cls, *args, **kwargs):
if cls.__instance is None:
with cls.lock:
if cls.__instance is None:
cls.__instance = super().__call__(*args, **kwargs)
return cls.__instance
class President(metaclass=SingletonMeta):
"""总统(单例类)"""
def __init__(self, name, country):
self.name = name
self.country = country
def __str__(self):
return f'{self.country}: {self.name}'
def main():
"""主函数"""
p1 = President('特朗普', '美国')
p2 = President('奥巴马', '美国')
p3 = President.__call__('克林顿', '美国')
print(p1 == p2)
print(p1 == p3)
print(p1, p2, p3, sep='\n')
if __name__ == '__main__':
main()
"""
扩展性系统性能
- 垂直扩展 - 增加单节点处理能力
- 水平扩展 - 将单节点变成多节点(读写分离/分布式集群)
并发编程 - 加速程序执行 / 改善用户体验
耗时间的任务都尽可能独立的执行,不要阻塞代码的其他部分
- 多线程
1. 创建Thread对象指定target和args属性并通过start方法启动线程
2. 继承Thread类并重写run方法来定义线程执行的任务
3. 创建线程池对象ThreadPoolExecutor并通过submit来提交要执行的任务
第3种方式可以通过Future对象的result方法在将来获得线程的执行结果
也可以通过done方法判定线程是否执行结束
- 多进程
- 异步I/O
"""
import glob
import os
import time
from concurrent.futures import ThreadPoolExecutor
from threading import Thread
from PIL import Image
# class ThumbnailThread(Thread):
# def __init__(self, infile):
# self.infile = infile
# super().__init__()
# def run(self):
# file, ext = os.path.splitext(self.infile)
# filename = file[file.rfind('/') + 1:]
# for size in (32, 64, 128):
# outfile = f'thumbnails/{filename}_{size}_{size}.png'
# image = Image.open(self.infile)
# image.thumbnail((size, size))
# image.save(outfile, format='PNG')
def gen_thumbnail(infile):
file, ext = os.path.splitext(infile)
filename = file[file.rfind('/') + 1:]
for size in (32, 64, 128):
outfile = f'thumbnails/{filename}_{size}_{size}.png'
image = Image.open(infile)
image.thumbnail((size, size))
image.save(outfile, format='PNG')
# def main():
# start = time.time()
# threads = []
# for infile in glob.glob('images/*'):
# # t = Thread(target=gen_thumbnail, args=(infile, ))
# t = ThumbnailThread(infile)
# t.start()
# threads.append(t)
# for t in threads:
# t.join()
# end = time.time()
# print(f'耗时: {end - start}秒')
def main():
pool = ThreadPoolExecutor(max_workers=30)
futures = []
start = time.time()
for infile in glob.glob('images/*'):
# submit方法是非阻塞式的方法
# 即便工作线程数已经用完,submit方法也会接受提交的任务
future = pool.submit(gen_thumbnail, infile)
futures.append(future)
for future in futures:
# result方法是一个阻塞式的方法 如果线程还没有结束
# 暂时取不到线程的执行结果 代码就会在此处阻塞
future.result()
end = time.time()
print(f'耗时: {end - start}秒')
# shutdown也是非阻塞式的方法 但是如果已经提交的任务还没有执行完
# 线程池是不会停止工作的 shutdown之后再提交任务就不会执行而且会产生异常
pool.shutdown()
if __name__ == '__main__':
main()
"""
线程间通信(共享数据)非常简单因为可以共享同一个进程的内存
进程间通信(共享数据)比较麻烦因为操作系统会保护分配给进程的内存
要实现多进程间的通信通常可以用系统管道、套接字、三方服务来实现
multiprocessing.Queue
守护线程 - daemon thread
守护进程 - firewalld / httpd / mysqld
在系统停机的时候不保留的进程 - 不会因为进程还没有执行结束而阻碍系统停止
"""
from threading import Thread
from time import sleep
def output(content):
while True:
print(content, end='')
def main():
Thread(target=output, args=('Ping', ), daemon=True).start()
Thread(target=output, args=('Pong', ), daemon=True).start()
sleep(5)
print('bye!')
if __name__ == '__main__':
main()
"""
多个线程竞争一个资源 - 保护临界资源 - 锁(Lock/RLock)
多个线程竞争多个资源(线程数>资源数) - 信号量(Semaphore)
多个线程的调度 - 暂停线程执行/唤醒等待中的线程 - Condition
"""
from concurrent.futures import ThreadPoolExecutor
from random import randint
from time import sleep
import threading
class Account():
"""银行账户"""
def __init__(self, balance=0):
self.balance = balance
lock = threading.Lock()
self.condition = threading.Condition(lock)
def withdraw(self, money):
"""取钱"""
with self.condition:
while money > self.balance:
self.condition.wait()
new_balance = self.balance - money
sleep(0.001)
self.balance = new_balance
def deposit(self, money):
"""存钱"""
with self.condition:
new_balance = self.balance + money
sleep(0.001)
self.balance = new_balance
self.condition.notify_all()
def add_money(account):
while True:
money = randint(5, 10)
account.deposit(money)
print(threading.current_thread().name,
':', money, '====>', account.balance)
sleep(0.5)
def sub_money(account):
while True:
money = randint(10, 30)
account.withdraw(money)
print(threading.current_thread().name,
':', money, '<====', account.balance)
sleep(1)
def main():
account = Account()
with ThreadPoolExecutor(max_workers=10) as pool:
for _ in range(5):
pool.submit(add_money, account)
pool.submit(sub_money, account)
if __name__ == '__main__':
main()
"""
多进程和进程池的使用
多线程因为GIL的存在不能够发挥CPU的多核特性
对于计算密集型任务应该考虑使用多进程
time python3 example22.py
real 0m11.512s
user 0m39.319s
sys 0m0.169s
"""
import concurrent.futures
import math
PRIMES = [
1116281,
1297337,
104395303,
472882027,
533000389,
817504243,
982451653,
112272535095293,
112582705942171,
112272535095293,
115280095190773,
115797848077099,
1099726899285419
] * 5
def is_prime(n):
"""判断素数"""
if n % 2 == 0:
return False
sqrt_n = int(math.floor(math.sqrt(n)))
for i in range(3, sqrt_n + 1, 2):
if n % i == 0:
return False
return True
def main():
"""主函数"""
with concurrent.futures.ProcessPoolExecutor() as executor:
for number, prime in zip(PRIMES, executor.map(is_prime, PRIMES)):
print('%d is prime: %s' % (number, prime))
if __name__ == '__main__':
main()
"""
协程(coroutine)- 可以在需要时进行切换的相互协作的子程序
"""
import asyncio
from example15 import is_prime
def num_generator(m, n):
"""指定范围的数字生成器"""
yield from range(m, n + 1)
async def prime_filter(m, n):
"""素数过滤器"""
primes = []
for i in num_generator(m, n):
if is_prime(i):
print('Prime =>', i)
primes.append(i)
await asyncio.sleep(0.001)
return tuple(primes)
async def square_mapper(m, n):
"""平方映射器"""
squares = []
for i in num_generator(m, n):
print('Square =>', i * i)
squares.append(i * i)
await asyncio.sleep(0.001)
return squares
def main():
"""主函数"""
loop = asyncio.get_event_loop()
future = asyncio.gather(prime_filter(2, 100), square_mapper(1, 100))
future.add_done_callback(lambda x: print(x.result()))
loop.run_until_complete(future)
loop.close()
if __name__ == '__main__':
main()
"""
aiohttp - 异步HTTP网络访问
异步I/O(异步编程)- 只使用一个线程(单线程)来处理用户请求
用户请求一旦被接纳,剩下的都是I/O操作,通过多路I/O复用也可以达到并发的效果
这种做法与多线程相比可以让CPU利用率更高,因为没有线程切换的开销
Redis/Node.js - 单线程 + 异步I/O
Celery - 将要执行的耗时间的任务异步化处理
异步I/O事件循环 - uvloop
"""
import asyncio
import re
import aiohttp
async def fetch(session, url):
async with session.get(url, ssl=False) as resp:
return await resp.text()
async def main():
pattern = re.compile(r'\<title\>(?P<title>.*)\<\/title\>')
urls = ('https://www.python.org/',
'https://git-scm.com/',
'https://www.jd.com/',
'https://www.taobao.com/',
'https://www.douban.com/')
async with aiohttp.ClientSession() as session:
for url in urls:
html = await fetch(session, url)
print(pattern.search(html).group('title'))
if __name__ == '__main__':
loop = asyncio.get_event_loop()
loop.run_until_complete(main())
loop.close()
"""
装饰器的应用
"""
from functools import wraps
from random import randint
from time import sleep
class Retry():
"""让函数可以重试执行的装饰器"""
def __init__(self, times=3, max_wait=0, errors=(Exception, )):
self.times = times
self.max_wait = max_wait
self.errors = errors
def __call__(self, func):
@wraps(func)
def wrapper(*args, **kwargs):
for _ in range(self.times):
try:
return func(*args, **kwargs)
except self.errors:
sleep(randint(self.max_wait))
return wrapper
def retry(*, times=3, max_wait=0, errors=(Exception, )):
"""让函数重试执行的装饰器函数"""
def decorate(func):
@wraps(func)
def wrapper(*args, **kwargs):
for _ in range(times):
try:
return func(*args, **kwargs)
except errors:
sleep(randint(max_wait))
return wrapper
return decorate
# @Retry(max_wait=5)
@retry(max_wait=5)
def get_data_from_url(url):
pass
"""
模拟面试编程题
"""
def second_max(items: list, gt=lambda x, y: x > y):
"""从列表中找出第二大元素"""
assert len(items) >= 2
max1, max2 = (items[0], items[1]) \
if gt(items[0], items[1]) else (items[1], items[0])
for i in range(2, len(items)):
if gt(max1, items[i]) and gt(items[i], max2):
max2 = items[i]
elif gt(items[i], max1):
max1, max2 = items[i], max1
return max2
def list_depth(items: list) -> int:
"""计算嵌套列表的嵌套深度"""
if isinstance(items, list):
max_depth = 1
for item in items:
max_depth = max(list_depth(item) + 1, max_depth)
return max_depth
return 0
def main():
"""主函数"""
one_set = {1}
pos, off = 1, 1
while pos <= 100000000:
pos += off
one_set.add(pos)
off += 1
num, *poses = map(int, input().split())
for pos in poses:
print(1 if pos in one_set else 0, end=' ')
# items1 = [38, 95, 27, 95, 88, 73, 61, 50]
# print(second_max(items1))
# items2 = [[1], [[[2]]],[[3]], 4, [[[[[5, [6]]]]]]]
# print(list_depth(items1))
# print(list_depth(items2))
if __name__ == '__main__':
main()
"""
单元测试 - 针对程序中最小的功能模块(函数和方法)的测试
测试方法:
- 白盒测试:程序自己写的测试
- 黑盒测试:测试人员或QA,不知道代码实现细节,只关注功能
编写Python单元测试 - 定义类继承TestCase,写测试方法(test_开头)
执行单元测试:
- unittest.main()
- python3 -m unittest test_example01.py
第三方库 - nose2 / pytest
pip install pytest pytest-cov
pytest -v --cov
------------------------------
pip install nose2 cov-core
nose2 -v -C
"""
from unittest import TestCase
from example01 import seq_search, bin_search
class TestExample01(TestCase):
"""测试查找函数的测试用例"""
# 执行每个测试函数之前要执行的方法
def setUp(self):
self.data1 = [35, 97, 12, 68, 55, 73, 81, 40]
self.data2 = [12, 35, 40, 55, 68, 73, 81, 97]
# 执行每个测试函数之后要执行的方法
def tearDown(self):
pass
def test_seq_search(self):
"""测试顺序查找"""
self.assertEqual(0, seq_search(self.data1, 35))
self.assertEqual(2, seq_search(self.data1, 12))
self.assertEqual(6, seq_search(self.data1, 81))
self.assertEqual(7, seq_search(self.data1, 40))
self.assertEqual(-1, seq_search(self.data1, 99))
self.assertEqual(-1, seq_search(self.data1, 7))
def test_bin_search(self):
"""测试二分查找"""
self.assertEqual(1, bin_search(self.data2, 35))
self.assertEqual(0, bin_search(self.data2, 12))
self.assertEqual(6, bin_search(self.data2, 81))
self.assertEqual(2, bin_search(self.data2, 40))
self.assertEqual(7, bin_search(self.data2, 97))
self.assertEqual(-1, bin_search(self.data2, 7))
self.assertEqual(-1, bin_search(self.data2, 99))
from unittest import TestCase
from example02 import select_sort, merge
class TestExample02(TestCase):
"""测试排序函数的测试用例"""
def setUp(self):
self.data1 = [35, 97, 12, 68, 55, 73, 81, 40]
self.items1 = [12, 35, 68, 97]
self.items2 = [40, 55, 73, 81]
def test_merge(self):
items = merge(self.items1, self.items2)
for i in range(len(items) - 1):
self.assertLessEqual(items[i], items[i + 1])
def test_select_sort(self):
"""测试顺序查找"""
items = select_sort(self.data1)
for i in range(len(items) - 1):
self.assertLessEqual(items[i], items[i + 1])
\ No newline at end of file
此差异已折叠。
## 开启团队项目
### 创建项目
我们的项目使用Git作为版本控制工具,首先可以在代码托管平台上创建一个新项目。这里我们使用了国内的[“码云”](https://gitee.com)来创建项目,并通过该平台实现版本控制和缺陷管理,当然如果愿意也可以使用[github](https://github.com/)或者国内的[CODING](https://coding.net/))来做同样的事情,当然也可以自己用诸如[Gitlab](https://gitlab.com)这样的工具来搭建自己的代码仓库。创建好项目之后,我们先为项目添加一个名为`.gitignore`文件,该文件用来忽略掉那些不需要纳入版本控制系统的文件,如果不知道怎么编写该文件,可以使用gitignore.io](https://www.gitignore.io/)网站提供的在线生成工具,如下所示。
![](./res/gitignore_io.png)
### 初始版本
接下来,我们将项目克隆到本地,并为项目创建真正意义上的初始版本。
```Shell
git clone https://gitee.com/jackfrued/fang.com.git
cd fang.com
python3 -m venv venv
source venv/bin/activate
pip install -U pip
pip install django django-celery django-redis djangorestframework pymysql redis pillow
pip freeze > requirements.txt
```
**提示**:在使用pip安装依赖项以后,可以通过`pip check`来检查依赖项是否兼容,确保万无一失。如果希望使用国内的PyPI源,可以按照如下所示的方式进行配置。在用户主目录下找到或创建名为`.pip`的文件夹,并在该文件夹中添加一个`pip.conf`文件,其内容如下所示。
```INI
[global]
trusted-host=mirrors.aliyun.com
index-url=http://mirrors.aliyun.com/pypi/simple/
# index-url=https://pypi.tuna.tsinghua.edu.cn/simple/
# index-url=https://mirrors.ustc.edu.cn/pypi/web/
# index-url=https://pypi.douban.com/simple
```
上面的配置文件中使用了阿里云的PyPI镜像,由于该镜像没有使用HTTPS,所以要先将其设置为受信任的站点,当然也可以直接使用下面提供的其他HTTPS镜像。使用Windows的用户可以在用户主目录下创建`pip`文件夹(注意前面有没有点)并添加该配置文件,文件名为`pip.ini`
接下来创建项目和应用,通常我们会对项目中的功能进行垂直拆分,因此需要创建多个应用来对应不同的功能模块。
```Shell
django-admin startproject fang .
python manage.py startapp common
python manage.py startapp forum
python manage.py startapp rent
```
对项目的公共配置文件进行必要修改后,就可以通过反向工程(根据关系型数据库中的表来生成模型从而减少不必要的重复劳动)完成对模型类的创建,反向工程生成的模型可能需要适当的修改和调整才能应用于项目,尤其是存在多对多关系的时候。
```Shell
python manage.py inspectdb > common/models.py
```
如果需要使用Django的admin项目或者第三方xadmin,还需要执行迁移操作来创建额外的数据库表。
```Shell
python manage.py migrate
```
至此,我们就可以将当前的工作纳入版本控制并同步到服务器。
```Shell
git add .
git commit -m '项目初始版本'
git push
```
### 日常开发
不管是使用“git-flow”还是“github-flow”,都不能够在master上面直接进行开发,对于后者要创建自己的分支并在上面进行开发。
```Shell
git checkout -b jackfrued
git add .
git commit -m '提交的原因'
git push origin jackfrued
git branch -d jackfrued
git checkout master
git pull
git checkout -b jackfrued
```
......@@ -306,7 +306,7 @@
### Day91~100 - [团队项目开发](./Day91-100)
#### 过程模型
#### 第91天:团队开发和项目选题
1. 软件过程模型
- 经典过程模型(瀑布模型)
......@@ -322,7 +322,18 @@
- 修复bug(问题描述、重现步骤、测试人员、被指派人)。
- 评审会议(Showcase)。
- 回顾会议(当前周期做得好和不好的地方)。
> 补充:敏捷软件开发宣言
>
> - **个体和互动** 高于 流程和工具
> - **工作的软件** 高于 详尽的文档
> - **客户合作** 高于 合同谈判
> - **响应变化** 高于 遵循计划
![](./res/the-daily-scrum-in-the-sprint-cycle.png)
2. 项目团队组建
- 团队的构成和角色
![company_architecture](./res/company_architecture.png)
......@@ -332,21 +343,29 @@
![](./res/pylint.png)
- Python中的一些“惯例”(请参考[《Python惯例-如何编写Pythonic的代码》](Python惯例-如何编写Pythonic的代码.md)
- 影响代码可读性的原因:
- 代码注释太少或者没有注释
- 代码破坏了语言的最佳实践
- 反模式编程(意大利面代码、复制-黏贴编程、自负编程、……)
3. 团队开发工具介绍
- 版本控制:Git、Mercury
- 缺陷管理:Github/Gitee、Redmine、禅道
- 持续集成:Jenkins、Travis-CI
- 缺陷管理:[Gitlab](https://about.gitlab.com/)[Redmine](http://www.redmine.org.cn/)
- 敏捷闭环工具:[禅道](https://www.zentao.net/)[JIRA](https://www.atlassian.com/software/jira/features)
- 持续集成:[Jenkins](https://jenkins.io/)[Travis-CI](https://travis-ci.org/)
请参考[《团队项目开发》](团队项目开发.md)
#### 项目选题和理解业务
##### 项目选题和理解业务
1. 选题范围设定
- CMS(用户端):新闻聚合网站、问答/分享社区、影评/书评网站等。
- MIS(用户端+管理端):KMS、KPI考核系统、HRS、仓储管理系统等。
- MIS(用户端+管理端):KMS、KPI考核系统、HRS、CRM系统、供应链系统、仓储管理系统等。
- App后台(管理端+数据接口):二手交易类App、报刊杂志类App、健康健美类App、旅游类App、社交类App、阅读类App等。
- App后台(管理端+数据接口):二手交易类、报刊杂志类、小众电商类、新闻资讯类、旅游类、社交类、阅读类等。
- 其他类型:自身行业背景和工作经验、业务容易理解和把控。
2. 需求理解、模块划分和任务分配
......@@ -366,11 +385,13 @@
| | 查看评论 | 白元芳 | 正在进行 | 20% | 4 | 2018/8/7 | | 2018/8/7 | | 需要进行代码审查 |
| | 评论投票 | 白元芳 | 等待 | 0% | 4 | 2018/8/8 | | 2018/8/8 | | |
#### 概念模型和正向工程
#### 第92天:数据库设计和OOAD
##### 概念模型和正向工程
1. UML和E-R
1. UML(统一建模语言)的类
![uml](./res/uml-graph.png)
![uml](./res/uml-class-diagram.png)
2. 通过模型创建表(正向工程)
......@@ -379,44 +400,53 @@
python manage.py migrate
```
#### 物理模型和反向工程
##### 物理模型和反向工程
1. PowerDesigner
![](./res/power-designer-pdm.png)
2. 通过数据表创建模型
2. 通过数据表创建模型(反向工程)
```Shell
python manage.py inspectdb > app/models.py
```
#### 项目开发中的公共问题
#### 第93-98天:使用Django开发项目
##### 项目开发中的公共问题
1. 数据的配置
2. 缓存的配置
1. 数据库的配置(多库、主从、路由)
2. 缓存的配置(分区缓存、键设置、超时设置、主从复制、故障恢复)
3. 日志的配置
4. Django的使用技巧
5. 好用的Python模块(图像处理、数据加密、三方API)
4. Django的使用技巧(Django-Debug-ToolBar)
5. 好用的Python模块(日期计算、图像处理、数据加密、三方API)
### REST API设计
##### REST API设计
1. RESTful架构
- [理解RESTful架构](http://www.ruanyifeng.com/blog/2011/09/restful.html)
- [RESTful API设计指南](http://www.ruanyifeng.com/blog/2014/05/restful_api.html)
- [RESTful API最佳实践](http://www.ruanyifeng.com/blog/2018/10/restful-api-best-practices.html)
2. API接口文档的撰写([《网络API接口设计》](网络API接口设计.md)
3. Django-REST-Framework的应用
- [RAP2](http://rap2.taobao.org/)
- [Apizza](https://apizza.net/)
3. [django-REST-framework](https://www.django-rest-framework.org/)的应用(具体请参考[《Django知识点概述》](Django知识点概述.md)
#### 项目中的重点难点剖析
##### 项目中的重点难点剖析
1. 使用缓存缓解数据库压力(Redis
2. 使用消息队列缓解服务器压力(Celery + RabbitMQ
1. 使用缓存缓解数据库压力 - Redis(具体请参考[《Django知识点概述》](Django知识点概述.md)
2. 使用消息队列缓解服务器压力 - Celery + RabbitMQ(具体请参考[《Django知识点概述》](Django知识点概述.md)
#### 单元测试
#### 第99-100天:测试和部署
##### 单元测试
1. 测试的种类
2. 编写单元测试(unitest、TestCase
3. 测试覆盖率(Coverage)
2. 编写单元测试(unittest、pytest、nose2、tox、ddt、……
3. 测试覆盖率(coverage)
#### 项目部署
##### 项目部署
1. 部署前的准备工作
- 关键设置(SECRET_KEY / DEBUG / ALLOWED_HOSTS / 缓存 / 数据库)
......@@ -424,25 +454,34 @@
- 日志相关配置
2. Linux常用命令回顾
3. Linux常用服务的安装和配置
4. uWSGI和Nginx的使用
5. 虚拟化容器(Docker)
4. uWSGI/Gunicorn和Nginx的使用
- Gunicorn和uWSGI的比较
- 对于不需要大量定制化的简单应用程序,Gunicorn是一个不错的选择,uWSGI的学习曲线比Gunicorn要陡峭得多,Gunicorn的默认参数就已经能够适应大多数应用程序。
- uWSGI支持异构部署。
- 由于Nginx本身支持uWSGI,在线上一般都将Nginx和uWSGI捆绑在一起部署,而且uWSGI属于功能齐全且高度定制的WSGI中间件。
- 在性能上,Gunicorn和uWSGI其实表现相当。
5. 虚拟化技术(Docker)
#### 性能测试
##### 性能测试
1. AB的使用
2. SQLslap的使用
3. sysbench的使用
#### 自动化测试
##### 自动化测试
1. 使用Shell和Python进行自动化测试
2. 使用Selenium实现自动化测试
- Selenium IDE
- Selenium WebDriver
- Selenium Remote Control
3. 测试工具Robot Framework介绍
#### 项目性能调优
##### 项目性能调优
1. 数据库性能调优
- 软硬件优化
- SQL优化
- 架构优化
2. 代码性能调优
3. 静态文件服务器和CDN加速
3. 云存储和CDN加速
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册