一份价值500个达不溜的大礼
双色球投注规则:
- 投注区分为红色球号码区和蓝色球号码区
- 红色球号码区由1-33共三十三个号码组成
- 蓝色球号码区由1-16共十六个号码组成
- 投注号码组成:6个红色球号码 + 1个蓝色球号码
通过这个小程序,了解Python的简洁之美
版本一:
import random
def getWealth():
balls = []
n = 6
while n != 0:
ball = random.randint(1, 33)
length = len(balls)
i = 0
isExist = False
while i < length:
b = balls[i]
if b == ball:
isExist = True
n += 1
break
i += 1
if not isExist:
balls.append(ball)
n -= 1
balls.sort()
ball = random.randint(1, 16)
balls.append(ball)
print(balls)
if __name__ == '__main__':
getWealth()
简化版
import random
def getWealth():
balls = []
n = 6
while n != 0:
ball = random.randint(1, 33)
if ball not in balls:
balls.append(ball)
n -= 1
balls.sort()
balls.append(random.randint(1, 16))
print(balls)
if __name__ == '__main__':
getWealth()
Python 常用知识原理剖析
一、for-in循环与迭代器
思考:
为什么 Python 中的字符串,元组,列表,字典等类型的数据,可以直接使用 for-in
直接进行遍历?
自定义的数据集合是否可以实现呢?
iter() 函数
在使用 for-in
循环对这些类型进行遍历时,for-in
会对被遍历的对象执行 iter()
函数,将被遍历的对象包装成一个迭代器对象。
datas = [1,2,3,4,5]
print(type(datas)) # <class 'list'>
iter_data = iter(datas)
print(type(iter_data)) # <class 'list_iterator'>
next() 函数
此时,for-in
循环会在内部调用 next()
函数,逐一取出迭代器中的元素数据,当取出最后一个元素后,再执行 next()
,会抛出 StopIteration
异常停止循环。
在一个对象没有调用 iter()
函数之前,是不能使用 next()
函数的,否则会报类型错误 。
datas = [1,2,3,4,5]
print(type(datas))
# next(datas) # TypeError: 'list' object is not an iterator
iter_data = iter(datas)
print(type(iter_data))
print(next(iter_data))
print(next(iter_data))
print(next(iter_data))
print(next(iter_data))
print(next(iter_data))
print(next(iter_data)) # StopIteration
for-in
就是通过捕捉 StopIteration
这个异常,判断何时退出循环。
自定义可迭代类型
通过前面的代码可知,只有可迭代的对象才可以使用 for-in
,那什么样的数据才算是可迭代的呢?
根据官方文档描述:
迭代器协议的幕后机制,给你的类添加迭代器行为非常容易。
在类中定义一个 iter() 方法来返回一个带有 next() 方法的对象。
如果当前类中已定义了 next(),则 iter() 可以简单地返回 self:
也就是说:
- 当在使用
iter()
函数时,实际是调用了类中实现的__iter__()
魔法方法,只要实现了该方法,就可以将对象放入for-in
中使用。 - 实现
__iter__()
函数,只是获取了使用for-in
的资格,如果真正能够遍历 ,还要实现另外一个方法 -
next()
函数在执行时,实际上通过可迭代对象实现的__next__()
函数来获取数据。
实战:实现一个可迭代类,可以逆序遍历各种容器类型中的元素
实现过程分析:
- 该类可以使用
for-in
循环进行逆序遍历,那么就要实现iter()
和next()
方法 - 字符串,元组,列表因为可以使用下标,可以直接进行遍历,但是字典不运行索引值形式的下标,需要特殊处理。
- 逆序遍历,记录元素个数做为实始索引,递减取值,如果个数为 0,则抛出
StopIteration
异常。 - 如果是字典类型,需要组合键值对返回数据,其它类型直接返回即可。
代码:
class Reverse:
"""通过实现迭代器,使用for-in逆序遍历数据"""
def __init__(self, data):
# 初始保存被遍历的数据
self.data_dict = None
self.data = None
self.index = -1
# 如果是字典类型,特殊处理
if type(data) == dict:
# 保存字典原数据
self.data_dict = data
# 默认的key不能使用下标,转换成列表
self.data = list(data.keys())
else:
# 不是字典,直接保存数据
self.data = data
# 下标初值为最大元素个数
self.index = len(self.data)
# 返回当前迭代器对象
def __iter__(self):
return self
# 实现迭代器对象取数据操作
def __next__(self):
# 如果下标为0,抛出异常
if self.index == 0:
raise StopIteration
# 下标递减
self.index = self.index - 1
# 通过下标逆序找数据
data = self.data[self.index]
# 判断,如果字典不为空,说明遍历的是字典,返回字典元素数据
if self.data_dict:
return (data,self.data_dict[data])
# 不是字典,直接返回
return data
obj = Reverse({"a":1,"b":2,"c":3})
# obj = Reverse([1,2,3,4,5])
# obj = Reverse((1,2,3,4,5,5,5))
# obj = Reverse("abcdef")
for c in obj:
print(c)
# print(next(obj))
# print(next(obj))
# print(next(obj))
# print(next(obj))
# print(next(obj))
# print(next(obj))
# print(next(obj))
二、sorted排序方法实现原理与lambda应用
在 Python 中,提供了两个排序的方法,一个是列表的实例方法 sort()
, 另一个是公共方法 sorted()
。
不同点:
- sort() 方法,会直接在原列表中排序。
- sorted 方法会返回一个排序后的新列表。
相同点:
- 两个方法只能对基本类型的元素进行排序,比如数字,字符串。
- 如果是复杂类型元素,比如字典,自定义类对象,默认无法进行排序。
思考:
- 复杂类型元素的数据如何排序?
函数引用
在讲解正式内容前,需要先掌握一些前置知识。
在 Python 中,函数名表示的是运行过程中函数在内存中的地址,这个地址,称为函数引用。
可以将这个地址赋值给其它变量,从而代替函数名来调用函数。
def show():
print("Show Run ...")
show()
func = show
func()
一般情况下,函数名赋值给其它变量会在将函数名作为参数进行传参,和实现装饰器装饰过程的时候使用。
def show():
print("Show Run ...")
def display():
print("Display Run ...")
def call(func):
print("Call 函数接收一个函数引用,并调用")
func()
call(show)
call(display)
Lambda
在大多数情况下,使用函数做为参数时,函数都只在参数传递时使用一次,而在其它位置不会再使用,这样就没有必要单独定义一个函数。
此时,就可以使用 Lambda 定义匿名函数来做为参数使用。
Lambda 可以用来定义简单的函数表达式,不能使用 if, while, for-in,return 等关键字。
def call(func,n):
print("Call 函数接收一个函数引用,并调用")
s = func(n)
print(s)
a = lambda : "Hello"
print(a())
call(lambda x: f"匿名函数做为函数参数{x}","100")
sort() 与 sorted() 方法解析
sort()方法格式:
sort(key=None, reverse=False)
sorted()方法格式:
sorted(obj, key=None, reverse=False)
参数解析:
-
key
: 接收一个排序规则函数 -
reverse
: 是否排序后逆序,默认是False。 -
obj
: 由于sorted
方法是公共方法,obj
参数用来接收排序对象。
通过 key
参数传入一个排序规则的函数,这样就可以对复杂结构的数据进行排序了。
students = [
{'name': 'Alice', 'id': '1001', 'class': 'Class1'},
{'name': 'Eve', 'id': '1005', 'class': 'Class2'},
{'name': 'Charlie', 'id': '1003', 'class': 'Class1'},
{'name': 'David', 'id': '1004', 'class': 'Class2'},
{'name': 'Bob', 'id': '1002', 'class': 'Class1'},
{'name': 'Frank', 'id': '1006', 'class': 'Class2'}
]
# students.sort(key=lambda item: item["name"])
students = sorted(students,key=lambda item: item["name"])
for item in students:
print(item)
实战: Sorted函数实现原理
实现过程分析:
- 模拟
sorted()
函数,实现公共排序函数,接收 排序对象,排序规则,是否逆序 三个参数 - 函数需要返回一个排序后的新列表,不影响原列表的数据
- 根据实现排序方式不同,可以选择创建新空列表,也可以使用深拷贝获得新列表
- 如果
key
参数没有传参,则使用默认排序,如果传参,则使用参数规则进行排序 - 这里选择使用空列表方式排序,根据条件如果找到位置则将元素插入到该位置,如果没找到合适的位置 ,就将元素追加到最后。
- 如果传入
reverse
参数,则根据参数值返回结果。
课后可以自行完成另一种深拷贝方式实现 sorted
排序。
代码:
students = [
{'name': 'Alice', 'id': '1001', 'class': 'Class1'},
{'name': 'Eve', 'id': '1005', 'class': 'Class2'},
{'name': 'Charlie', 'id': '1003', 'class': 'Class1'},
{'name': 'David', 'id': '1004', 'class': 'Class2'},
{'name': 'Bob', 'id': '1002', 'class': 'Class1'},
{'name': 'Frank', 'id': '1006', 'class': 'Class2'}
]
def mySorted(obj, key=None, reverse=False):
# 将参数转换成列表
obj = list(obj)
print(obj)
# 用来保存新数据的列表
newList = []
# 遍历原数据中的元素
for s in obj:
# 遍历新数据中的元素
for n in newList:
# 如果有规则使用规则排序
if key:
# 使用规则比较原数据元素和新数据中的每个元素
if(key(s) > key(n)):
# 如果满足条件,则在新数据中拿到下标
idx = newList.index(n)
# 将原数据中的元素插入到新数据的下标中
newList.insert(idx, s)
# 进入下一轮循环
break
else:
# 无排序规则时比较方式
if (s > n):
idx = newList.index(n)
newList.insert(idx, s)
break
# 如果比较一轮没有符合的位置,直接将元素添加到新数据的最后
else:
newList.append(s)
# 根据 reverse 参数返回结果
return newList if reverse else newList[::-1]
if __name__ == '__main__':
students = mySorted(students, key=lambda s: s["name"])
# students.sort(key=lambda s: s["name"])
# students = [1,4,2,6,7,8,4,3,3]
# students = mySorted(students, reverse=True)
print(students)
# for s in students:
# print(s)
# print(mySorted({"name": "tom", "age": 12, "gender": "male"}))
# print(mySorted("hdollo"))
其它应用场景
与 Sorted 原理类似的,还有在大数据应用非常多的三个函数 map
, reduce
, filter
, 在使用时也需要像 Sorted 函数一样,能用 lambda
传入操作规则进行使用
-
map
将传入的数据序列以指定的规则映射成新序列upperString = map(lambda s: s.upper(), ["hello", "world", "pytHon"]) for s in upperString: print(s)
-
reduce
将传入的数据序列中的数据根据指定的规则进行累计print(reduce(lambda x, y: x * y, [1, 2, 3, 4, 5])) # 累乘 print(reduce(lambda x, y: x if x > y else y, [5, 1, 2, 3, 4])) # 找最大值 print(reduce(lambda x,y: x * 10 + y, [3, 5, 8, 1])) # 数字组合
-
filter
将传入的数据序列中的数据根据指定的规则进行过滤。students = [{"Name": "Tom", "age": 20}, {"Name": "Tom", "age": 10}, {"Name": "Tom", "age": 22}, {"Name": "Tom", "age": 8}] new_stu = filter(lambda x: x["age"] >= 18, students) for s in new_stu: print(s)
三、装饰器与数据驱动原理实现
装饰器是 Python 中非常重要的语法,可以通过装饰器,在不修改函数定义和调用的前提下,简洁高效的为函数扩展额外的功能。
装饰器语法:@装饰器名
系统中有很多预制装饰器,比如:@staticmethod
, @classmethod
等。
如果想要实现自定义装饰器,要配合闭包一起实现。
闭包
闭包是一种函数的特殊定义形式,定义闭包需要满足以下几个条件:
- 在函数内部嵌套定义另外一个函数
- 内函数使用外函数中定义的变量
- 外函数返回内函数的引用
正常情况下,一旦函数执行结束,那么函数内部定义局部变量就会随着函数的执行结束而销毁。
而闭包是由内函数和外函数局部变量构成的一个环境。
因为内函数中使用了外函数的局部变量,所以此时外函数的局部变量并不会销毁,而是被保存到了闭包环境中。
当使用内函数时,就可以通过内函数间接使用外函数的局部变量。
def outer():
data = "外函数变量的数据"
def inner():
print("内函数执行,并使用了外函数中的局部变量")
print(data)
return inner
func = outer()
func()
装饰器
利用闭包的这个特性,就可以自定义装饰器使用,对其它函数进行功能的扩展。
import time
def count_time(func):
def inner():
start_time = time.time()
func()
stop_time = time.time()
print(f"函数共执行了{stop_time - start_time}秒")
return inner
@count_time
def show():
for i in range(1,4):
print(f"第 {i} 次循环输出")
time.sleep(1)
show()
通用装饰器
前面定义的装饰器过于简陋,只为理解自定义装饰器如何使用。
理论上一个装饰器可以对任何函数进行装饰,但前面的代码却无法实现。
这是因为函数在定义时,由于参数不同,返回值不同,处理也不尽相同。
为了能让装饰器适配于所有的函数,就需要对实现装饰器的闭包进行重新定义
import time
def count_time(func):
def inner(*args, **kwargs):
start_time = time.time()
result = func(*args, **kwargs)
stop_time = time.time()
print(f"函数共执行了{stop_time - start_time}秒")
return result
return inner
装饰器的原理
装饰器的本质就是函数的调用,而装饰器的语法形式是 Python 提供的一种语法糖。
装饰器的装饰过程就是将被装饰函数的引用做为参数,传递到闭包的外函数中,然后返回内函数的引用重新赋值给被装饰函数。
此过程是 Python 解释器在解释代码时,自动传入的,不需要手动实现。
一旦装饰器的装饰过程执行完毕,那么被装饰函数的引用就发生了变化,保存的是闭包的内函数引用。
当调用被装饰函数时,实际调用的是闭包的内函数,再由内函数间接调用被装饰函数。
import time
def count_time(func):
def inner(*args, **kwargs):
start_time = time.time()
result = func(*args, **kwargs)
stop_time = time.time()
print(f"函数共执行了{stop_time - start_time}秒")
return result
return inner
def show():
for i in range(1,4):
print(f"第 {i} 次循环输出")
time.sleep(1)
# 装饰过程
show= count_time(show)
show()
该装饰过程只为帮助理解装饰器如何工作,实际使用时不需要这样操作。
带参数的装饰器
在使用装饰器的时候还可以给装饰器携带参数,这种情况在 Web 框架的路由,Pytest 框架的数据驱动都非常常见。
语法格式:@装饰器名(参数列表...)
可以问题随之而来,传递参数容易,接收参数难,装饰器的函数是由解释器自动传入的,额外的参数该如何接收呢?
解决办法也很简单,将实现装饰器的闭包外再定义一层函数,形成三层函数嵌套,使用最外层函数来接收装饰器的参数。
import time
def wrapper(*args,**kwargs):
def count_time(func):
print("接收的装饰器参数为:", *args,**kwargs)
def inner(*args, **kwargs):
start_time = time.time()
result = func(*args, **kwargs)
stop_time = time.time()
print(f"函数共执行了{stop_time - start_time}秒")
return result
return inner
return count_time
@wrapper(1,2,3,4)
def show():
for i in range(1,4):
print(f"第 {i} 次循环输出")
time.sleep(1)
show()
带参装饰器装饰过程
实际上,带参的装饰器装饰过程也非常简单,万变不离其宗。只用最简单的 Python 语法就可以解释这个过程。
装饰过程步骤如下:
-
@wrapper(1,2,3,4)
可拆分为两个运算符@
和()
,一个标识符为wrapper
- 根据运算符运算规则,括号具有最高优先级,结合后变为
wrapper(1,2,3,4)
- 此时,
wrapper(1,2,3,4)
就变成了普通的函数调用,得到函数执行结果中层函数的引用count_time
- 函数执行结果再与
@
结合,就回到了最初的装饰器的形式@count_time
- 执行
show = count_time(show)
后,完成完整装饰过程
实战:带参数装饰实现数据驱动
在使用 Pytest 测试框架进行测试时,可以使用不同的数据集,对同一个测试用例进行测试。
import pytest
@pytest.mark.parametrize("a,b,c",[(1,2,3),(4,5,9),(7,8,9)])
def test_case(a,b,c):
assert a + b == c
数据驱动的方式就是使用带参数装饰器来完成的。
装饰器的参数有两个:
- 参数一:使用字符串形式表示的参数列表,参数列表中的参数名要和用例中的参数名保持一致,并使用逗号分隔
- 参数二:使用列表或其它可迭代对象保存数据集,数据集中的每个元素是用例执行的一组数据。
实现过程分析:
- 实现装饰器使用的三层闭包函数,外层函数用来接收装饰器的参数
- 在中间层函数中,处理外层函数接收的参数,将参数和数据组合成字典列表(难点)
- 在中间层函数中,遍历组合好的字典列表数据,将数据做为参数调用内层函数
代码:
# 接收装饰器参数的函数
# 参数一:以字符串形式接收被装饰函数的参数列表,需要与被装饰函数参数名保持一致,例:"a,b,c"
# 参数二:以[(),(),()] 形式传入驱动数据。
def decorator_args(vars, datas):
def decorator(func):
# 将字符串参数分割备用
v_keys = vars.split(",")
# 定义保存 [{},{},{}] 形式的数据
new_datas = []
# 遍历数据,取出一组元素数据
for item in datas:
# 定义一个新字典,用来保存 变量名与传入数据组成的字典
d_item = {}
# 使用 zip 函数,同时遍历两个元组,变量名做为key, 元素数据做为value
for k, v in zip(v_keys, item):
# 将 变量名和值对应保存到字典中
d_item[k] = v
# 将组合好的字典追加到新数据中备用
new_datas.append(d_item)
def inner(*args, **kwargs):
return func(*args, **kwargs)
# 遍历新数据,取出元素字典
for item in new_datas:
# 将字典中的数据解包传给内函数
inner(**item)
return inner
return decorator
# 数据驱动数据
data = [(1,2,3),(4,5,6),(7,8,9)]
# 装饰器传参
@decorator_args("a,b,c", data)
def show(a,b,c):
print("接收的数据:", a,b,c)
课后调查表: