Python后端面试(持续更新)

Python后端面试

Python后端技术栈

Web请求的流程

  • 浏览器

  • 负载均衡

  • Web框架

  • 业务逻辑

  • 数据库缓存

Python语言基础

  • 语言特点

  • 语法基础

  • 高级特性

算法与数据结构

  • 常用算法和数据结构

  • 分析时间、控件复杂度

  • 实现常见数据结构和算法

编程范式

  • 面向对象编程

  • 常用设计模式

  • 函数式编程

操作系统

  • 常用Linux命令

  • 进程、线程

  • 内存管理

网络编程

  • 常用协议TCP、IP、HTTP

  • Socket编程基础

  • Python并发库

数据库

  • Mysql常考,索引优化

  • 关系型和NoSQL的使用场景

  • Redis缓存

Python Web框架

  • 常用框架对比,RESTful

  • WSGI原理

  • Web安全问题

系统设计

  • 设计原则,如何分析

  • 后端系统常用组件(缓存、数据库、消息队列等)

  • 技术选型和实现(短网址服务、Feed流系统)

技术之外,软实力

  • 学习能力

  • 业务理解能力,沟通交流能力

  • 心态

Python初、中级工程师技能要求

初级工程师

  • 扎实的计算机理论基础

  • 代码规范,风格良好

  • 能在指导下靠谱地完成业务要求

中级工程师

  • 扎实的计算机基础和丰富的项目经验

  • 能够独立设计和完成项目要求

  • 熟悉成员web组件(缓存、消息队列),具有一定的系统设计能力

软技能

  • 具有产品意识,技术引导产品

  • 沟通交流嫩合理,团队协作能力

  • 技术领导能力和影响力

面试准备

  • 工作内容和业务紧密相关

  • 平台决定成长(业务体量)

  • 准备面试需要有的放矢,跟职位相匹配

简历书写与自我介绍

简历内容

  • 基本信息(姓名、学校、学历、联系方式等)

  • 职业技能(编程语言、框架、数据库、开发工具等)

  • 关键项目经验(担任职责,用到了哪些技术)

简历加分项

  • 知名项目经验

  • 技术栈比较匹配

  • 开源项目(GitHub、技术blog、Linux、unix geek)

简历注意事项

  • 内容精简、突出重点。不宜超过两页、可以套用模板

  • 主要格式,推荐PDF

  • 信息真实,不弄虚作假。技能要和岗位匹配

自我介绍

  • 个人信息

  • 掌握的技术,参与过的项目

  • 应聘的岗位,表达对该岗位的看法和兴趣

Python后端面试(持续更新)_第1张图片
image

行为面试题与表达技巧

什么是行为面试

根据候选人过去的行为评测其胜任能力

  • 理论依据:行为的连贯性

  • 人在面对相似的场景时会倾向于重复过去的行为模式

  • 评判人的业务能力,沟通交流能力,语言表达能力,坑压能力等

行为面试套路

  • 提问方式:说说你曾经

  • 说说你做过的这个项目

  • 说说你碰到过的技术难题?你是如何解决的?有哪些收获?

STAR模型

Python后端面试(持续更新)_第2张图片
image

面试官一般会问:你还有什么要问我的吗?

  • 千万别说没了,直接说表明你对岗位缺乏了解和兴趣

  • 表现出兴趣:提问工作内容(业务)、技术栈、团队、项目等

  • 问自己的感兴趣的问题

注意事项

  • 态度真诚,力求真实,不要弄虚作假

  • 言简意赅,突出重点,省略细枝末节。适当模拟训练

  • 采用STAR模型让回答更有条理

Python语言基础常见考题

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

  • 动态强类型语言

  • 动态还是静态指的是编译期还是运行期确定类型

  • 强类型指的是不会发生隐式类型转换

Python作为后端语言优缺点

  • 胶水语言,轮子多,应用广泛

  • 语言灵活,生成力高

  • 性能问题、代码维护问题、Python2、3兼容问题

什么是鸭子类型

  • 关注点在对象的行为,而不是类型(duck typing)

  • 比如file,StringIO,socket对象都支持read/write方法

  • 再比如定义了__iter__魔术方法的对象可以用for迭代

什么是monkey patch

  • 所谓的monkey patch就是运行时替换

  • 比如gevent库需要修改内置的socket

  • from gevent import monkey; monkey.patch_socket()

什么是自省(Introspection)

  • 运行时判断一个对象的类型的能力

  • Python一切皆对象,用type,id,isinstance获取对象类型信息

  • Inspect模块提供了更多获取对象信息的函数

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

  • 比如[i for i in range(10) if i % 2 == 0]

  • 一种快速生成list、dict、set的方式。用来替代map、filter等

  • (i for i in range(10))返回生成器

Python2/3对比

Python3改进

  • print成为函数

  • 编码问题。Python3不再有Unicode对象,默认str就是unicode

  • 除法变化。Python3返回浮点数

  • 类型注解(type hint)。帮助IDE实现类型检查

def hello(name: str) -> str:
   return 'hello' + name
  • 优化的super()方便直接调用父类函数
class Base(object):
 def hello(self):
     print('say hello')

class C(Base):
 def hello(self):
         # py2
     return super(C, self).hello()

class C2(Base):
 def hello(self):
         # py3
     return super().hello()
  • 高级解包操作。a, b, *rest = range(10)

  • Keyword only arguments。限定关键字参数

def add(a, b, *, c):
    print(a+b+c)

# 会报错
add(1, 2, 3)
# 正确写法
add(1, 2, c=3)
  • Chained exceptions。Python3重新抛出异常不会丢失栈信息

  • 一切返回迭代器range, zip, map, dict.values, etc. are all iterators.

  • 生成的pyc文件统一放在__pycache__

  • 一些内置库的修改。urllib,selector等

  • 性能优化等

