深信服 Python 面经

1,058 阅读10分钟

Python相关

python里面的进程、线程、协程特点、手动切换协程

进程是资源分配的单位
线程是操作系统调度的单位
协程,又称微线程,纤程,协程的切换只是单纯的操作CPU的上下文,资源很小,效率高;想要使用协程,那么我们的任务必须有等待(例子:拉面馆下单)

线程执行开销小,但不利于资源的管理和保护;而进程正相反

统计各个函数执行的时间,python装饰器

python装饰器就是用于拓展原来函数功能的一种函数
# 带有不定参数的装饰器
import time
def deco(func):
    def wrapper(*args, **kwargs):
        startTime = time.time()
        func(*args, **kwargs)
        endTime = time.time()
        res = (endTime - startTime) * 1000
        print("time is %d ms" %res)
     return wrapper

@deco
def func1(a, b):
    print(a, b)
    
@deco
def func2(a, b, c):
    print(a, b, c)

if __name__ == '__main__':
    func2(3, 4, 5)
    func1(1, 2)

深拷贝、浅拷贝

浅拷贝:拷贝了引用,并没有拷贝内容
    copy.copy对于可变类型,会进行浅拷贝
    copy.copy对于不可变类型,不会拷贝,仅仅是指向

深拷贝:对于一个对象所有层次的拷贝(递归)

内存管理(循环引用怎么解决)

python内部使用引用计数,来保持追踪内存中的对象,Python内部记录了对象有多少个引用,即引用计数,当对象被创建时就创建了一个引用计数,当对象不再需要时,这个对象的引用计数为0时,它被垃圾回收。

python 协程

在Python中,这种一边循环一边计算的机制,就称为生成器(Generator)。
如果一个函数中包含yield关键字,那么这个函数就不再是一个普通函数,而是一个generator。
线程是系统级别的,它们是由操作系统调度;协程是程序级别的,由程序员根据需要自己调度。

io多路复用

1.select/poll
老李去火车站买票,委托黄牛,然后每隔6小时电话黄牛询问,黄牛三天内买到票,然后老李去火车站交钱领票。
耗费:打电话
2.epoll
老李去火车站买票,委托黄牛,黄牛买到后即通知老李去领,然后老李去火车站交钱领票。
耗费:无需打电话

openStack

OpenStack为私有云和公有云提供可扩展的弹性的云计算服务,这种服务云必须是简单部署并且扩展性强。
1、模块松耦合
2、组件配置较为灵活
3、二次开发容易(必须学会python)

单例模式(单例的缺点)

django 登录认证(登录后后台会发生什么)

为什么进入页面,登录状态还在?

#验证是否登录的装饰器
def check_user(func):
    def inner(*args, **kwargs):
        #判断是否登录
        username = args[0].session.get("login_user", "")
        if username == "":
            #保存当前的url到session中
            args[0].session["path"] = args[0].path
            #重定向到登录页面
            return redirect(reverse("user:login"))
        return func(*args, **kwargs)

    return inner

Python多线程

开发Python多线程程序时,主要涉及到3个模块,分别是thread,threading和Queue。

thread模块提供了基本的线程和锁定支持;而threading模块提供了更高级别、功能更全面的线程管理。
应该避免使用thread模块,尽量使用threading模块,原因如下:
(1)threading模块更加先进,有更好的线程支持,并且thread模块中的一些属性会和threading模块有冲突.
(2)thread模块拥有的同步原语很少,而threading模块有很多。
(3)thread模块对于进程何时退出没有控制。当主线程结束时,所有其他线程也都强制结束,不会发出警告或者进行适当的清理。

sizeof 和strlen的区别

strlen计算字符串的长度,以'\0'为字符串结束标志

sizeof是分配的数组实际所占的内存空间大小,不受里面存储内容

垃圾回收机制

