python核心技术与实战:基础篇
基础篇
01 如何逐步突破,成为python高手?
- 不同语言,需融汇贯通
- 唯一语言,可循序渐进
- 勤加练习
- 代码规范
- 开发经验
02 JupyterNotebook为什么是现代python的必学技术?
JupyterNotebook,几乎支持所有语言,能够把软件代码、计算输出、解释文档、多媒体资源整合在一起的多功能科学运算平台。
-
JupyterNotebook 优点
-
整合所有的资源
-
交互性编程体验
-
零成功重现结果
JupyterNote云端化:使用binder打开JupyterNotebook文件时,不需要安装任何软件,直接在浏览器打开一份代码,就能在云端上运行。平台有Jupyter 官方的 Binder 平台、 Google 提供的 Google Colab 环境。
-
-
JupyterNotebook学习资源
-
Jupyter 官方:
-
Google Research 提供的 Colab 环境,尤其适合机器学习的实践应用
https://colab.research.google.com/notebooks/basic_features_overview.ipynb
-
-
安装、运行JupyterNotebook
-
在本地或者远程的机器上安装Jupyter Notebook
-
运行JupyterNotebook
https://jupyter.readthedocs.io/en/latest/running.html#running
-
03 列表和元组,到底用哪一个?
列表 | 元组 |
---|---|
一个可以放置任意数据类型的有序集合 | 同列表 |
支持负数索引: -1表示最后一个元素,-2表示倒数第二个元素... |
同列表 |
支持切片操作:[start:end:step ] ,end不包含 |
同列表 |
可以随意嵌套 | 同列表 |
可以转为tuple:tuple(list) |
可以转为list:list(tuple) |
动态可变mutable: 长度大小不固定,可增、删、改元素 |
静态不可变immutable: 长度大小固定,不能增、删、改元素 |
存储方式: 列表是动态的,需要存储指针,来指向对应的元素; 列表可变,还需要额外存储已经分配的长度大小,以实时追踪列表空间的使用情况,当空间不足时,即时分配额外空间。 |
存储方式: 元组不可变,存储空间固定。 |
常用内置函数:list.count(item) 统计列表中item出现的次数list.index(item) 返回列表中item第一次出现的索引,item not in list的时候会报错list.reverse() 等同于list[:-1] 逆序列表list.sort() 列表排序,默认是从小到大(reverse=False )reversed(list) 返回一个倒转后的迭代器,使用list() 函数可以将其转换为列表sorted(list) 返回排好序的新列表,默认是从小到大 |
常用内置函数:tuple.count(item) 统计列表中item出现的次数tuple.index(item) 返回列表中item第一次出现的索引,item not in list的时候会报错reversed(tuple) 返回一个倒转后的迭代器,使用list() 函数可以将其转换为列表sorted(tuple) 返回排好序的新列表,默认是从小到大 |
适用场景: 存储的数据或数量可变,用列表。 |
适用场景: 存储的数据或数量不变。 |
-
foo.__sizeof__
查看变量foo的存储空间大小。 -
list增删的时候,为了减少每次在增加或删除操作时空间分配的开销,python每次分配空间都会额外多分配一些,这样的机制over-allocating保证了其操作的高效性:增加或删除的时间复杂度均为O(1)。
-
总体上,元组的性能速度略优于列表。
-
初始化
python -m timeit 'x=(1,2,3)'
和python -m timeit 'x=[1,2,3]'
,前者速度更快。 -
索引操作
python -m timeit 'x=(1,2,3)''y=x[1]'
和python -m timeit 'x=[1,2,3]''y=x[1]'
,两者速度差异很小。 -
增删改,列表更优。
-
-
空list的创建:
list1=[]
速度快于list1=list()
。
04 字典、集合,你真的了解吗?
-
字典、集合的基础
-
字典(dict)
字典是由一系列键值对(key-value)元素组成的集合。键和值都可以是混合类型(键的数据类型只能是不可变类型,可变类型unhashable)。在python3.7+版本之后,才被确定为有序。字段长度大小可变,元素可以任意地删减和改变。
与列表和元组相比,字典的性能更优,特别是查找、添加和删除操作,字典都能在常数时间复杂度O(1)内完成。
-
集合(set)
集合是有一系列无序且唯一的元素组成的集合。元素可以是混合类型。
字典 集合 创建方式: dict={key:value, key:value}
dict=dict({key:value, key:value})
dict=dict([(key,value), (key,value)])
dict=dict(key=value, key=value)
创建方式: set={item, item}
set=set([item, item])
索引:
value=dict[key]
如果key不存在,抛出异常value=dict.get(key,default)
如果key不存在,返回默认值default索引:
集合不支持索引
集合本质上是一个哈希表?in
判断key是否在dict中:key in dict
in
判断item是否在set中:item in set
增删改操作: dict[key]=value
如果key不存在,向dict中添加这个key-value键值对,如果key存在,则是修改此key对应的value值dict.pop(key)
删除此key的键值对,return被删除的value值。增删改操作: set.add(item)
将item添加到set中set.remove(item)
将set中的item移出set.pop()
将set的最后一个元素删除,返回被删除的元素排序操作:
by key或by value排序操作: sorted(set)
默认升序(reverse = False),返回一个列表 -
dict的创建,
dict=dict({key:value})
速度快于dict={key:value}
。 -
set.pop()
删除集合的最后一个元素,但是集合本身无序,所以无法确定被删除的item是谁。 -
dict 排序(默认升序, reverse = False)
-
-
字典、集合的性能
字典、集合是性能高度优化的数据结构,特别是对于查找、添加和删除操作。
数据越大,不同数据类型之间的性能差别越大。
list dict set 知一key查value 遍历:O(n)
排序后二分查找:排序需O(nlogn),二分查找O(logn)O(1)
原因:字典的内部组成是一张哈希表,可以直接通过键的哈希值,找到其对应的值不定key查所有value 遍历key得value,遍历value看是否重复:O(n^2) 遍历key得value,set自动去重:O(n) -
字典、集合的工作原理
字典、集合的内部结构都是一张哈希表。
字典的哈希表中,存储了哈希值hash、键key 和值value 三元素。集合的哈希表中,存储的是哈希值hash、元素item 二元素。
hash表位置 index = hash(key) & mask。hash(key或item)是通过key或item计算出来的哈希值。mask = PyDicMinSize -1 。
- 插入操作(dict为例)
- 计算待插入元素应该插入的哈希表位置index。
- 如果哈希表位置为空,插入元素。
- 如果哈希表位置被占用,比较元素的哈希值和键是否相等。如果相等 ,表明这个元素已经存在,如果值不同,则更新值。如果两个有一个不相等(哈希冲突,键不同,但是哈希值相同),继续寻找表中空余的位置,直到找到位置为止。
- 查找操作(dict为例)
- 计算待查找元素的哈希表位置index。
- 比较哈希表位置中的哈希值和键,与需要查找的元素是否相等。如果相等,则直接返回;如果不相等,则继续查找,直到找到空位(?)或者抛出异常为止。
- 删除操作(dict为例)
- 计算待删除元素的哈希表位置index。
- 赋予哈希表位置的元素一个特殊的值。
- 等到重新调整哈希表大小时,将该元素删除。
- 插入操作(dict为例)
-
哈希冲突
哈希冲突,降低字典和集合操作速度。
为了保证高效性,字典和集合的哈希表,通常会保证至少留1/3的剩余空间。随着元素的不停插入,当剩余空间小于1/3时,python会重新获取更大的内存空间,扩充哈希表,此时表内的所有元素都会被重新排放。
虽然哈希冲突和哈希表大小的调整,都会导致速度减慢,但此类情况发生的次数极少。所以,平均来看,这仍能保证插入、查找和删除的时间复杂度为O(1)。
05 深入浅出字符串
-
字符串基础
字符串是由独立字符组成的一个序列,通常包含在单引号(’ ‘)、双引号(" ")、或三引号(""" """)中。
三引号,主要应用于多行字符串,如函数的注释。(使用函数的__doc__魔法方法获取函数注释。)
python支持转义字符,转义字符就是用反斜杠\开头的字符串。转义字符不影响字符串的length。常用转义字符见下表。
转义字符 说明 \newline
接下一行 \\ \ \'
单引号 \"
双引号 \n
换行 \t
横向制表符 \b
退格(光标回退一位,不能跨行?) \v
纵向制表符 -
字符串的常用操作(有tricks的一些常见操作)
-
索引
string[index]
index从0开始,index=0表示第一个字符。 -
切片
string[start:end :step]
从start个元素到end-1个元素组成的字符串,step默认为1。 -
遍历
# 遍历字符串中的每个字符。 for str in strings: print(str)
-
“修改”
字符串immutable,不能直接修改原字符串,只能新建字符串。新建字符串的时间复杂度是O(n),n为新字符串的长度。
# 将字符串string修改为String str_old = "string" # 方法一 字符串拼接 + str_new1 = "H" + str_old[1:] # 方法二 字符替换string.replace(old_str,new_str) str_new2 = str_old.replace("h","H")
-
“拼接 +=”
使用
+=
拼接字符串时,python(version 2.5+)首先会检测str
还有没有其他的引用。如果没有的话,会尝试原地扩充字符串buffer的大小,而不是重新分配一块内存来创建新的字符串并copy。# 使用+=拼接字符串,时间复杂度为遍历所需的时间O(n) str = "" for i in range(10): str += str(i)
-
拼接
string.join(iterable对象)
# 使用string.join(iterable)拼接,时间复杂度=遍历的O(n) * list append的O(1),性能更优。 iterable_list = [] for i in range(10): iterable_list.append(str(i)) str = "".join(iterable_list)
-
分割
string.split(separator分割字符)
表示把字符串string按照separator分割字符分割成子字符串sub_string,并返回一个分割后子字符串sub_string组合的列表。
-
去除
string.strip(str)
去首尾、string.lstrip(str)
去首、string.rstrip(str)
去尾str不写的时候默认是空白字符
" "
。
-
-
字符串的格式化
通常,我们使用一个字符串作为模板,模板中会有格式符。这些格式符为后续真实值预留位置,以呈现出真实值应该呈现的形式。
字符串格式化,使代码更清晰、易读、规范,不易出错。
字符串的格式化,通常会用在程序的输出、logging等场景。
# 字符串格式输出,推荐使用最新规范 format函数。 name = "Apear" age = 1 score = 80.2 # 方法一 “{}”.format(foo),{}格式符。 print("Hi,{}!".format(name)) # 方法二 “%s"%(foo),%s表示字符串型,%d表示整数型,%f表示浮点数型。 print("%s:age=%02d,score=%.02f"%(name,age,score)) # 方法三 f“{foo}”。 print(f"Bye,{name}")
06 python '黑箱':输入与输出
命令行的I/O,是python交互的最基本方式,适用一些简单小程序的交互。
生产级别的python代码,大部分I/O来自于文件、网络、其他进程的消息等等。
-
命令行I/O
input(提示信息)
-
输入Input
最简单直接的输入来自键盘操作。
input()
函数暂停程序运行,同时等待键盘输入。直到回车被按下,函数的参数即为提示语。输入的类型永远是字符串型。可将输入转换为其他类型,如int或float。在生产环境中使用强制转换时,建议使用异常捕获try...except。
-
提防I/O安全漏洞
python对int类型没有最大限制(C++的int最大为2147483647,超过这个数字会产生溢出?),但是对float类型仍然有精度限制。在生产环境中,要提防因边界条件判断不清而造成的bug甚至0day(危重安全漏洞)。
-
-
文件I/O
open(文件路径,操作模式,encoding格式)
I/O操作可能会有各种各样的情况,所以所有I/O都应该进行异常捕获try...except。
-
open(文件路径,操作模式,encoding格式)
使用open函数拿到文件的指针。
- 第一个参数,指定文件位置(相对位置或绝对位置)。
- 第二个参数,声明open方式,
"r"
表示读取,"w"
表示写入,"rw"
表示读写都要,"a"
表示追加、从原始文件的最末尾开始写入。
-
read
-
file文件对象.read()
读取文件的全部内容,结果返回string。也可以
file文件对象.read(n)
一次读取n Bytes。 -
file文件对象.readlines()
按行读取文件的全部内容,结果返回 以每行内容string组成的list。 -
file文件对象.readline()
按行读取文件,一次一行。
-
-
write
file文件对象.write(data)
将data写入file文件对象。 -
close
open()
函数对应于close()
函数,打开文件,完成读取或写入后,应立刻关掉释放内存。在
with
语境下任务执行完毕后,默认调用close()
函数,不再需要显示调用close()
。
-
-
Json 序列化与实战
Json,JavaScript Object Notation,一种轻量级的数据交换格式。Json的设计意图是把所有事情都用设计的字符串来表示,这样既方便在互联网上传递信息,也方便人进行阅读(相比binary协议)。
-
json的序列化与反序列化
json.dumps(数据)
将python的基本数据类型序列化为json字符串。json.loads(数据)
将json字符串反序列化为python的基本数据类型。 -
json类似的工具 Google的Protocol Buffer
Protocl Buffer特点:生成优化后的二进制文件,因此性能更好。但生成的二进制序列,不能直接阅读。
Protocol Buffer应用在对性能有要求的系统中如TensorFlow。
-
07 修炼基本功:条件与循环
条件与循环,控制着代码的逻辑,可以说是程序的中枢系统。
-
条件语句
if 语句可以单独使用,但是elif、else都必须和if成对使用。
# 单条件判断 if x<0 : print(x) # 双条件判断 if x<0 : print(x) else: print("Wrong number.") # 多条件判断 if x == 0: print('zero') elif x == 1: print("one") else: print("not 1 or 0")
省略判断条件的用法见下表。
建议,除了boolean类型的数据,条件判断最好是显性的。
数据类型 结果 string 空字符串解析为False,其余为true integer 0解析为False,其余为True Boolean True为True,False为False list/tuple/dict/set iterable数据为空解析为False,其余为True object None解析为False,其余为True -
循环语句
python中的循环一般通过for循环和while循环实现。
通常来说,遍历一个已知的集合,找出满足条件的元素,并进行相应的操作,使用for循环更加简洁。如果需要在满足某个条件之前,不停地重复某些操作,并且没有特定的集合需要去遍历,一般会使用while循环。
- 遍历
可迭代的数据(iterable),如string\list\tuple\dict\set等,都可以进行遍历。特别强调,dict本身只有键可迭代,如果要遍历dict的值或键值对,需要通过其内置的函数value()或者items()实现;value()返回字典的值的集合,items()返回(key,value)元组的集合。
当同时需要索引和元素时,通过python内置的函数enumerate()。用它遍历集合,不仅返回每个元素,还返回对应的索引。
# python 内置函数enumerate()的使用举例 tuple_example = (5,6,7,8,9) for i,j in enumerate(tuple_example,1): # 1,表示从1开始计数 print("第{}元素是{}".format(i,j))
-
continue 和 break
continue
让程序跳过当前这层循环,继续执行下面的循环。break
完全跳出所在的整个循环体。# 利用continue或break减少嵌套(赞) # name_price: 产品名称(str)到价格(int)的映射字典 # name_color: 产品名字(str)到颜色(list of str)的映射字典 for name, price in name_price.items(): if price >= 1000: continue if name not in name_color: print('name: {}, color: {}'.format(name, 'None')) continue for color in name_color[name]: if color == 'red': continue print('name: {}, color: {}'.format(name, color))
-
条件与循环的复用
# if...else与循环的复用 result = expression1 if condition else expression2 for item in iterable # 上述代码等同于 for item in iterable: if condition: result = expression1 else: result = expression2 # if...与循环的复用 result = expression for item in iterable if condition # 上述代码等同于 for item in iterable: if condition: result = expression
08 异常处理:如何提高程序的稳定性?
-
错误与异常
程序错误:一种是语法错误,一种是异常。
语法错误:代码不符合编程规范,无法被识别与执行。
Invalid Syntax
异常:程序的语法正确,也可以被执行,但在执行过程中遇到了错误,抛出了异常。
相关文档:https://docs.python.org/3/library/exceptions.html#bltin-exceptions
-
如何处理异常
try...except
try block:无异常时,正常执行。
except block:有异常,且异常类型匹配时执行。如果程序抛出的异常不匹配,程序照样会中止退出。
try: """如果程序无异常,执行try这个block""" except Exception as e: """如果程序有异常,且异常属于Exception,执行except这个block""" finally: """无论如何都要执行的语句,即使try或except block中使用了return语句""" print("如果程序正常执行try或except block,会执行这个语句")
为了使程序覆盖所有的异常类型,做法有:
-
列举多个异常类型
try: ... except (ValueError,IndexError) as e: ...
-
使用多条except语句
程序中存在多个except block时,最多只有一个except block会被执行。也即,如果多个except声明的异常类型都与实际相匹配,那么只有最前面的except block会被执行,其他则被忽略。
try: ... except ValueError as e: ... except IndexError as e: ...
-
使用Exception
Exception,是所有非系统异常的基类,能够匹配任意非系统异常。
try: ... except ValueError as e: ... except IndexError as e: ... except Exception as e: ...
-
省略异常类型
except后面省略异常类型,表示与任意异常相匹配(包括系统异常等)。
-
-
用户自定义异常
定义异常类,继承自Exception。
-
异常的使用场景与注意点
如果不确定某段代码能够成功执行,往往这个地方就需要使用异常处理。如数据库的连接、读写等。
对于流程控制flow-control的代码逻辑,一般不用异常处理,直接用条件语句即可。
09 不可或缺的自定义函数
-
函数基础
函数就是为了实现某一功能的代码段,写好之后就可以重复利用。
# 定义函数 def my_func(message): # def 函数的声明;my_func 函数的名称;(message) message是函数的参数 print("Got a message:{}".format(message)) #函数的主体部分 # 调用函数 my_func("Hello Word!") # 函数名() 调用函数 # 函数形式 def func_name(param1,param2,param3...): statements return/yield value # optional 可有可无
def是可执行语句,这意味着函数直到被调用前,都是不存在的。当程序调用函数时,def语句才会创建一个新的函数对象,并赋予其名字。
主程序调用函数时,必须保证这个函数此前已经定义过,不然就会报错。但是,在函数内部调用其他函数,函数间哪个声明在前、哪个在后就无所谓,因为def是可执行语句,函数在调用之前都不存在,但需保证调用时所需的函数都已经声明定义。
python函数的参数可以设定默认值。在调用函数时,如果参数param没有传入,则参数为默认值;而如果传入了参数param,则会覆盖默认值。
-
python函数的特性——多态
多态:python不用考虑输入的数据类型,而是将其交给具体的代码去判断执行,同一函数可用在整型、列表、字符串等操作中。
多态,方便,但实际使用时也会有问题。建议必要时进行数据的类型检查。
-
python函数的特性——支持函数嵌套
嵌套:函数里面又有函数。
嵌套,能够保证内部函数的隐私。内部函数只能被外部函数所调用和访问,不会暴露在全局作用域中。内部函数无法在外部函数以外被单独调用。
嵌套,能够提高程序的运行效率。
-
-
函数变量作用域
局部变量:在函数内部定义的变量。只在函数内部有效。一旦函数执行完毕,局部变量就会被回收、无法访问。
全局变量:整个文件层次上的变量。可以在文件内的任何地方被访问,当然在函数内部也是可以的。
-
在函数内部修改全局变量,global声明
# 在函数内部改变全局变量的值,必须加上global声明 global_a = 1 def func_test(): global global_a # 使用global声明global_a是全局变量 global_a = 12 print(global_a)
-
函数内部局部变量与全局变量同名,在函数内部局部变量覆盖全局变量
-
嵌套函数的内部函数修改外部函数定义的变量,nonlocal声明
def outer(): x ="outer" def inner(): nonlocal x x = "new_outer" print("inner:{}".format(x)) # "inner:new_outer" inner() print("outer:{}".format(x)) # "outer:new_outer"
-
-
闭包(closure)
外部函数返回的是一个函数,而不是一个具体的值。
闭包,让程序变得更简洁易读。
闭包,常和装饰器一起使用。
# 定义闭包 def outer(exponent): def inner(base): return base**exponent return inner # 调用闭包 square = outer(2) s1 = square(3) # 3**2 s2 = square(5) # 5**2
10 简约不简单的匿名函数lambda
-
lambda基础
# lambda格式 lambda 参数args1,参数args2...参数argsN:表达式expression # lamdba举例 square = lambda x :x**2 # lambda调用 s1 = square(3) # 3**2
-
lambda是一个表达式expression,不是一个语句statement。
表达式,就是用一系列”公式“去表达一个东西。语句,则是完成了某些功能。
作为一个表达式,lambda可用在列表中或作为某些函数的参数使用。
-
lambda的主体是只有一行的简单表达式,并不能拓展成一个多行的代码块。
lambda专注于简单的任务,而常规函数则负责更复杂的多行逻辑。
参考阅读:https://www.artima.com/weblogs/viewpost.jsp?thread=147358
-
lambda的使用场景:程序中需要使用一个函数完成一个简单的功能,并且该函数只调用一次。
-
-
为什么要使用匿名函数?
运用匿名函数的代码更简洁。
-
python函数式编程
函数式编程:指代码中每一块都是不可变的(immutable),都是由纯函数(pure function)的形式组成。这里的纯函数,指函数本身相互独立、互不影响,对于相同的输入,总会有相同的输出,没有任何副作用。
函数式编程的优点,主要在于其纯函数和不可变的特性使程序更加健壮,易于调试(debug)和测试。缺点主要在于限制多,难写。
-
python内置函数
数据量多的时候,倾向函数式编程的表示(map\filter\reduce),效率更高。
数据量不多的时候,想要程序更加pythonic的话,列表表达式(list comprehension)是不错的选择。
-
map(func,iterable)
map()函数,对iterable中的每个元素,都运用func这个函数,最后返回一个新的可遍历的集合(map对象,可以通过list()转化为list)。
map()函数,由C语言写成,运行时不需要通过python解释器间接调用,且内部做了诸多优化,所以运行速度可以很快。
-
filter(func,iterable)
filter()函数,对iterable中的每个元素,都使用fucntion判断,并返回True或False,最后将返回True的元素组成一个新的可遍历的集合(filter对象,不能通过list()转化为list,但可以通过tuple()转化为tuple)。
-
reduce(func,iterable)
reduce()函数,对iterable做一些累积操作。func函数对象,要求有两个参数,表示对iterable中的每个元素以及上一次调用后的结果,运用function进行计算。最后返回的是一个单独的数值。
python 3.x 之后,reduce需要从functools中导入使用:
from functools import reduce
-
11 面向对象(上):从生活中的类说起
OOP:object oriented programming
CRUD:create, retrieve, update, delete
面向对象四要素:封装、抽象、继承、多态。封装区分类内和类外。抽象区分接口和实现。继承一个类拓展其他类。多态,一个接口多个实现。封装是基础。抽象和多态有赖于继承实现。
继承:类生子。
图灵完备?图灵完备编程?汇编语言的MOV指令?
-
对象,你找到了吗?
类:一群有着相似性的事物的集合,对应python class。
对象:集合中的一个事物,对应由class生成的某一个object。
属性:对象的某个静态特征。
函数:对象的某个动态能力。
以上为非严谨定义。
严谨定义,类:一群有着相同属性和函数的对象的集合。
# 定义类 class ClassName(): # 类名-大驼峰 def __init__(self): # 生成对象时会被自动调用 self.name = "zoe" self.__gender = "female" #__开头,私有属性,在类的函数之外不能被访问或修改。 def self_func(self): # 类的普通函数,调用时方被执行 pass
-
老师,能不能再给力点?
静态函数
@staticmethod
:做一些简单独立的任务,既方便测试,也能优化代码结构。类函数
@classmethod
:最常用的功能是实现不同的init构造函数。成员函数:不需要任何装饰器声明,第一个参数self代表当前的对象的引用,可以通过此函数实现想要的查询/修改类的属性等功能。
-
继承,是每个富二代的梦想
类的继承,顾明思义,指的是一个类(子类)既拥有另一个类(父类)的特征,也拥有不同于另一个类(父类)的独特特征。
继承的优势:减少重复的代码,降低系统的熵值(即复杂度)。
# 父类 class Father(): def __init__(self): pass def print_info(self): pass # 子类 class Son(Father): def __init__(self): Father.__init__(self) #子类生成对象的时候,不会调用父类的init函数,需要手动显示调用。执行顺序 子类init->父类init。 pass def print_info(self): print("son") #子类重写父类函数 pass
-
抽象类?
抽象类,一种特殊的类,只能作为父类存在,一旦对象化就会报错。抽象函数定义在抽象类中,使用
@abstractmethod
装饰,子类必须要重写抽象函数才能使用。抽象类,一种自上而下的设计风范,用少量的代码描述清楚要做的事情,定义好接口,不同开发人员去开发和对接。
拓展:Facebook中,idea提出后,开发组和产品组召开产品设计会,PM写产品需求文档,TL写开发文档,开发文档中会定义不同模块的大致功能和接口、每个模块之间如何协作、单元测试和集成测试、线上灰度测试、监测和日志等一系列开发流程。
12 面向对象(下):如何实现一个搜索引擎?
代码地址:https://gitee.com/apear1107/search_engine.git
-
“高大上”的搜索引擎
搜索引擎search engine:搜索器、索引器、检索器和用户接口四部分组成。
搜索器,通俗来讲就是我们常提到的爬虫,在互联网上爬取各类网站的内容,送给索引器。索引器拿到网页和内容后,会对内容进行处理,形成索引,存储于内部的数据库等待检索。用户接口指网页和App前端页面,用户通过接口,向搜索引擎发出询问,询问解析后送达检索器;检索器高效检索索引后,再将结果返回给用户。
-
Bag of Words and Inverted Index
优化索引库,把语料分词,看成一个个的词汇,对每篇文章存储它所有词汇的set。
齐夫定律?:在自然语言的语料库里,一个单词出现的频率与它在频率表里的排名成反比,呈现幂律分布。
-
BOW model
BOW model,bag of words model,词袋模型。NLP领域最常见最简单的模型之一。不考虑语法、句法、段落、词汇出现的顺序,将文本看成词汇的集合。
-
倒序索引 Inverted Index model
正常索引是 id:text键值对的字典,search时遍历text的词库,如果有就返回id。
倒序索引时word:[id]键值对的字典,search时只需看是否in dict.keys(),如果有就返回其对应的值[id]。如果是对多字段进行索引,返回 每个字段的search结果[id] 的共有元素。
-
-
LRUcache和多重继承
-
LRUCache缓存类
可以通过继承LRUCache缓存类来调用其方法。
LRU缓存是一种很经典的缓存,符合自然界的局部性原理,可以保留最近使用过的对象,而逐渐淘汰掉很久没有用过的对象。
使用pylru package。
has()
函数判断是否在缓存中,如果在,调用get()
函数直接返回结果;如果不在,送入后台计算结果并将结果存入缓存。 -
多重继承
# 多重继承 class A(): def a_info(): pass class B(): def b_info(): pass class C(A,B): # 调用父类A方法,super()默认继承的第一个父类 super().a_info() # 调用父类B方法 B.b_info()
-
-
拓展:私有属性
Python并没有真正的私有化支持,但可用下划线实现伪私有。
-
_xxx
"单下划线 " 开始的成员变量叫做保护变量,意思是只有类对象和子类对象自己能访问到这些变量,需通过类提供的接口进行访问。
-
__xxx
"双下划线,类中的私有变量/方法名,只有类对象自己能访问,连子类对象也不能访问到这个数据。
-
__xxx__
魔法方法,前后均有一个“双下划线” ,python里特殊方法专用的标识,如
__init__
代表类的构造函数。
-
13 搭建积木:python 模块化
-
简单模块化
-
调用同级目录
from . import 模块
.代替/来表示同级目录。 -
调用上级目录
使用
sys.path.append("..")
将当前程序所在位置向上提一级,然后可以直接调用上级目录的文件。
-
-
项目模块化
-
文件路径
绝对路径:以/开头,来表示根目录到叶子节点的路径。
相对路径:相对某一文件的路径。
-
模块路径
在大型工程中,尽可能使用绝对位置作为模块路径。对于一个独立的项目,所有的模块的追寻方式,最好从项目的根目录开始追溯,即相对的绝对路径。
做项目时,以根目录作为最基本的目录,所有的模块调用,都要通过根目录一层层向下索引的方式来import。将模块放在不同子文件夹里,跨模块调用则是从顶层直接索引,一步到位,非常方便。
-
import路径
python解释器在遇到import的时候,会在一个特定的列表中寻找模块。
# 导包路径 import sys import_path = sys.path # [导包路径,导包路径...] # Pycharm将第一项设置为项目根目录的绝对地址 # import时,从项目根目录中找相应的包
# 设置项目根目录 建议方法二 # 方法一 将第一项修改为项目目录的绝对路径 import sys sys.path.insert(0,绝对路径) # 方法二 修改PYTHONHOME 在一个Virtual Environment里,在activate文件末尾添加PYTHONPATH (LINUX) export PYTHONPATH = "项目路径"
-
-
神奇的
if __name__ == "__main__"
import 导入文件的时候,会自动把所有暴露在外面的代码全都执行一遍。如果要把一个东西封装成模块,又不想让它执行的话,可以将执行代码放在
if __name__ == "__main__"
下面。__name__
作为python的魔术内置函数,本质上是模块对象的一个属性——模块对象的名字。