Python3新增

  • yield from链接子生成器

  • asyncio内置库,async/await原生协程支持异步编程

  • 新的内置库enum,mock,asyncio,ipaddress,concurrent.futures等

Python2/3工具

  • six模块

  • 2to3等工具转换代码

  • __future__

Python函数常考题

以下Python代码分别输出什么?

def flist(l):
    l.append(0)
    print(l)
    ​
l = []
flist(l) #[0]
flist(l) #[0, 0]
def fstr(s):
    s + = 'a'
    print(s)

s = 'hehe'
fstr(s) #'hehea'
fstr(s) #'hehea'
  • 可变类型参数

  • 不可变类型参数

Python如何传递参数?

  • 传递值还是引用呢?都不是。唯一支持的参数传递是共享传参

  • Call by Object (Call by Object Reference or Call by sharing)

  • Call by sharing(共享传参)。参数形参获得实参中各个引用的副本

Python可变/不可变对象

  • 不可变bool int float tuple str frozenset

  • 可变list set dict

测试

# 一个小例题,请问这段代码会输出什么结果?
# first
def clear_list(l):
    l = []

ll = [1, 2, 3]
clear_list(ll)
print(ll)
​
# second
# 默认参数只计算一次
def flist(l=[1]):
    l.append(1)
    print(l)

flist()
flist()

Python *args, *kwargs

  • 用来处理可变参数

  • *args被打包成tuple

  • **kwargs被打包成dict

Python异常机制常考题

什么是Python异常?

  • Python使用异常处理错误

    • BaseException

      • SystemExit/KeyboardInterrupt/GeneratorExit

      • Exception

什么时候需要捕获处理异常?看Python内置异常类型

  • 网络请求(超时、链接错误等)

  • 资源访问(权限问题、资源不存在)

  • 代码逻辑(越界访问、KeyError等)

如何处理Python异常

try:
    # func                            # 可能会抛出异常的代码
except (Exception1, Exception2) as e: # 可以捕获多个异常并处理
    # 异常处理代码
else:
    pass                              # 异常没有发生的时候代码逻辑
finally:
    pass                              # 无论异常有没有发生都会执行的代码,一般处理资源的关闭和释放

如何自定义异常

  • 继承Exception实现自定义异常(想想为什么不是BaseException)

  • 给异常加上一些附加信息

  • 处理一些业务相关的特定异常(rasie MyException)

class MyException(Exception):
    pass 
​
try:
    raise MyException('my exception')
except MyException as e:
    print(e)

Python性能分析与优化,GIL常考题

什么是Cpython GIL

  • GIL, Global Interpreter Lock

  • Cpython解释器的内存管理并不是线程安全的

  • 保护多线程情况下Python对象的访问

  • Cpython使用简单的锁机制避免多个线程同时执行字节码

GIL影响

  • 限制了程序的多核执行

    • 同一时间只能有一个线程执行字节码

    • CPU密集程序难以利用多核优势

    • IO期间会释放GIL,对IO密集程序影响不大(爬虫,web)

  • Python后端面试(持续更新)_第3张图片
    image

如何规避GIL影响

  • CPU密集可以使用多进程

  • IO密集可以使用多进程、协程

  • cython扩展

Python后端面试(持续更新)_第4张图片
image

问题

import threading
​
n = [0]
​
def foo():
    n[0] = n[0] + 1
    n[0] = n[0] + 1

threads = []
for i in range(50000):
    t = threading.Thread(target=foo)
    threads.append(t)

for t in threads:
    t.start()

print(n)

# 大多数情况下打印10000,偶尔打印9998,Python的多线程并不是绝对安全的

为什么有了GIL还要关注线程安全

  • Python中什么操作才是原子的?一步执行到位

    • 一个操作如果是一个字节码指令可以执行完成的就是原子的

    • 原子的是可以保证线程安全的

    • 使用dis操作才分析字节码

import dis
​
def update_list(l):
 # 原子操作,不用担心线程安全问题
    l[0] = 1

dis.dis(update_list)
'''
 5           0 LOAD_CONST               1 (1)
 2 LOAD_FAST                0 (l)
 4 LOAD_CONST               2 (0)
 6 STORE_SUBSCR             # 字节码操作,线程安全
 8 LOAD_CONST               0 (None)
 10 RETURN_VALUE
'''
​
def incr_list(l):
 # 危险!不是原子操作
    l[0] += 1

dis.dis(incr_list)
'''
 3           0 LOAD_FAST                0 (l)
 2 LOAD_CONST               1 (0)
 4 DUP_TOP_TWO
 6 BINARY_SUBSCR
 8 LOAD_CONST               2 (1)
 10 INPLACE_ADD              # 需要多个字节码操作,有可能在线程执行过程中切到其他线程
 12 ROT_THREE
 14 STORE_SUBSCR
 16 LOAD_CONST               0 (None)
 18 RETURN_VALUE
'''

如何剖析程序性能

  • 使用各种profile工具(内置或者第三方)

    • 二八定律,大部分时间花费在少量代码上

    • 内置的profile、cprofile等工具

    • 使用pyflame(uber开源)的火焰图工具

服务端性能优化措施

  • web应用一般语言不会成为瓶颈

    • 数据结构和算法

    • 数据库层:索引优化,慢查询消除,批量操作减少IO,NoSQL

    • 网络IO:批量操作,pipeline操作减少IO

    • 缓存:使用内存数据库redis/memcached

    • 异步:asyncio,celery

    • 并发:gevent/多线程

Python生成器与协程

生成器(Generator)

  • 生成器就是可以生成值的函数
  • 当一个函数里有了yield关键字就成了生成器
  • 生成器可以挂起执行并且保持当前执行状态
def simple_gen():
    yield 'hello'
    yield 'world'

gen = simple_gen()
print(type(gen))
print(next(gen))
print(next(gen))
'''

hello
world
'''