引用计数原理: 当数据的引用数变成0的时候,python解释器就认为这个数据是垃圾,进行垃圾回收,释放空间.引用计数机制缺点:
1、维护引用计数需要消耗一定的资源
2、循环应用时,无法回收

标记-清理: 死亡容器、存活容器。
问题:是对所有对象都同时执行吗?

分代回收
1、新创建的对象做为0代
2、每执行一个【标记-删除】,存活的对象代数就+1
3、代数越高的对象(存活越持久的对象),进行【标记-删除】的时间间隔就越长

算法

一个很大的文件,存放英语单词,怎么读取并计算有哪些单词,每个单词的数目

手撸代码(给个日期字符串,计算是这一年的第几天)

英文论文中找前10个出现次数最多的字母

# dict
i=['a','b','c','d']
l=[1,2,3]
# 转换字典
dic = dict(zip(i,l))  # {'a': 1, 'b': 2, 'c': 3}

# 查询
dic.get('a')  # 1

dict.has_key(key)
如果键在字典dict里返回true,否则返回false

dict.update(dict2)
把字典dict2的键/值对更新到dict里

数据结构

排序算法

计算快排花了多少时间,不嵌入代码(暗示装饰器)

# 带有不定参数的装饰器
import time
def deco(func):
    def wrapper(*args, **kwargs):
        startTime = time.time()
        func(*args, **kwargs)
        endTime = time.time()
        res = (endTime - startTime) * 1000
        print("time is %d ms" %res)
     return wrapper

hash怎么实现的,如何解决哈希冲突?

常用HASH函数
散列函数能使对一个数据序列的访问过程更加迅速有效,通过散列函数,数据元素将被更快地定位。常用Hash函数有:
1.直接寻址法。取关键字或关键字的某个线性函数值为散列地址。即H(key)=key或H(key) = a·key + b,其中a和b为常数(这种散列函数叫做自身函数)
2. 数字分析法。分析一组数据,比如一组员工的出生年月日,这时我们发现出生年月日的前几位数字大体相同,这样的话,出现冲突的几率就会很大,但是我们发现年月日的后几位表示月份和具体日期的数字差别很大,如果用后面的数字来构成散列地址,则冲突的几率会明显降低。因此数字分析法就是找出数字的规律,尽可能利用这些数据来构造冲突几率较低的散列地址。
3. 平方取中法。取关键字平方后的中间几位作为散列地址。
4. 折叠法。将关键字分割成位数相同的几部分,最后一部分位数可以不同,然后取这几部分的叠加和(去除进位)作为散列地址。
5. 随机数法。选择一随机函数,取关键字作为随机函数的种子生成随机值作为散列地址,通常用于关键字长度不同的场合。
6. 除留余数法。取关键字被某个不大于散列表表长m的数p除后所得的余数为散列地址。即 H(key) = key MOD p,p<=m。不仅可以对关键字直接取模,也可在折叠、平方取中等运算之后取模。对p的选择很重要,一般取素数或m,若p选的不好,容易产生碰撞。

解决冲突的方法:
一、闭散列法(开地址法):
    1、线性探测法
    2、二次探测法
二、开散列法(链地址法)

什么情况使用链表、什么时候使用数组

数组一般就制定大小了,做查询的时候比较好。
链表的话做插入删除操作比较方便。

找子字符串

# KMP
def KMP_algorithm(string, substring):
    '''
    KMP字符串匹配的主函数
    若存在字串返回字串在字符串中开始的位置下标,或者返回-1
    '''
    pnext = gen_pnext(substring)
    n = len(string)
    m = len(substring)
    i, j = 0, 0
    while (i < n) and (j < m):
        if string[i] == substring[j]:
            i += 1
            j += 1
        elif j != 0:
            j = pnext[j - 1]
        else:
            i += 1
    if j == m:
        return i - j
    else:
        return -1


def gen_pnext(substring):
    """
    构造临时数组pnext
    """
    index, m = 0, len(substring)
    pnext = [0] * m
    i = 1
    while i < m:
        if substring[i] == substring[index]:
            pnext[i] = index + 1
            index += 1
            i += 1
        elif index != 0:
            index = pnext[index - 1]
        else:
            # pnext[i] = 0
            i += 1
    return pnext


if __name__ == "__main__":
    string = 'abcxabcdabcdabcy'
    # substring = 'abcdabcy'
    substring = 'aabbaacc'
    out = KMP_algorithm(string, substring)
    print(out)

动态规划

大根堆、小根堆。小根堆的先序序列

数据库

update最简单的语句

UPDATE user
SET user.password = '123456'
WHERE user.name = 'Leo'

sqlalchemy,一种orm框架,常用于flask

SQLAlchemy: 对象关系映射器(ORM, Object Relational Mapper)提供了一种方法,用于将用户定义的Python类与数据库表相关联,并将这些类(对象)的实例与其对应表中的行相关联。

mysql集群

计算机网络

在linux下分层,后来改成标准的分层(七层模型)

应用层
    网络服务与最终用户的一个接口。
    协议有:HTTP FTP TFTP SMTP SNMP DNS TELNET HTTPS POP3 DHCP
表示层
    数据的表示、安全、压缩。(在五层模型里面已经合并到了应用层)
    格式有,JPEG、ASCll、DECOIC、加密格式等
会话层
    建立、管理、终止会话。(在五层模型里面已经合并到了应用层)
    对应主机进程,指本地主机与远程主机正在进行的会话
传输层
    定义传输数据的协议端口号,以及流控和差错校验。
    协议有:TCP UDP,数据包一旦离开网卡即进入网络传输层
网络层
    进行逻辑地址寻址,实现不同网络之间的路径选择。
    协议有:ICMP IGMP IP(IPV4 IPV6) ARP RARP
    ICMP报文是一种差错控制协议
    IGMP是管理组成员关系的协议
数据链路层
    建立逻辑连接、进行硬件地址寻址、差错校验 [2]  等功能。(由底层网络定义协议)
    将比特组合成字节进而组合成帧,用MAC地址访问介质,错误发现但不能纠正。
物理层
    建立、维护、断开物理连接。(由底层网络定义协议)

mac地址在哪层、ip地址在哪层、arp协议干嘛(为什么要有arp?)

IP是网络层,MAC是链路层,ARP协议的作用是在局域网内通过IP地址来查询MAC地址,工作流程是在局域网内广播数据包,其他主机接收到数据包后,将其目的IP地址与自身IP地址比较,若相同则返回MAC地址,也就是说仅通过IP地址即可确定目的主机。

tcp三次握手,为什么不两次或者四次

三次握手的目的:是为了确认双方都有收发数据的能力。
第一次: A->B,证明A有发消息的能力。
第二次: ->B && B->A,证明B有收消息,并且有发消息的能力。
第三次: A->B,证明A有收消息的能力。
二次握手达不到目的,四次多余。

TCP滑动窗口

  1. 已发送已确认数据流中最早的字节已经发送并得到确认。这些数据是站在发送设备的角度来看的。如下图所示,31个字节已经发送并确认。
  2. 已发送但尚未确认 已发送但尚未得到确认的字节。发送方在确认之前,不认为这些数据已经被处理。下图所示14字节为第2类。
  3. 未发送而接收方已Ready 设备尚未将数据发出 ,但接收方根据最近一次关于发送方一次要发送多少字节确认自己有足够空间。发送方会立即尝试发送。如图,第3类有6字节。
  4. 未发送而接收方Not Ready 由于接收方not ready,还不允许将这部分数据发出。

操作系统

linuxs命令,查看进程命令

ps命令

-a,查看所有

-u,以用户(user)的格式显示

-x, 显示后台进程运行参数

-ef,以全格式显示进程所有信息,包括父进程Pid,创建人,创建时间,进程号。等等

kill - 9 表示强制杀死该进程;而 kill 则有局限性,例如后台进程,守护进程等;