python核心技术与实战:基础篇

基础篇

01 如何逐步突破,成为python高手?

  • 不同语言,需融汇贯通
  • 唯一语言,可循序渐进
    • 勤加练习
    • 代码规范
    • 开发经验

02 JupyterNotebook为什么是现代python的必学技术?

JupyterNotebook,几乎支持所有语言,能够把软件代码、计算输出、解释文档、多媒体资源整合在一起的多功能科学运算平台

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]',前者速度更快。

      1600247682431

      1600247735566

    • 索引操作 python -m timeit 'x=(1,2,3)''y=x[1]'python -m timeit 'x=[1,2,3]''y=x[1]',两者速度差异很小。

    • 增删改,列表更优。

  • 空list的创建:list1=[]速度快于list1=list()1600248141872

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}

      1600264810757

    • set.pop()删除集合的最后一个元素,但是集合本身无序,所以无法确定被删除的item是谁。

    • dict 排序(默认升序, reverse = False)

      1600260089861

  • 字典、集合的性能

    字典、集合是性能高度优化的数据结构,特别是对于查找、添加和删除操作。

    数据越大,不同数据类型之间的性能差别越大。

    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为例)
      1. 计算待插入元素应该插入的哈希表位置index。
      2. 如果哈希表位置为空,插入元素。
      3. 如果哈希表位置被占用,比较元素的哈希值和键是否相等。如果相等 ,表明这个元素已经存在,如果值不同,则更新值。如果两个有一个不相等(哈希冲突,键不同,但是哈希值相同),继续寻找表中空余的位置,直到找到位置为止。
    • 查找操作(dict为例)
      1. 计算待查找元素的哈希表位置index。
      2. 比较哈希表位置中的哈希值和键,与需要查找的元素是否相等。如果相等,则直接返回;如果不相等,则继续查找,直到找到空位(?)或者抛出异常为止。
    • 删除操作(dict为例)
      1. 计算待删除元素的哈希表位置index。
      2. 赋予哈希表位置的元素一个特殊的值。
      3. 等到重新调整哈希表大小时,将该元素删除。
  • 哈希冲突

    哈希冲突,降低字典和集合操作速度。

    为了保证高效性,字典和集合的哈希表,通常会保证至少留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)
      

      1600271390567

    • 分割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/Oinput(提示信息)

    • 输入Input

      最简单直接的输入来自键盘操作。

      input()函数暂停程序运行,同时等待键盘输入。直到回车被按下,函数的参数即为提示语。输入的类型永远是字符串型。

      可将输入转换为其他类型,如int或float。在生产环境中使用强制转换时,建议使用异常捕获try...except。

    • 提防I/O安全漏洞

      python对int类型没有最大限制(C++的int最大为2147483647,超过这个数字会产生溢出?),但是对float类型仍然有精度限制。在生产环境中,要提防因边界条件判断不清而造成的bug甚至0day(危重安全漏洞)。

  • 文件I/Oopen(文件路径,操作模式,encoding格式)

    I/O操作可能会有各种各样的情况,所以所有I/O都应该进行异常捕获try...except。

    • open(文件路径,操作模式,encoding格式)

      使用open函数拿到文件的指针。

      1. 第一个参数,指定文件位置(相对位置或绝对位置)。
      2. 第二个参数,声明open方式,"r"表示读取,"w"表示写入,"rw"表示读写都要,"a"表示追加、从原始文件的最末尾开始写入。
    • read

      1. file文件对象.read()读取文件的全部内容,结果返回string。

        也可以file文件对象.read(n)一次读取n Bytes。

      2. file文件对象.readlines() 按行读取文件的全部内容,结果返回 以每行内容string组成的list。

      3. 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,会执行这个语句")
    

    为了使程序覆盖所有的异常类型,做法有:

    1. 列举多个异常类型

      try:
          ...
      except (ValueError,IndexError) as e:
          ...
      
    2. 使用多条except语句

      程序中存在多个except block时,最多只有一个except block会被执行。也即,如果多个except声明的异常类型都与实际相匹配,那么只有最前面的except block会被执行,其他则被忽略。

      try:
          ...
      except ValueError as e:
          ...
      except IndexError as e:
          ...
      
    3. 使用Exception

      Exception,是所有非系统异常的基类,能够匹配任意非系统异常。

      try:
          ...
      except ValueError as e:
          ...
      except IndexError as e:
          ...
      except Exception as e:
          ...
      
    4. 省略异常类型

      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的魔术内置函数,本质上是模块对象的一个属性——模块对象的名字。

posted @ 2020-09-19 10:35  快果一只  阅读(195)  评论(0编辑  收藏  举报