基于生成器的协程

  • Python3之前没有原生协程,只有基于生成器的协程
    • PEP 342(Coroutines via Enhanced Generators)增强生成器功能
    • 生成器可以通过yield暂停执行和产出数据
    • 同时支持send()向生成器发送数据和throw()向生成器抛出异常
def coro():
    # yield关键字在=右边作为表达式,可以被send值
    hello = yield 'hello'
    yield hello

c = coro()
# 输出'hello',这里调用next产出第一个值'hello',之后函数暂停
print(next(c))
# 再次调用send发送值,此时hello变量赋值为'world',然后yield产出hello变量的值'world'
print(c.send('world'))
# 之后协程结束,后续再send值会抛出异常StopIteration

协程注意点

  • 协程需要使用send(None)或者next(coroutine)来[预激](prime)laiqidong
  • 在yield处协程会暂停执行
  • 单独的yield value会产出值给调用方
  • 可以通过coroutine.send(value)来给协程发送值,发送的值会赋值给yield左边的变量value = yield
  • 协程执行完成后(没有遇见下一个yield语句)会抛出StopIteration异常

协程装饰器

  • 避免每次都要用send预激它
from functools import wraps

def coroutine(func):
    '''
    装饰器:向前执行一个yield表达式,预激func
    '''
    @wraps(func)
    def primer(*args, **kwargs):
        gen = func(*args, **kwargs)
        next(gen)
        return gen
    return primer

Python3.5引入async/await支持原生协程(native coroutine)

import asyncio
import datetime
import random

async def display_date(num, loop):
    end_time = loop.time() + 50.0
    while True:
        print('Loop: {} Time: {}'.format(num, datetime.datetime.now()))
        if (loop.time() + 1.0) >= end_time:
            break
        await asyncio.sleep(random.randint(0, 5))
        
      
loop = asyncio.get_event_loop()
asyncio.ensure_future(display_date(1, loop))
asyncio.ensure_future(display_date(2, loop))
loop.run_forever()

Python单元测试

什么是单元测试

  • Unit Testing
    • 针对程序模块进行正确性检验
    • 一个函数,一个类进行验证
    • 自底向上保证程序正确性

为什么写单元测试

  • 三无代码不可取(无文档、无注释、无单测)
    • 保证代码逻辑的正确性(甚至有些采用测试驱动开发(TDD))
    • 单测影响设计,易测的代码往往是高内聚低耦合的
    • 回归测试,防止改一处整个服务不可用

单元测试相关的库

  • nose/pytest较为常用
  • mock模块用来模拟替换网络请求等
  • coverage统计测试覆盖率
def binary_search(arr, target):
    if not arr:
        return -1
    low, high = 0, len(arr) - 1
    while low <= high:
        mid = low + (high - low) >> 2
        if arr[mid] == target:
            return mid
        elif arr[mid] > target:
            high = mid - 1
        else:
            low = mid + 1
    return - 1


def test():
    '''
    如何设计测试用例:
    - 正常值功能测试
    - 边界值(比如最大最小,最左最右)
    - 异常值(比如None,空值,非法值)
    '''
    # 正常值,包含有和无两种结果
    assert binary_search([0, 1, 2, 3, 4, 5], 1) == 1
    assert binary_search([0, 1, 2, 3, 4, 5], 6) == -1
    assert binary_search([0, 1, 2, 3, 4, 5], -1) == -1
    # 边界值
    assert binary_search([0, 1, 2, 3, 4, 5], 0) == 0
    assert binary_search([0, 1, 2, 3, 4, 5], 5) == 5
    assert binary_search([0], 0) == 0
    # 异常值
    assert binary_search([], 1) == -1

Python基础练习题

Python深拷贝与浅拷贝

  • 什么是深拷贝?什么是浅拷贝?
  • Python中如何实现深拷贝?
  • 思考:Python中如何正确初始化一个二维数组?
import copy

l1 = [1, 2, 3]
l2 = l1
l3 = copy.copy(l1)
l4 = copy.deepcopy(l1)
print(l1, l2, l3, l4)
'''
[1, 2, 3] [1, 2, 3] [1, 2, 3] [1, 2, 3]
'''
l2[0] = 666
print(l1, l2, l3, l4)
'''
[666, 2, 3] [666, 2, 3] [1, 2, 3] [1, 2, 3]
'''
l3[0] = 888
print(l1, l2, l3, l4)
'''
[666, 2, 3] [666, 2, 3] [888, 2, 3] [1, 2, 3]
'''
l4[0] = 999
print(l1, l2, l3, l4)
'''
[666, 2, 3] [666, 2, 3] [888, 2, 3] [999, 2, 3]
'''

Python内置数据结构和算法常考

数据结构/算法 语言内置 内置库
线性结构 list、tuple array(数组,不常用)/collections.namedtuple
链式结构 collections.deque(双端队列)
字典结构 dict collections.Counter(计数器)/OrderedDict(有序字典)
集合结构 set/frozenset(不可变集合)
排序算法 sorted
二分算法 bisect模块
堆算法 heapq模块
缓存算法 functools.lru_cache(Least Recent Used, python3)

collections模块提供了一些内置数据结构的扩展

  • namedtuple
import collections

Point = collections.namedtuple('Point', 'x, y')
p = Point(1, 2)
print(p.x)
'''
1
'''
print(p.y)
'''
2
'''
print(p.x == p[0])
'''
True
'''
  • deque
import collections

de = collections.deque()
de.append(1)
de.appendleft(0)
print(de)
'''
deque([0, 1])
'''
de.pop()
'''
1
'''
de.popleft()
'''
0
'''
print(de)
'''
deque([])
'''
  • Counter
  • OrderedDict(使用循环双端链表保存key的顺序)
  • defaultdict

Python dict底层结构

  • dict底层使用哈希表
    • 为了支持快速查找使用了哈希表作为底层结构
    • 哈希表平均查找时间复杂度O(1)
    • Cpython解释器使用二次探查解决哈希冲突问题

Python list/tuple区别

  • 都是线性结构,支持下标访问
  • list是可变对象,tuple保存的引用不可变
t = [1,2], 1, 2
t[0].append(3)
print(t)
'''
([1, 2, 3], 1, 2)
'''
  • list无法作为字典的key,tuple可以(可变对象不可hash)

什么是LRUCache?

  • Least-Recently-Used替换掉最近最少使用的对象

    • 缓存剔除策略,当缓存空间不够用的时候需要一种方式剔除key
    • 常见的有LRU, LFU等
    • LRU通过使用一个循环双端队列不断的把最新访问的key放到表头实现
  • 字典用来缓存,循环双端链表用来记录访问顺序

    • 利用Python内置的dict+collections.OrderedDict实现
    • dict用来当做k/v键值对的缓存
    • OrderedDict用来实现最新最近访问的key

Python常考算法

排序+查找,重中之重

  • 常考排序算法:冒泡排序、快速排序、归并排序、堆排序
  • 线性查找,二分查找
  • 能独立实现代码(手写),能够分析时间空间复杂度

常见排序算法的时空复杂度

排序算法 最差时间复杂度 平均时间复杂度 稳定度 空间复杂度
冒泡排序 O(n^2) O(n^2) 稳定 O(1)
选择排序 O(n^2) O(n^2) 不稳定 O(1)
插入排序 O(n^2) O(n^2) 稳定 O(1)
快速排序 O(n^2) O(n*log2n) 不稳定 O(log2n)~O(n)
堆排序 O(n*log2n) O(n*log2n) 不稳定 O(1)

排序算法的稳定性

  • 相同大小的元素在排序之后依然保持相对位置不变,就是稳定的
  • r[i]=r[j]r[i]r[j]之前,排序之后r[i]依然在r[j]之前
  • 稳定性对于排序一个复杂结构,并且需要保持原有排序才有意义

请写出快速排序

  • 快排经常问:分治法(divide and conquer),快排三步走
    • Partition:选择基准分割数组为两个子数组,小于基准和大于基准的
    • 对两个子数组分别快排
    • 合并结果
def quicksort(array):
  # 递归出口
  if len(array) < 2:
    return array
  else:
    pivot_index = 0
    pivot = array[pivot_index]
    less_part = [i for i in array[pivot_index+1:] if i <= pivot]
    great_part = [i for i in array[pivot_index+1:] if i > pivot]
    return quicksort(less_part) + [pivot] + quicksort(great_part)
 
def test_quicksort():
    import random
    l = list(range(10))
    random.shuffle(l)
    print('排序前:', l)
    res = quicksort(l)
    print('排序后:', res)
    assert res == sorted(l)

合并两个有序数组

  • 要求m+n复杂度内
def merge_sorted_list(sorted_a, sorted_b):
    len_a, len_b = len(sorted_a), len(sorted_b)
    a = b = 0
    res = []
    while a < len_a and b < len_b:
        if sorted_a[a] < sorted_b[b]:
            res.append(sorted_a[a])
            a += 1
        else:
            res.append(sorted_b[b])
            b += 1
    if a < len_a:
        res.extend(sorted_a[a:])
    else:
        res.extend(sorted_b[b:])
    return res

res = merge_sorted_list([0,3,6,7],[0, 0, 23,33])
print(res)

归并排序

def mergesort(array):
    # 递归出口
    if len(array) <= 1:
        return array
    else:
        mid = int(len(array)/2)
        left_half = mergesort(array[:mid])
        right_half = mergesort(array[mid:])
        return merge_sorted_list(left_half, right_half)

堆排序

# 借助heapq模块
def heapsort_use_heapq(iterable):
    from heapq import heappush, heappop
    items = []
    for value in iterable:
        heappush(items, value)
    return [heappop(items) for i in range(len(items))]

二分查找

def binary_search(sorted_array, val):
    if not sorted_array:
        return -1
    start = 0
    end = len(sorted_array) - 1
    while start <= end:
        mid = start + ((end - start) >> 2)
        if sorted_array[mid] == val:
            return mid
        elif sorted_array[mid] > val:
            end = mid - 1
        else:
            start = mid + 1
    return -1

Python常考数据结构

Python web后端常考数据结构

  • 常见的数据结构链表、队列、栈、二叉树、堆
  • 使用内置的结构实现高级数据结构,比如内置的list/deque实现栈
  • LeetCode或者剑指Offer上的常考题

链表

链表有单链表、双链表、循环双端链表

  • 如何使用Python来表示链表结构

  • 实现链表常见操作,比如插入节点,反转链表,合并多个链表等

  • LeetCode练习常见链表题目

    • 206翻转链表

      class ListNode:
          def __init__(self, x):
              self.val = x
              self.next = None
              
      class Solution:
          def reverseList(self, head):
              '''
              :type head: ListNode
              :rtype ListNode
              '''
              pre, cur = None, head
              while cur:
                  cur.next, pre, cur = pre, cur, cur.next
              return pre
      

队列

队列(queue)是先进先出结构

  • 如何使用Python实现队列?
  • 实现队列的append和pop操作,如何做到先进先出
  • 使用collections.deque实现队列
from collections import deque

class Queue:
    def __init__(self):
        self.items = deque()
        
    def append(self, val):
        return self.items.append(val)
        
    def pop(self):
        return self.items.popleft()
    
    def empty(self):
        return len(self.items) == 0

  • 如何使用Python实现栈?
  • 实现栈的push和pop操作,如何做到先进后出
  • 使用collections.deque实现队列
from collections import deque

