Skip to content

Instantly share code, notes, and snippets.

@lttzzlll
Last active May 15, 2018 10:43
Show Gist options
  • Save lttzzlll/1203e4f28dbe656a906f4d0892d355d0 to your computer and use it in GitHub Desktop.
Save lttzzlll/1203e4f28dbe656a906f4d0892d355d0 to your computer and use it in GitHub Desktop.
some interview questions about python backend

1、什么是协程?--> yield 有什么作用?--> yield 实现协程底层是怎么实现的?--> yield from 又是什么?

协程,又称微线程,纤程。英文名Coroutine。

协程的概念很早就提出来了,但直到最近几年才在某些语言(如Lua)中得到广泛应用。

子程序,或者称为函数,在所有语言中都是层级调用,比如A调用B,B在执行过程中又调用了C,C执行完毕返回,B执行完毕返回,最后是A执行完毕。

所以子程序调用是通过栈实现的,一个线程就是执行一个子程序。

子程序调用总是一个入口,一次返回,调用顺序是明确的。而协程的调用和子程序不同。

协程看上去也是子程序,但执行过程中,在子程序内部可中断,然后转而执行别的子程序,在适当的时候再返回来接着执行。

最大的优势就是协程极高的执行效率。因为子程序切换不是线程切换,而是由程序自身控制,因此,没有线程切换的开销,和多线程比,线程数量越多,协程的性能优势就越明显。

第二大优势就是不需要多线程的锁机制,因为只有一个线程,也不存在同时写变量冲突,在协程中控制共享资源不加锁,只需要判断状态就好了,所以执行效率比多线程高很多。

因为协程是一个线程执行,那怎么利用多核CPU呢?最简单的方法是多进程+协程,既充分利用多核,又充分发挥协程的高效率,可获得极高的性能。

这个地方使用多进程+协程的方式,而不是用多线程+协程的方式,还因为一个线程中只能有一个事件循环(event_loop),所以线程和协程序[event_loop]之间是一对一的关系,与其这样,还不如直接使用多线程模式。

yield 用来交出当前协程的控制权,将程序运行的控制权转让给其他程序部分,所以协程是可以实现“非抢占式的多任务系统”。

yield from 可以简单的理解为将一个子协程展开。比如:

def gen(n):
    for i in range(n):
        yield i

def main():
    yield from gen(10)
for i in main():
    print(i)
# 0 1 2 3 4 5 6 7 8 9

yield from 的作用不止于此,他还可是实现调用方和被调用方两者的双向通信。

more details yield from 从入门到精通

yield from 的意义

PEP380 分6点说明了yield from 的行为。

  1. 子生成器产出的值都直接传给委派生成器的调用方(客户端代码)
  2. 使用send() 方法发给委派生成器的值都直接传给子生成器。如果发送的值是None,那么会调用子生成器的 next()方法。如果发送的值不是None,那么会调用子生成器的send()方法。如果调用的方法抛出StopIteration异常,那么委派生成器恢复运行。任何其他异常都会向上冒泡,传给委派生成器。
  3. 生成器退出时,生成器(或子生成器)中的return expr 表达式会触发 StopIteration(expr) 异常抛出。
  4. yield from表达式的值是子生成器终止时传给StopIteration异常的第一个参数。
  5. 传入委派生成器的异常,除了 GeneratorExit 之外都传给子生成器的throw()方法。如果调用throw()方法时抛出 StopIteration 异常,委派生成器恢复运行。StopIteration之外的异常会向上冒泡。传给委派生成器。
  6. 如果把 GeneratorExit 异常传入委派生成器,或者在委派生成器上调用close() 方法,那么在子生成器上调用close() 方法,如果他有的话。如果调用close() 方法导致异常抛出,那么异常会向上冒泡,传给委派生成器;否则,委派生成器抛出 GeneratorExit 异常。

目前我能理解的只有三四两条。

refer 协程

2、什么是装饰器?它有什么作用?

本质上,decorator就是一个返回函数的高阶函数.

decorator可以增强函数的功能,定义起来虽然有点复杂,但使用起来非常灵活和方便.可以很方便的实现AOP[面向切面]的场景需求。

refer 理解 Python 装饰器

3、对网络编程了解吗? --> 多进程与多线程的区别?--> 进程之间通信有哪些方式?

Socket编程(TCP, UDP).

refer 这篇文章很不错 多线程与多进程

  1. 管道(pipe),流管道(s_pipe)和有名管道(FIFO)

管道这种通讯方式有两种限制,一是半双工的通信,数据只能单向流动,二是只能在具有亲缘关系的进程间使用。进程的亲缘关系通常是指父子进程关系。

流管道s_pipe: 去除了第一种限制,可以双向传输.

