Python基础概览

《Python服务端工程师面试宝典-PegasusWang》学习笔记,第一章:鸭子类型、猴子补丁、性能分析、单元测试等。
306阅读 · 2020-5-24 23:55发布

Python语言基础

Python是静态还是动态类型?是强类型还是弱类型?

  • 动态强类型语言。
  • 动态指在运行时确定类型。
  • 强类型指不会发生隐式转换。(例如1+'1'会报错)

为什么要用Python?

  • 胶水语言,第三方库多,应用广泛。(爬虫、后端、数据分析、机器学习等)
  • 语言灵活,快速构建项目。
  • 性能问题、代码维护难(动态语言特点)、Python2/3兼容问题。

什么是鸭子类型?

比方一只鸟走路像鸭子、游泳像鸭子、叫起来像鸭子,那么这只鸟就可以被称为鸭子。

  • 关注对象的行为,而不是类型。(鸭子类型更专注接口而非类型)
  • 例如file,socket对象都支持read/write方法(file like object)
  • 例如定义了iter魔术方法的对象可以用for迭代。
  • 示例代码:

    class Duck:
          def quack(self):
              print("gua gua")
    
      class Person:
          def quack(self):
              print("gua gua")
    
      def in_the_forset(duck):
          duck.quack()
    
      if __name__ == '__main__':
          a = Duck()
          b = Person()
          in_the_forset(a)
          in_the_forset(b)
    

什么是monkey patch(猴子补丁)?

  • monkey patch指运行时的属性替换。
  • 例如gevent库需要修改内置socket阻塞为非阻塞,就是使用monkey.patch_socket()切换。
  • 示例代码:

    import time
      print(time.time())
      def _time():
          return 1234
      time.time = _time
    
      print(time.time())
    

什么是自省(Introspection)?

  • 即可以运行时判断一个对象的类型的能力。
  • 通过type、id、isinstance获取对象的类型信息。

什么是列表推导(List Comprehension)和字典推导(Dict Comprehension)?

  • 是一种python语法糖。
  • 用于快速生成list/dict/set的方式。用来替代map/filter等。
  • 使用圆括号包裹时会返回生成器:(i for i in range(10) if i%2 ==0)
  • 使用生成器时会节省内存。

什么是Python之禅(The Zen of Python)?

  • Time Peters(他是Python核心开发者)编写的关于Pthon编程的准则。
  • import this可以看到Python之禅。
  • 用于编写优美的代码。

Python2和3的差异

Python3改进

  • print成为函数。
  • 编码变化。Python3不再有Unicode对象,默认str是unicode。
  • 除法变化。Python除号返回浮点数。
  • 父类调用优化。super方法直接调用父类函数。
  • 高级解包操作。示例:

    a, b, *rest = range(10)
    
  • 类型注解。给变量加类型提示。示例:

    def hello(name:str) -> str  # 表示参数name应该为str类型,返回值应该为str类型
          return 'hello ' + name
    
  • 限定关键字参数(Keyword only arguments)。

  • 异常优化。Python3重新抛出异常不会丢失栈信息(Chained exceptions)。
  • 更多返回迭代器。例如:range,zip,map,dict.values。

Python3新增的语法和内置库

  • yield from 链接子生成器。
  • asyncio内置库,async/await原生协程支持异步编程。
  • 新的内置库:enum,mock,asyncio,ipaddress,concurrent.futures等。
  • 生成的pyc文件统一放到pycache
  • 内置库的修改。例如urllib,selector等。
  • 性能优化。例如dict的性能。

Python2/3工具(兼容工具)

  • six模块
  • 2to3等工具转换代码

函数相关知识

函数的参数传递

  • 可变类型作为参数,传引用。

    def flist(l):
          l.append(0)
          print(l)
    
      l = []
      flist(l)
      flist(l)
    
  • 不可变类型作为参数,传值。

    def fstr(s)
          s += 'a'
          print(s)
      s = 'hehe'
      fstr(s)
      fstr(s)
    
  • Call by sharing(共享传参)。函数形参获得实参中各个引用的副本。

  • 不可变对象:bool/int/float/tuple/str/frozenset。
  • 可变对象:list/set/dict。
  • 默认参数只计算一次。示例:

    def flist(l=[2]):
          l.append(3)
          print(l)
    
      flist()
      flist([9])
      flist()
      # 输出:
      # [2, 3]
      # [9, 3]
      # [2, 3, 3]
    

Python的*args和**kwargs

  • 用来代表可变参数。
  • *args被打包成tuple。
  • **kwargs被打包成dict。

Python异常机制

有哪些Python异常?

异常指一种错误处理机制。

  • BaseException,所有异常都继承该类。
  • 系统相关异常:SystemExit/KeyboardInterrupt/GeneratorExit。
  • 其他异常:Exception。

使用异常的场景

  • 网络请求(超时、连接错误)
  • 资源访问(权限问题、资源不存在)
  • 代码逻辑错误(越界访问、KeyError等)

处理异常的语句

  • try、except处理异常。示例:
    try:
          # func   #此处编写可能会抛出异常的代码
      except (Exception1, Exception2) as e: #此处可以捕获多个异常,并别名为e
          # 发生异常时处理的逻辑
      else:
          # 异常没有发生时处理的逻辑
      finally:
          # 此处是无论异常有没有发生,最后都会执行的代码
    

如何自定义异常

  • 继承Exception实现自定义异常。
  • 给异常增加附加信息。
  • 编写处理业务相关的异常。

    class MyException(Exception):
          pass
    
      try:
          raise MyException("my exception")
      excep MyException as e:
          print(e)
      # 输出
      # my exception
    