class Stack:
    def __init__(self):
        self.itmes = deque()
      
    def push(self, val):
        self.items.append(val)
        
    def pop(self, val):
        return self.items.pop()
      
    def empty(self):
        return len(self.items) == 0

字典与集合

Python dict/set底层都是哈希表

  • 哈希表的实现原理,底层其实就是一个数组
  • 根据哈希函数快速定位一个元素,平均查找O(1)
  • 不断加入元素会引起哈希表重新开辟空间,拷贝之前的元素到新数组

哈希表如何解决冲突

  • 链接法
    • 元素key冲突之后使用一个链表填充相同key的元素
  • 开放寻址法
    • 冲突之后根据一种方式(二次探查)寻找下一个可用的槽
  • cpython使用的二次探查

二叉树

先序、中序、后序

  • 先序 根左右
  • 中序 左根右
  • 后序 左右根
class BinTreeNode:
    def __init__(self, data, left=None, right=None):
        self.data = data
        self.left = left
        self.right = right
        
    def BinTree:
        def __init__(self, root=None):
            self.root = root
            
        def preorder_trav(self, subtree):
            '''先序'''
            if subtree is not None:
                print(subtree.data)
                self.preorder_trav(subtree.left)
                self.preorder_trav(subtree.right)
                
        def inorder_trav(self, subtree):
            if subtree is not None:
                self.inorder_trav(subtree.left)
                print(subtree.data)
                self.inorder_trav(subtree.right)
                
        def lastorder_trav(self, subtree):
            if subtree is not None:
                self.lastorder_trav(subtree.left)
                self.lastorder_trav(subtree.right)
                print(subtree.data)

堆其实是完全二叉树,有最大堆和最小堆

  • 最大堆:对于每个非叶子节点V,V的值都比它的两个孩子大
  • 最小堆:对于每个非叶子节点V,V的值都比它的两个孩子小
  • 最大堆支持每次pop操作获取最大的元素,最小堆获取最小元素
  • 常见问题:用堆完成topK问题,从海量数字中寻找最大的K个
import heapq

class TopK:
    '''
    获取大量元素topK大个元素,固定内存
    思路:
    1. 先放入元素前k个建立一个最小堆
    2. 迭代剩余元素:
       如果当前元素小于堆顶元素,跳过该元素(肯定不是前k大)
       否则替换堆顶元素为当前元素,并重新调整堆
    '''
    def __init__(self, iterable, k):
        self.miniheap = []
        self.capacity = k
        self.iterable = iterable
        
    def push(self, val):
        if len(self.miniheap) >= self.capacity:
            min_val = self.miniheap[0]
            if val > min_val:
                heapq.heapreplace(self.miniheap, val)
        else:
            self.miniheap.append(val)
            
    def get_topk(self):
        for val in self.iterable:
            self.push(val)
        return self.miniheap
      
if __name__ == '__main__':
    import random
    l = list(range(10))
    random.shuffle(l)
    topk = TopK(l, 3)
    res = topk.get_topk()
    print(res)

Python白板编程

什么是白板编程

传说中的手写算法题,白纸或者白板上手写代码

  • ACM/蓝桥杯之类的算法竞赛
  • 刷题,LeetCode、《剑指Offer》

为什么要手写算法

工作用不到,为什么还要考?

  • 有些公司为了筛选编程能力强的同学,近年来对算法要求越来越高
  • 针对刚出校门的同学问得多,有经验的反而算法考的少(偏向工程经验)
  • 竞争越来越激烈,大家水平差不多的优先选取有算法竞赛经验的

如何准备

没有太好的方式,刷常见题。防止业务代码写多了算法手生

  • 刷题,LeetCode,《剑指Offer》
  • 面试之前系统整理之前做过的题目,不要靠记忆而是真正的理解掌握
  • 打好基础是重点,面试可以刷常见题突击,保持手感

面试前练习

刷题(LeetCode+剑指Offer+不同公司的面经)

  • 《剑指Offer》上常见题用Python实现
  • 把LeetCode上常见分类题目刷一遍(GitHub搜LeetCode分类)
  • 常见排序算法和数据结构可以手写

Python常考数据结构之链表

链表

链表涉及指针操作较为复杂,容易出错,经常用作考题

  • 熟悉链表的定义和常见操作

  • 常考题:删除一个链表节点

    • LeetCode237

      # Definition for singly-linked list.
      # class ListNode:
      #     def __init__(self, x):
      #         self.val = x
      #         self.next = None
      
      class Solution:
          def deleteNode(self, node):
              """
              :type node: ListNode
              :rtype: void Do not return anything, modify node in-place instead.
              """
              node.val = node.next.val
              node.next = node.next.next
      
  • 常考题:合并两个有序链表

    • LeetCode21

      # Definition for singly-linked list.
      # class ListNode:
      #     def __init__(self, x):
      #         self.val = x
      #         self.next = None
      
      class Solution:
          def mergeTwoLists(self, l1: ListNode, l2: ListNode) -> ListNode:
              res = ListNode(0)
              if l1 is None:
                  return l2
              if l2 is None:
                  return l1
              if l1.val < l2.val:
                  res = l1
                  res.next = self.mergeTwoLists(l1.next, l2)
              else:
                  res = l2
                  res.next = self.mergeTwoLists(l1, l2.next)
              return res
      

多写多练

找到相关的题目,多做一些练习

  • 一般一次很难写对
  • 尝试自己先思考,先按照自己的方式写代码,提交后发现问题
  • 如果实在没有思路可以先去看其他人的题解

Python常考数据结构之二叉树

二叉树