管道可用于具有亲缘关系进程间的通信,命名管道:name_pipe克服了管道没有名字的限制,因此,除具有管道所具有的功能外,它还允许无亲缘关系进程间的通信;

  1. 信号(signal)

信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生。

主要作为进程间以及同一进程不同线程之间的同步手段。

  1. 消息队列

消息队列是由消息的链表,存放在内核中并由消息队列标识符标识。消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点。

消息队列是消息的链接表,包括Posix消息队列system V消息队列。有足够权限的进程可以向队列中添加消息,被赋予读权限的进程则可以读走队列中的消息。消息队列克服了信号承载信息量少,管道只能承载无格式字节流以及缓冲区大小受限等缺点。

  1. 共享内存

共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问。共享内存是最快的 IPC 方式,它是针对其他进程间通信方式运行效率低而专门设计的。它往往与其他通信机制,如信号量,配合使用,来实现进程间的同步和通信。

使得多个进程可以访问同一块内存空间,是最快的可用IPC形式。是针对其他通信机制运行效率较低而设计的。往往与其它通信机制,如信号量结合使用,来达到进程间的同步及互斥。

  1. 信号量

信号量是一个计数器,可以用来控制多个进程对共享资源的访问。它常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段。

信号是比较复杂的通信方式,用于通知接受进程有某种事件发生,除了用于进程间通信外,进程还可以发送信号给进程本身;linux除了支持Unix早期信号语义函数sigal外,还支持语义符合Posix.1标准的信号函数sigaction(实际上,该函数是基于BSD的,BSD为了实现可靠信号机制,又能够统一对外接口,用sigaction函数重新实现了signal函数);

  1. 套接字(socket)

套接口也是一种进程间通信机制,与其他通信机制不同的是,它可用于不同机器间的进程通信

更为一般的进程间通信机制,可用于不同机器之间的进程间通信。起初是由Unix系统的BSD分支开发出来的,但现在一般可以移植到其它类Unix系统上:Linux和System V的变种都支持套接字。

refer Linux进程间通信的几种方式总结--linux内核剖析(七)

4、你用过哪些 Python 的扩展库?或者哪些标准库?

  • pandas

  • aiohttp

  • django

  • flask

  • scrapy

  • click

  • asyncio

  • functools

  • collections

  • heapq

  • filecmp

  • hashlib

  • os

  • sys

  • concurrent

  • itertools

  • time

  • xml

  • random

  • unittest

  • shutil

5、initnew 分别什么作用?

new 是真正的构造函数,用来创建一个实例, new 是实例化一个类的时候第一个调用的方法。

init 用来初始化当前实例。

Use new when you need to control the creation of a new instance. Use init when you need to control initialization of a new instance.

new is the first step of instance creation. It's called first, and is responsible for returning a new instance of your class. In contrast, init doesn't return anything; it's only responsible for initializing the instance after it's been created.

In general, you shouldn't need to override new unless you're subclassing an immutable type like str, int, unicode or tuple.

new, init 的调用顺序

# Python 3, 新式类
class A:
    def __init__(self):
        print('Class {} in init function'.format(self.__class__.__name__))

    def __new__(cls):
        print('Class {} in new method'.format(cls.__name__))
        return super().__new__(cls)


A()
# Class A in new method
# Class A in init function

上面的语法是Python3的新式类, 所以在定义一个类的时候不需要 class A(object):, 使用super的时候也不需要 super(A, cls).__new__(cls)

这里因为重写了 new 方法,所以需要调用一下父类的 new 方法,因为最终创建对象还是需要type的__new__方法实现。这里就引出了一个Python类继承的 结构的问题。

A --> object --> type

注意:type所有类的基类,同时也是一个元类。

refer understanding-new-and-init

6、getattrgetattribute 分别什么作用?

Customizing attribute access
  • object.getattr(self, name)

Called when an attribute lookup has not found the attribute in the usual places (i.e. it is not an instance attribute nor is it found in the class tree for self). name is the attribute name. This method should return the (computed) attribute value or raise an AttributeError exception.

Note that if the attribute is found through the normal mechanism, getattr() is not called. (This is an intentional asymmetry between getattr() and setattr().) This is done both for efficiency reasons and because otherwise getattr() would have no way to access other attributes of the instance. Note that at least for instance variables, you can fake total control by not inserting any values in the instance attribute dictionary (but instead inserting them in another object). See the getattribute() method below for a way to actually get total control in new-style classes.

  • object.setattr(self, name, value)

Called when an attribute assignment is attempted. This is called instead of the normal mechanism (i.e. store the value in the instance dictionary). name is the attribute name, value is the value to be assigned to it.