Python性能分析和优化

什么是Cpython GIL

  • GIL,Global Interpreter Lock。
  • Cpython解释器的内存管理性不是线程安全的。
  • 用于保护多线程情况下对Python对象的访问。
  • Cpython使用简单的锁机制里面多线程同时执行字节码。

GIL的影响

  • 同一个时间只能有一个线程执行字节码。
  • CPU密集程序难以利用多核优势。
  • IO期间会释放GIL,对IO密集程序影响不大。

如何规避GIl影响

  • CPU密集可以使用多进程+进程池。
  • IO密集使用多线程/协程。
  • cpython扩展。(把python代码转换成c)

GIL的实现

  • 每执行一定字节码之后,会检查当前是否有GIL锁,有的话就释放锁,再重新获取锁。

GIL为什么不是线程安全的?

  • 指定不止一个字节码时,会导致线程不安全。示例:

    import threading
      n = [0]
      def foo():
          n[0] = n[0] +1
          n[0] = n[0] +1
    
      threads = []
      for i in range(5000):
          t = threading.Thread(target=foo)
          threads.append(t)
    
      for t in threads:
          t.start()
    
      print(n)
      # 会出现结果非10000的情况
    
  • 一个操作如果一个字节码指令可以完成的就是原子操作。

  • 原子的才可以保证线程安全。
  • 使用dis操作来分析字节码。示例:

    import threading
      import dis
      n = [0]
      def foo():
          n[0] = n[0] +1
          n[0] = n[0] +1
    
      threads = []
      for i in range(5000):
          t = threading.Thread(target=foo)
          threads.append(t)
    
      for t in threads:
          t.start()
    
      print(dis.dis(foo))
      """
        5           0 LOAD_GLOBAL              0 (range)
                    2 LOAD_CONST               1 (10)
                    4 CALL_FUNCTION            1
                    6 GET_ITER
              >>    8 FOR_ITER                20 (to 30)
                   10 STORE_FAST               0 (i)
    
        6          12 LOAD_GLOBAL              1 (n)
                   14 LOAD_CONST               2 (0)
                   16 BINARY_SUBSCR
                   18 LOAD_CONST               3 (1)
                   20 BINARY_ADD                         # 执行增加操作
                   22 LOAD_GLOBAL              1 (n)      # 在这步可能会切换到其他线程
                   24 LOAD_CONST               2 (0)      # 在这步可能会切换到其他线程
                   26 STORE_SUBSCR                        # 执行赋值操作
                   28 JUMP_ABSOLUTE            8
              >>   30 LOAD_CONST               0 (None)
                   32 RETURN_VALUE
      None
      """
    
  • 可以手动加锁使线程安全。示例:

    import threading
      lock = threading.Lock()
      n = [0]
      def foo():
          with lock:
              n[0] = n[0] +1
              n[0] = n[0] +1
    
      threads = []
      for i in range(5000):
          t = threading.Thread(target=foo)
          threads.append(t)
    
      for t in threads:
          t.start()
    
      print(n)
    

如何剖析程序性能

  • 使用内置的profile/cprofile等工具。
  • 使用pyflame(uber开源)的火焰图工具。

服务端性能优化措施

  • 数据结构与算法优化。
  • 数据库层:索引优化、慢查询消除、批量操作减少IO、NoSQL。
  • 网络IO:批量操作、pipeline操作减少IO。
  • 缓存:使用内存数据库redis/memcached。(内存读取数据快,通常用于高并发请求)
  • 异步:asyncio、celery。
  • 并发:gevent/多线程。

Python生成器与协程

什么是生成器(Generator)?

  • 生成器就是可以生成值的函数。
  • 当一个函数里有了yield关键字就成了生成器。
  • 生成器可以挂起执行并且保持当前执行状态。示例:

    def simple_gen():
          yield 'hello'
          yield 'world'
    
      gen = simple_gen()
      print(next(gen))
    

什么是生成器的协程?

  • 协程是在一个线程执行过程中可以在一个子程序的预定或者随机位置中断,然后转而执行别的子程序,在适当的时候再返回来接着执行。他本身是一种特殊的子程序或者称作函数。
  • Python3之前没有原生协程,只有基于生成器的协程。
  • 基于生成器的协程,支持send()向生成器发送数据和throw()向生成器抛出异常。示例:

    def core():
          hello = yield 'hello'
          yield hello
    
      c = core()
      print(next(c))
      print (c.send('world'))
    
  • Python3.5引入async/await支持原生协程(native coroutine)

Python单元测试

什么是单元测试?

  • 针对程序模块进行正确性检验。
  • 用于对一个函数,一个类进行验证。

为什么要写单元测试?

  • 保证代码逻辑的正确性(有些采用测试驱动开发(TDD))
  • 单元测试影响设计时的思维,易测代码往往是高内聚低耦合。
  • 回归测试,防止改一处导致整个服务不可用。

单元测试相关库

  • nose/pytest较为常用。
  • mock模块用来模拟替换网络请求等。
  • coverage统计测试覆盖率。

如何编写单元测试

  • 使用assert(通常assert替代print)。

    def demo(array):
          if not array:
              return -1
          for i in array:
              if i>2:
                  return i
          return -1
    
      def test():
          assert demo([1,3,6]) == 3
          assert demo([6,1]) == 2
    
  • 单元测试中编写名为test的函数,使用pytest会执行文件中所有test开头的函数。

    # pip install pytest -i https://pypi.douban.com/simple
      # pytest xxx.py