二叉树涉及到递归和指针操作,常结合递归考察

  • 二叉树的操作很多可以用递归的方式解决,不了解递归会比较吃力

  • 常考题:二叉树的镜像(反转二叉树)

    • LeetCode226

      # Definition for a binary tree node.
      # class TreeNode:
      #     def __init__(self, x):
      #         self.val = x
      #         self.left = None
      #         self.right = None
      
      class Solution:
          def invertTree(self, root: TreeNode) -> TreeNode:
              if root:
                  root.left, root.right = root.right, root.left
                  self.invertTree(root.left)
                  self.invertTree(root.right)
              return root
      
  • 常考题:如何层序遍历二叉树(广度优先)

    • LeetCode102

      # Definition for a binary tree node.
      # class TreeNode:
      #     def __init__(self, x):
      #         self.val = x
      #         self.left = None
      #         self.right = None
      
      class Solution:
          def levelOrder(self, root):
              """
              :type root: TreeNode
              :rtype: List[List[int]]
              """
              if not root:
                  return []
              res = []
              cur_nodes = [root]
              next_nodes = []
              res.append([i.val for i in cur_nodes])
              while cur_nodes or next_nodes:
                  for node in cur_nodes:
                     if node.left:
                         next_nodes.append(node.left)
                     if node.right:
                         next_nodes.append(node.right)
                  if next_nodes:
                      res.append([i.val for i in next_nodes])
                  cur_nodes = next_nodes
                  next_nodes = []
              return res
      

Python常考数据结构之栈和队列

栈和队列

后进先出(栈)vs 先进先出(队列)

  • 熟练掌握用Python的list或者collections.deque()实现栈和队列

  • 常考题:用栈实现队列

    • LeetCode232

      class Stack:
          def __init__(self):
              self.stack = []
              
          def push(self, x: int) -> None:
              self.stack.append(x)
              
          def pop(self):
              return self.stack.pop()
          
          def top(self):
              return self.stack[-1]
          
          def empty(self):
              return self.stack == []
          
      class MyQueue:
      
          def __init__(self):
              """
              Initialize your data structure here.
              """
              self.s1 = Stack()
              self.s2 = Stack()
              
      
          def push(self, x: int) -> None:
              """
              Push element x to the back of queue.
              """
              self.s1.push(x)
              
      
          def pop(self) -> int:
              """
              Removes the element from in front of queue and returns that element.
              """
              if not self.s2.empty():
                  return self.s2.pop()
              
              while not self.s1.empty():
                  self.s2.push(self.s1.pop())
              return self.s2.pop()
              
      
          def peek(self) -> int:
              """
              Get the front element.
              """
              if not self.s2.empty():
                  return self.s2.top()
              
              while not self.s1.empty():
                  self.s2.push(self.s1.pop())
              return self.s2.top()
              
      
          def empty(self) -> bool:
              """
              Returns whether the queue is empty.
              """
              return self.s1.empty() and self.s2.empty()
              
      
      
      # Your MyQueue object will be instantiated and called as such:
      # obj = MyQueue()
      # obj.push(x)
      # param_2 = obj.pop()
      # param_3 = obj.peek()
      # param_4 = obj.empty()
      

Python常考数据结构之堆

堆的常考题基本围绕在合并多个有序(数组/链表)、TopK问题

  • 理解堆的概念,堆是完全二叉树,有最大堆和最小堆

  • 会使用Python内置的heapq模块实现堆的操作

  • 常考题:合并k个有序链表

    • LeetCode23
    # Definition for singly-linked list.
    # class ListNode:
    #     def __init__(self, x):
    #         self.val = x
    #         self.next = None
    
    class Solution:
        def mergeKLists(self, lists: List[ListNode]) -> ListNode:
            # 读取所有节点
            h = []
            for node in lists:
                while node:
                    h.append(node.val)
                    node = node.next
            if not h:
                return
            # 构造一个最小堆
            from heapq import heapify, heappop
            # 转换成最小堆
            heapify(h)
            # 构造链表
            root = ListNode(heappop(h))
            curnode = root
            while h:
                nextnode = ListNode(heappop(h))
                curnode.next = nextnode
                curnode = nextnode
            return root
    

Python常考数据结构之字符串

字符串

了解字符串的常用操作

  • Python内置了很多字符串操作,例如split、strip、upper、replace等

  • 常考题:翻转一个字符串

    • LeetCode344

      class Solution:
          def reverseString(self, s: List[str]) -> None:
              """
              Do not return anything, modify s in-place instead.
              """
              start, end = 0, len(s)-1
              while start < end:
                  s[start], s[end] = s[end], s[start]
                  start += 1
                  end -= 1
      
  • 常考题:判断一个字符串是否为回文

    • LeetCode9

      class Solution:
          def isPalindrome(self, x: int) -> bool:
              if x < 0:
                  return False
              s = str(x)
              start, end = 0, len(s)-1
              while start < end:
                  if s[start] == s[end]:
                      start += 1
                      end -= 1
                  else:
                      return False
              return True
      

算法与数据结构练习题

反转链表

链表在面试中是一个高频考点

  • 如何反转一个单链表
  • 能否用循环的方式实现吗?
  • 能否用递归的方式实现吗?

面向对象基础及Python类常考问题

什么是面向对象编程

Object Oriented Programming(OOP)

  • 把对象作为基本单元,把对象抽象成类(Class),包含成员和方法
  • 数据封装、继承、多态
  • Python中使用类来实现。过程式编程(函数),OOP(类)

Python中如何创建类?

class Person(object): # py3可以省略object直接写作 class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age 
        
    def print_name(self):
      print('my name is {}'.format(self.name))

组合与继承

优先使用组合而非继承

  • 组合是使用其他的类实例作为自己的一个属性(Has-a 关系)
  • 子类继承父类的属性和方法(Is a 关系)
  • 优先使用组合保持代码简单

类变量和实例变量的区别

区分类变量和实例变量

  • 类变量由所有实例共享
  • 实例变量由实例单独享有,不同实例之间不影响
  • 当我们需要一个类的不同实例之间共享变量的时候使用类变量

classmethod和staticmethod区别

classmethod vs staticmethod

  • 都可以通过Class.method()方式使用
  • classmethod第一个参数是cls,可以引用类变量
  • staticmethod使用起来和普通函数一样,只不过放在类里

什么是元类?使用场景

元类(Meta Class)是创建类的类

  • 元类允许我们控制类的生成,比如修改类的属性等
  • 使用type来定义元类
  • 元类最常见的一个使用场景就是ORM框架
# 元类继承自type
class LowercaseMeta(type):
    '''修改类的属性名称为小写的元类'''
    def __new__(mcs, name, bases, attrs):
        lower_attrs = {}
        for k, v in attrs.items():
            if not k.startswith('__'):
                lower_attrs[k.lower()] = v
            else:
                lower_attrs[k] = v
        return type.__new__(mcs, name, bases, lower_attrs)
     
class LowercaseClass(metaclass=LowercaseMeta):
    BAR = True
    
    def Hello(self):
        print('hello')
        
print(dir(LowercaseClass))

Python装饰器常见问题

Decorator

  • Python中一切皆对象,函数也可以当做参数传递
  • 装饰器是接受函数作为参数,添加功能后返回一个新函数的函数(类)
  • Python中通过@使用装饰器

编写一个计算耗时的装饰器

import time

def log_time(func):
    def wrapper(*args, **kwargs):
        start = time.time()
        res = func(*args, **kwargs)
        print('use time:{}'.format(time.time()-start))
        return 
    return wrapper
 
@log_time
def mysleep():
    time.sleep(1)
    
mysleep()

如何使用类编写装饰器?

import time

class LogTime:
    def __call__(self, func):
        def wrapper(*args, **kwargs):
            start = time.time()
            res = func(*args, **kwargs)
            print('use time. {}'.format(time.time()-start))
            return res
        return wrapper
      
      
@LogTime()
def mysleep2():
    time.sleep(1)
    
mysleep2()

如何给装饰器增加参数?

使用类装饰器比较方便实现装饰器参数

import time

class LogTimeParams:
  def __init__(self, use_int=False):
      self.use_int = use_int
      
  def __call__(self, func):
      def wrapper(*args, **kwargs):
          start = time.time()
          res = func(*args, **kwargs)
          if self.use_int:
              print('use time: {}'.format(int(time.time()-start)))
          else:
              print('use time: {}'.format(time.time()-start))
          return res
      return wrapper
    
@LogTimeParams(True)
def mysleep3():
    time.sleep(1)
    
mysleep3()

Python设计模式之创建型模式

创建型设计模式常考题

常见创建型设计模式

  • 工厂模式(Factory):解决对象创建问题
  • 构造模式(Builder):控制复杂对象的创建
  • 原型模式(Prototype):通过原型的克隆创建新的实例
  • 单例模式(Borg/Singleton):一个类只能创建同一个对象
  • 对象池模式(Pool):预先分配同一类型的一组实例
  • 惰性计算模式(Lazy Evaluation):延迟计算(Python的property)

工厂模式

什么是工厂模式(Factory)

  • 解决对象的创建问题
  • 解耦对象的创建和使用
  • 包括工厂方法和抽象工厂
# 一个工厂方法的例子
class DogToy:
    def speak(self):
        print('wang wang')
        
class CatToy:
    def speak(self):
        print('miao miao')
        
def toy_factory(toy_type):
    if toy_type == 'dog':
        return DogToy()
    else:
        return CatToy()

构造模式

什么是构造模式(Builder)

  • 用来控制复杂对象的构造
  • 创建和表示分离。比如你要买电脑,工厂模式直接给你需要的电脑
  • 但是构造模式允许你自己定义电脑的配置,组装完成后给你
# 一个构造模式的例子
class Computer:
    def __init__(self, serial_number):
        self.serial = serial_number
        self.memory = None
        self.hdd = None
        self.gpu = None
        
    def __str__(self):
        info = ('Memory: {}GB'.format(self.memory),
               'Hard Disk: {}'.format(self.hdd),
               'Graphics Card: {}'.format(self.gpu))
        return '\n'.join(info)
      

class ComputerBuilder:
    def __init__(self):
        self.computer = Computer('Macbook Pro 2018')
        
    def configure_memory(self, amount):
        self.computer.memory = amount
    
    def configure_hdd(self, amount):
        self.computer.hdd = amount
        
    def configure_gpu(self, amount):
        self.computer.gpu = amount
        
        
class HardwareEngineer:
    def __init__(self):
        self.builder = None
        
    def construct_computer(self, memory, hdd, gpu):
        self.builder = ComputerBuilder()
        [step for step in (self.builder.configure_memory(memory),
                          self.builder.configure_hdd(hdd),
                          self.builder.configure_gpu(gpu))]
    
    @property
    def computer(self):
        return self.builder.computer
      
      
# 使用builder,可以创建多个builder类实现不同的组装方式
engineer = HardwareEngineer()
engineer.construct_computer(hdd=500, memory=8, gpu='GeForece GTX 650 Ti')
computer = engineer.computer
print(computer)

原型模式

什么是原型模式(Prototype)

  • 通过克隆原型来创建新的实例
  • 可以使用相同的原型,通过修改部分属性来创建新的实例
  • 用途:对于一些创建实例开销比较高的地方可以用原型模式

单例模式

单例模式的实现有多种方式

  • 单例模式:一个类创建出来的对象都是同一个
  • Python的模块其实就是单例,只会导入一次
  • 使用共享同一个实例的方式来创建单例模式
# 单例模式
class Singleton:
    def __new__(cls, *args, **kwargs):
        if not hasattr(cls, '_instance'):
            _instance = super().__new__(cls, *args, **kwargs)
            cls._instance = _instance
        return cls._instance
      
class MyClass(Singleton):
    pass
  