If setattr() wants to assign to an instance attribute, it should not simply execute self.name = value — this would cause a recursive call to itself. Instead, it should insert the value in the dictionary of instance attributes, e.g., self.dict[name] = value. For new-style classes, rather than accessing the instance dictionary, it should call the base class method with the same name, for example, object.setattr(self, name, value).

  • object.delattr(self, name)

Like setattr() but for attribute deletion instead of assignment. This should only be implemented if del obj.name is meaningful for the object.

More attribute access for new-style classes

The following methods only apply to new-style classes.

  • object.getattribute(self, name)

Called unconditionally to implement attribute accesses for instances of the class. If the class also defines getattr(), the latter will not be called unless getattribute() either calls it explicitly or raises an AttributeError. This method should return the (computed) attribute value or raise an AttributeError exception. In order to avoid infinite recursion in this method, its implementation should always call the base class method with the same name to access any attributes it needs, for example, object.getattribute(self, name).

Note This method may still be bypassed when looking up special methods as the result of implicit invocation via language syntax or built-in functions. See Special method lookup for new-style classes.

注意:以上是python2.7的文档,python3.7的相应内容已经发生了变化。

refer Difference between getattr vs getattribute

getattr vs getattribute

当你访问一个对象的属性的时候,getattribute一定会被访问到。只有在该对象的属性不存在或者getattribute函数内部显示地调用了getattr的情况下, getattr才会被调用。

In order to avoid infinite recursion in this method, its implementation should always call the base class method with the same name to access any attributes it needs, for example, object.getattribute(self, name).

为了避免递归调用该方法从而陷入无限循环,getattribute的实现应该总是按照给定的属性名称来调用基类的属性, 最顶层的基类调用是 object.getattribute(self, name).

同理, setattr也遵循相同的原则。

refer Python getattribute vs getattr 浅谈

refer object.getattr

应用:web url API 自动生成.

class UrlGenerator:
    def __init__(self, root_url):
        self.url = root_url

    def __getattr__(self, item):
        if item == 'get' or item == 'post': # endpoint
            return self.url
        return UrlGenerator('{}/{}'.format(self.url, item))


url_gen = UrlGenerator('http://www.baidu.com')

print(url_gen.map.site.get)
print(url_gen.map.site.post)

# http://www.baidu.com/map/site
# http://www.baidu.com/map/site

7、谈谈 Python 中的多继承,了解些什么?

Python支持多继承。通常使用Mixin方式来实现多继承。

说到Python中的多重继承,就要说一下Python中的 MRO[Method Resolve Order] 方法解析顺序。

class Base:
    def __init__(self):
        print('Base.__init__')


class A(Base):
    def __init__(self):
        super().__init__()
        print('A.__init__')


class B(Base):
    def __init__(self):
        super().__init__()
        print('B.__init__')


class C(A, B):
    def __init__(self):
        super().__init__()  # Only one call to super() here
        print('C.__init__')


c = C()
print(C.__mro__)
print(C.mro())
# Base.__init__
# B.__init__
# A.__init__
# C.__init__
# (<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class '__main__.Base'>, <class 'object'>)
# (<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class '__main__.Base'>, <class 'object'>)

Python的多重继承,其中最需要注意的是方法解析顺序。其中,理解super的使用(原理)是非常重要的。

super

super figures out which is the next class in the Method Resolution Order(MRO). The two arguments you pass in are what lets it figure that out - self gives it the entire MRO via an attribute; the current class tells it where you are along the MRO right now. So what super is actually doing is basically:

def super(cls, inst):
    mro = inst.__class__.__mro__
    return mro[mro.indexof(cls) + 1]

super 用来找出在方法解析列表中的下一个类。

C.__mro__ == Class.mro()
# True

refer python-super-inheritance-and-needed-arguments

refer 多重继承

8、asyncio 是干什么的?

asyncio是Python 3.4版本引入的标准库,直接内置了对异步IO的支持。

asyncio的编程模型就是一个消息循环。我们从asyncio模块中直接获取一个EventLoop的引用,然后把需要执行的协程扔到EventLoop中执行,就实现了异步IO

那么 asyncio 做为一个库,做了什么,没做什么?

  • 控制流的暂停与恢复,这是通过 Python 内部的 Generator(生成器)相关的功能实现的。
  • 协程链,即把不同协程链链接在一起的机制。依旧是通过 Python 的内置支持,即 async/await,或者说是生成器的 yield from。
  • Event Loop,这个是 asyncio 实现的。它决定了我们能对什么事件进行异步操作,目前只支持定时器与网络 IO 的异步。
  • 协程链的控制流恢复,即内部的协程暂停了,恢复时却需要从最外层的协程开始恢复。这是 asyncio 实现的内容。
  • 其它的库支持,这里指的是像 asyncio.sleep() 这种协程链的最内层的协程,因此我们一般不希望自己去调用 event loop 注册/注销事件。

