Python学习日记(二)
进度有点慢,还是不能够像在学校里一样把自己管的很好,不多说了
今日总结
一.常用数据结构
上一篇元组之后,还有一些数据结构没有提及,记到这里,字符串就不专门记录了,因为其中多涉及的都是对字符串的操作方法,再遇到对应情况自行查阅Python字符串讲解。
1.元组
列表和元组都是容器型的数据类型,即一个变量可以保存多个数据,而且它们都是按一定顺序组织元素的有序容器。列表是可变数据类型,元组是不可变数据类型,创建之后其中的元素不能在添加或删除,所以列表可以添加元素、删除元素、清空元素、排序反转,但这些操作对元组来说是不成立的。列表和元组都可以支持拼接运算、成员运算、索引运算、切片运算等操作:
# 定义一个三元组
t1 = (35, 12, 98)
# 定义一个四元组
t2 = ('鱼', 45, True, '陕西安康')
# 查看变量的类型
print(type(t1)) # <class 'tuple'>
print(type(t2)) # <class 'tuple'>
# 查看元组中元素的数量
print(len(t1)) # 3
print(len(t2)) # 4
# 索引运算
print(t1[0]) # 35
print(t1[2]) # 98
print(t2[-1]) # 陕西安康
# 切片运算
print(t2[:2]) # ('鱼', 43)
print(t2[::3]) # ('鱼', '陕西安康')
# 循环遍历元组中的元素
for elem in t1:
print(elem)
# 成员运算
print(12 in t1) # True
print(99 in t1) # False
print('Hao' not in t2) # True
# 拼接运算
t3 = t1 + t2
print(t3) # (35, 12, 98, '鱼', 43, True, '陕西安康')
# 比较运算
print(t1 == t3) # False
print(t1 >= t3) # False
print(t1 <= (35, 11, 99)) # False
注意点:如果元组中只有一个元素,需要加上一个逗号,不然就会被认为是改变优先级的圆括号举个例子,('hello', )和(100, )才是一元组,而('hello')和(100)只是字符串和整数。几个元素就是几元组。
打包解包
多个元素赋值给一个变量的时候,会自动打包为一个元组。把一个元组赋值给多个变量的时候,元组会自动解包然后赋值给对应的变量,解包元素个数和变量不能对应的时候会报错,太少或者太多都会报错,示例如下:
# 打包操作
a = 1, 10, 100
print(type(a)) # <class 'tuple'>
print(a) # (1, 10, 100)
# 解包操作
i, j, k = a
print(i, j, k) # 1 10 100
#解包元素不足或过多会报错
a = 1, 10, 100, 1000
# i, j, k = a # ValueError: too many values to unpack (expected 3)
# i, j, k, l, m, n = a # ValueError: not enough values to unpack (expected 6, got 4)
列表生成式补充
上一篇这一部分没太用心直接用的原文档中的内容,这里在细致的记录一下,因为确实方便:
普通for循环:
#普通情况
result = []
for x in range(5):
result.append(x * x) # 核心逻辑在这里
#带筛选功能
result = []
for x in range(10):
if x % 2 == 0: # 筛选条件
result.append(x * x)
#带if...else
列表生成式写法:
#普通情况
# [ 要在这个列表里放什么 循环逻辑 ]
result = [ x * x for x in range(5) ]
#带筛选功能
# [ 结果 循环逻辑 筛选条件 ]
result = [ x * x for x in range(10) if x % 2 == 0 ]
口诀:
[ 我想要的结果 for 变量 in 可迭代对象 ]
需要用到if...else的情况就把判断语句(三元运算符)放在for之前就ok了,理解了之后还是挺简单的,演示如下:
# 任务:把数字转成单词
#普通写法
result = []
for x in range(3):
if x % 2 == 0:
result.append("Even")
else:
result.append("Odd")
# 生成式写法
# [ 三元表达式(结果) 循环逻辑 ]
result = [ "Even" if x % 2 == 0 else "Odd" for x in range(3) ]
2.集合
集合具有三个特点:
- 无序性:一个集合中,每个元素的地位都是相同的,元素之间是无序的。
- 互异性:一个集合中,任何两个元素都是不相同的,即元素在集合中只能出现一次。
- 确定性:给定一个集合和一个任意元素,该元素要么属这个集合,要么不属于这个集合,二者必居其一,不允许有模棱两可的情况出现。
set1 = {1, 2, 3, 3, 3, 2}
print(set1)
set2 = {'banana', 'pitaya', 'apple', 'apple', 'banana', 'grape'}
print(set2)
set3 = set('hello')
print(set3)
set4 = set([1, 2, 2, 3, 3, 3, 2, 1])
print(set4)
set5 = {num for num in range(1, 20) if num % 3 == 0 or num % 7 == 0}
print(set5)
Python 中的集合类型是一种无序容器,不允许有重复运算,由于底层使用了哈希存储,集合中的元素必须是hashable类型。集合与列表最大的区别在于集合中的元素没有顺序、所以不能够通过索引运算访问元素、但是集合可以执行交集、并集、差集等二元运算,也可以通过关系运算符检查两个集合是否存在超集、子集等关系。
在这里引出一个小知识,在我学习文档的时候,提示说集合底层使用了哈希存储,作者提醒不是相关专业的可以放放,但是计算机相关专业不懂这个概念难以原谅,看的我是羞愧难当,因为我在之前也是些微的了解,忘的也差不多了,唉,然后我手头上刚好在图书馆借了Hello算法的书,所以就复习了一下,在这里做个总结。
哈希表
哈希表又称散列表,通过建立key和value之间的映射实现高效的元素查询,在遇到元素比较多的情况下,可以在O(1)的时间内获取对应的value,本质上是一种空间换时间的数据结构。
实现原理(简化)
输入一个key,通过哈希哈数得到该key对应的兼职对在数组中的存储位置,可分为如下两步:
- 通过哈希算法将key转化为一个哈希值
- 通过这个哈希值对桶(假设一个简单的哈希表由数组构成,数组中的空位可以称为桶,一个桶可以存放一个键值对.)的数量(数组长度)取模,从而得到
key对应的索引index
index = hash(key) % capacity(容量)
哈希冲突与扩容
哈希表中映射关系是把key构成的输入空间映射到数组所有索引构成的输出空间,而输入空间往往大于输出空间,所以会纯在多个输入对应相同输出结果的情况,就是两个要放在一个哈希表中的数据,最后计算的index结果相同,减少哈希冲突的方法就是通过扩容哈希表。
哈希表结构改良方法
开放寻址和链式地址,后者是java使用的优化方法,前者是python的优化方法,简单的说一下。
链式地址就是数组加链表的形式进行数据存储,当遇到哈希冲突的情况,就把数据加到对应数组索引链表下,查询数据就是先查数组,没有就再查对应链表,java在链表达到一定长度时还引入了红黑树,来优化结构。
开放寻址,分为三种方法,线性探测,平方探测,多次哈希,简单的说一下前两个,当插入元素遇到哈希冲突时,从冲突位置向后线性遍历,步长通常为1,直到找到空桶,放入元素,查询也是,遇到空桶就说明元素不在哈希表中返回None,需要注意的是,使用开放寻址结构的哈希不能随意删除元素,删除会让数组中产生一个空桶,查询元素时,访问到空桶就会返回,这个空桶之后的元素就会变得无法访问,解决之个问题要使用懒删除,即删除元素使用一个常量标记这个桶,遇到这个常量的时候不返回继续向下寻找,直到找到对应元素,当删除元素多之后会拖慢搜索时间,为了优化当遇到常量标记的桶时,记录其索引,在找到正确的元素之后调换二者位置,让正常的桶更靠近探测起始点。
3.字典
Python 程序中的字典跟现实生活中字典非常像,允许我们以键值对的形式保存数据,再通过键访问对应的值。字典是一种非常有利于数据检索的数据类型,但是需要再次提醒大家,字典中的键必须是不可变类型,列表、集合、字典等类型的数据都不能作为字典的键。
xinhua = {
'麓': '山脚下',
'路': '道,往来通行的地方;方面,地区:南~货,外~货;种类:他俩是一~人',
'蕗': '甘草的别名',
'潞': '潞水,水名,即今山西省的浊漳河;潞江,水名,即云南省的怒江'
}
print(xinhua)
person = {
'name': '王大锤',
'age': 55,
'height': 168,
'weight': 60,
'addr': '成都市武侯区科华北路62号1栋101',
'tel': '13122334455',
'emergence contact': '13800998877'
}
print(person)
需要注意的就是字典的键必须是不可变类型,因为可变类型因为内容的改变最后的哈希值也会变,集合也是储存在其中的内容只能为不可变类型,其底层也是哈希表实现的嘛,另外就是再给python字典类型赋值的时候如果字点中没有这个键值对数据python就会自动创建这个键值对存入到其中,其他的就不多赘述了。
4.函数
Pyrhon函数的定义可以通过下图来了解:

函数的参数
位置参数:在调用函数按照顺序从左到右依次传入参数
def make_judgement(a, b, c):
"""判断三条边的长度能否构成三角形"""
return a + b > c and b + c > a and a + c > b
print(make_judgement(1, 2, 3)) # False
print(make_judgement(4, 5, 6)) # True
关键字参数:通过参数明 = 参数值的形式传入参数
print(make_judgement(b=2, c=3, a=1)) # False
print(make_judgement(c=6, b=4, a=5)) # True
可变参数:Python 语言中可以通过星号表达式语法让函数支持可变参数。所谓可变参数指的是在调用函数时,可以向函数传入0个或任意多个参数,双星号代表通过“参数名=参数值”的形式传入若干个参数。
# 参数列表中的**kwargs可以接收0个或任意多个关键字参数
# 调用函数时传入的关键字参数会组装成一个字典(参数名是字典中的键,参数值是字典中的值)
# 如果一个关键字参数都没有传入,那么kwargs会是一个空字典
def foo(*args, **kwargs):
print(args)
print(kwargs)
foo(3, 2.1, True, name='骆昊', age=43, gpa=4.95)
函数进阶
函数传参可以传方法,实现解耦和,逻辑如下:
#原本形式
def calc(*args, **kwargs):
items = list(args) + list(kwargs.values())
result = 0
for item in items:
if type(item) in (int, float):
result += item
return result
#进阶
def calc(init_value, op_func, *args, **kwargs):
items = list(args) + list(kwargs.values())
result = init_value
for item in items:
if type(item) in (int, float):
result = op_func(result, item)
return result
def add(x, y):
return x + y
def mul(x, y):
return x * y
#如果要做求和的运算,我们可以按照下面的方式调用calc函数。
print(calc(0, add, 1, 2, 3, 4, 5)) # 15
#如果要做求乘积运算,我们可以按照下面的方式调用calc函数。
print(calc(1, mul, 1, 2, 3, 4, 5)) # 120
lambda函数
在使用高阶函数的时候,如果作为参数或者返回值的函数本身非常简单,一行代码就能够完成,也不需要考虑对函数的复用,那么我们可以使用 lambda 函数。Python 中的 lambda 函数是没有的名字函数,所以很多人也把它叫做匿名函数,lambda 函数只能有一行代码,代码中的表达式产生的运算结果就是这个匿名函数的返回值。之前的代码中,我们写的is_even和square函数都只有一行代码,我们可以考虑用 lambda 函数来替换掉它们,代码如下所示。
old_nums = [35, 12, 8, 99, 60, 52]
new_nums = list(map(lambda x: x ** 2, filter(lambda x: x % 2 == 0, old_nums)))
print(new_nums) # [144, 64, 3600, 2704]
通过上面的代码可以看出,定义 lambda 函数的关键字是lambda,后面跟函数的参数,如果有多个参数用逗号进行分隔;冒号后面的部分就是函数的执行体,通常是一个表达式,表达式的运算结果就是 lambda 函数的返回值,不需要写return 关键字。
前面我们说过,Python 中的函数是“一等函数”,函数是可以直接赋值给变量的。在学习了 lambda 函数之后,前面我们写过的一些函数就可以用一行代码来实现它们了,大家可以看看能否理解下面的求阶乘和判断素数的函数。
import functools
import operator
# 用一行代码实现计算阶乘的函数
fac = lambda n: functools.reduce(operator.mul, range(2, n + 1), 1)
# 用一行代码实现判断素数的函数
is_prime = lambda x: all(map(lambda f: x % f, range(2, int(x ** 0.5) + 1)))
# 调用Lambda函数
print(fac(6)) # 720
print(is_prime(37)) # True
提示1:上面使用的reduce函数是 Python 标准库functools模块中的函数,它可以实现对一组数据的归约操作,类似于我们之前定义的calc函数,第一个参数是代表运算的函数,第二个参数是运算的数据,第三个参数是运算的初始值。很显然,reduce函数也是高阶函数,它和filter函数、map函数一起构成了处理数据中非常关键的三个动作:过滤、映射和归约。
提示2:上面判断素数的 lambda 函数通过range函数构造了从 2 到 x 的范围,检查这个范围有没有x的因子。all函数也是 Python 内置函数,如果传入的序列中所有的布尔值都是True,all函数返回True,否则all函数返回False。
5.装饰器
这个是Python的特色语法,说是这么说但是在java中使用Springboot框架,里面的Aop切面编程功能和这个装饰器的功能基本一致,非常相像,所以我这里不过多赘述可以直接访问地址仔细了解函数高级应用-装饰器和递归