c1 = MyClass()
c2 = MyClass()
assert c1 is c2 # 单例的,c1 c2是同一个实例

Python设计模式之结构型模式

常见结构型设计模式

  • 装饰器(Decorator):无需子类化扩展对象功能
  • 代理模式(Proxy):把一个对象的操作代理到另一个对象
  • 适配器模式(Adapter):通过一个间接层适配统一接口
  • 外观模式(Facede):简化复杂对象的访问问题
  • 享元模式(Flyweight):通过对象复用(池)改善资源利用,比如连接池
  • Model-View-Controller(MVC):解耦展示逻辑和和业务逻辑

代理模式

什么是代理模式(Proxy)

  • 把一个对象的操作代理到另一个对象
  • 这里又要提到我们之前实现的Stack/Queue,把操作代理到deque
  • 通常使用has-a组合关系
from collections import deque

# 使用组合的例子
class Stack:
    
    def __init__(self):
            self._deque = deque() # has a deque()
        
    def push(self, val):
        return self._deque.append(val)
    
    def pop(self):
        return self._deque.pop()
      
    def empty(self):
        return len(self._deque) == 0
  

适配器模式

什么是适配器模式(Adapter)

  • 把不同对象的接口适配到同一个接口
  • 想象一个多功能充电头,可以给不同的电器充电,充当了适配器
  • 当我们需要给不同的对象统一接口的时候可以使用适配器模式
# 适配器模式的例子
class Dog:
    def __init__(self):
        self.name = 'Dog'
        
    def bark(self):
        return 'woof!'
      
  
class Cat:
    def __init__(self):
        self.name = 'Cat'
        
    def meow(self):
        return 'meow'
    
    
class Adapter:
    def __init__(self, obj, **adapted_methods):
        '''
        We set the adapted methods in the object's dict
        '''
        self.obj = obj
        self.__dict__.update(adapted_methods)
        
    def __getattr__(self, attr):
        '''
        All non-adapted calls are passed to the object
        '''
        return getattr(self.obj, attr)
      
      
objects = []
dog = Dog()
objects.append(Adapter(dog, make_noise=dog.bark))
cat = Cat()
objects.append(Adapter(cat, make_noise=cat.meow))
for obj in objects:
    print('A {0} goes {1}'.format(obj.name, obj.make_noise()))

Python设计模式之行为型模式

常见行为型设计模式

  • 迭代器模式(Iterator):通过统一的接口迭代对象
  • 观察者模式(Observer):对象发生改变的时候,观察者执行相应动作
  • 策略模式(Strategy):针对不同规模输入使用不同的策略

迭代器模式

什么是迭代器模式(Iterator)

  • Python内置对迭代器模式的支持
  • 比如我们可以用for遍历各种Iterable的数据类型
  • Python里可以实现__next____iter__实现迭代器
from collections import deque

# 使用组合的例子
class Stack:
    
    def __init__(self):
            self._deque = deque() # has a deque()
        
    def push(self, val):
        return self._deque.append(val)
    
    def pop(self):
        return self._deque.pop()
      
    def empty(self):
        return len(self._deque) == 0

    def __iter__(self):
        res = []
        for i in self._deque:
            res.append(i)
        for i in reversed(res):
            yield i
            
            
s = Stack()
s.push(1)
s.push(2)
for i in s:
    print(i)

观察者模式

什么是观察者模式(Observer)

  • 发布订阅是一种最常用的实现方式
  • 发布订阅用于解耦逻辑
  • 可以通过回调等方式实现,当发生事件时,调用相应的回调函数
# 发布订阅模式

# 发布者
class Publisher:
    def __init__(self):
        # 观察者
        self.observers = []
        
    # 加入观察者
    def add(self, observer):
        if observer not in self.observers:
            self.observers.append(observer)
        else:
            print('Failed to add: {}'.format(observer))
     
    # 移除观察者
    def remove(self, observer):
        try:
            self.observers.remove(observer)
        except ValueError:
            print('Failed to remove: {}'.format(observer))
            
    # 调用观察者的回调
    def notify(self):
        [o.notify_by(self) for o in self.observers]
        

# 继承自发布者
class Formatter(Publisher):
    def __init__(self, name):
        super().__init__()
        self.name = name
        self._data = 0
       
    @property
    def data(self):
        return self._data
    
    @data.setter
    def data(self, new_value):
        self._data = int(new_value)
        # data在被合法赋值以后会执行notify
        self.notify()
        
        
class BinaryFormatter:
    '''订阅者'''
    
    def notify_by(self, publisher):
        print("{}: '{}' has now bin data = {}".format(
            type(self).__name__,
            publisher.name,
            bin(publisher.data))
        )
        
        
# 发布者
df = Formatter('formatter')
# 订阅者
bf = BinaryFormatter()
df.add(bf)
df.data = 3

策略模式

什么是策略模式(Strategy)

  • 根据不同的输入采用不同的策略
  • 比如买东西超过10个大八折,超过20个打七折
  • 对外暴露统一的接口,内部采用不同的策略计算
# 策略模式

class Order:
    def __init__(self, price, discount_strategy=None):
        self.price = price
        self.discount_strategy = discount_strategy
        
    def price_after_discount(self):
        if self.discount_strategy:
            discount = self.discount_strategy(self)
        else:
            discount = 0
        return self.price - discount
    
    def __repr__(self):
        fmt = ''
        return fmt.format(self.price, self.price_after_discount())
    
def ten_percent_discount(order):
    return order.price * 0.10

def on_sale_discount(order):
    return order.price * 0.25 + 20

order0 = Order(100)
order1 = Order(100, discount_strategy=ten_percent_discount)
order2 = Order(1000, discount_strategy=on_sale_discount)
print(order0)
print(order1)
print(order2)

你可能感兴趣的:(Python后端面试(持续更新))