refer understand-python-asyncio

refer asyncio

9、在 json 转换的时候,json.dump() 不支持哪些类型的数据结构转换?

int. 整形.

json的定义就不支持int类型。因为JavaScript中没有int类型,但是有number类型。

ujson 是一个速度更快的Python Json实现。

Why do int keys of a python dict turn into strings when using json.dumps?

10、你用 flask 框架时,session用的是自带的吗?session底层用的是什么数据结构存储的?

Object

Is Flask's session object a dict or an object?

11、flask 框架从请求到响应是怎么一个流程

12、flask 框架有什么特点?它与 Django 相比有些什么不同之处?

####### flask

  • 微框架,以插件的形式支持扩展,有完整的生态系统
  • 良好的文档
  • 丰富的插件
  • 包含开发服务器和调试器(debugger)
  • 集成支持单元测试
  • RESTful请求调度
  • 支持安全cookies
  • 基于Unicode
Django
  • 大而全, batteries included, Django已经内建了模板、表单、路由、认证、基本数据库管理.
  • DRY(Don't Repeat Yourself)
  • 采用MTV(Model-Template-View)模型组织
  • 强大的数据库功能:用python的类继承,几行代码就可以拥有一个丰富,动态的数据库操作接口(API),如果需要你也能执行SQL语句
  • 强大的后台功能:几行简单的代码就让你的网站拥有一个强大的后台,轻松管理你的内容
  • 优雅的网址:用正则匹配网址,传递到对应函数,随意定义
  • 模板系统:强大,易扩展的模板系统,设计简易,代码,样式分开设计,更容易管理
  • 缓存系统:与memcached或其它的缓存系统联用,更出色的表现,更快的加载速度
  • 国际化:完全支持多语言应用,允许你定义翻译的字符,轻松翻译成不同国家的语言

13、你在用 flask 时,连接数据库用的是 ORM 还是原生 SQL ,如果是 ORM ,是用的什么库?

14、Redis 和 MySQL 分别是什么作用?各适用于什么样的场景?

redis是内存键值对非关系型数据库。

三个要点:

  • 非关系型(nosql)
  • 键值对(key-val)
  • 基于内存(也支持持久化)

另外几个点:

  • 常用来存储一个网站的热点数据
  • 断电后数据会丢失,但是可以设置备份策略,用来备份热点数据

redis支持的5种数据结构:

  • String 字符串, 整数, 浮点数

  • List 链表,可视为一个双端队列, 并且支持切片操作

  • Set 无序集合, 支持集合的并, 交, 或操作

  • ZSet 有序集合,

  • Hash 散列表

  • MySQL MySQL Mysql 是磁盘关系型数据库。

15、什么是 wsgi,它和 uwsgi 和 gunicorn 有什么关系?

wsgi 是一个规范, Web Server gateway interface. 通用网关接口。 Web服务器网关接口

refer 深入理解uwsgi和gunicorn网络模型[上]

16、你了解 SQL 优化吗?

refer 数据库SQL优化大总结之 百万级数据库优化方案

17、你了解 MySQL 索引吗?

索引分单列索引和组合索引。单列索引,即一个索引只包含单个列,一个表可以有多个单列索引,但这不是组合索引。组合索引,即一个索引包含多个列。

MySQL索引类型

(1) 主键索引 PRIMARY KEY

它是一种特殊的唯一索引,不允许有空值。一般是在建表的时候同时创建主键索引。

当然也可以用 ALTER 命令。记住:一个表只能有一个主键。

(2) 唯一索引 UNIQUE

唯一索引列的值必须唯一,但允许有空值。如果是组合索引,则列值的组合必须唯一。可以在创建表的时候指定,也可以修改表结构,如:

ALTER TABLE table_name ADD UNIQUE (column)

(3) 普通索引 INDEX

这是最基本的索引,它没有任何限制。可以在创建表的时候指定,也可以修改表结构,如:

ALTER TABLE table_name ADD INDEX index_name (column)

(4) 组合索引 INDEX

组合索引,即一个索引包含多个列。可以在创建表的时候指定,也可以修改表结构,如:

ALTER TABLE table_name ADD INDEX index_name(column1, column2, column3)

(5) 全文索引 FULLTEXT

全文索引(也称全文检索)是目前搜索引擎使用的一种关键技术。它能够利用分词技术等多种算法智能分析出文本文字中关键字词的频率及重要性,然后按照一定的算法规则智能地筛选出我们想要的搜索结果。

可以在创建表的时候指定,也可以修改表结构,如:

ALTER TABLE table_name ADD FULLTEXT (column)

refer MySQL 索引及查询优化总结

@lttzzlll
Copy link
Author

整理到此为止,具体的地方再总结。

@lttzzlll
Copy link
Author

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment