python面试01 -- 面试

简介

本文内容主要对近期面试问题和答案进行总结并记录注意的地方,只供参考,无其他实际用途

PEP8规范

变量

常量:大写加下划线 USER_CONSTANT

私有变量:小写加一个前导下划线 _private_value (通俗约定,python中并不存在,需要保护的变量会使用)

内置变量:__class__ (为避免内置变量和其他变量产生冲突,两个前导下划线会导致变量在解释期间被更名 python的名称改编特性)

函数和方法 (通用小写和下划线)

私有方法:小写和一个前导下划线 (与私有变量一样)_func

特殊方法:小写和两个前导下划线,两个后置下划线 __str__ __cmp__ (只用于特殊函数)

函数参数:小写和下划线,缺省值等号两边无空格 user_number=1

驼峰格式命名,所有单词首字母大写其余小写 通常采用其类型或者特性的后缀 SQLEngine MimeTypes

基类:使用Base 或者Abstract 前缀 BaseCookie AbstractGroup

模块和包

特殊模块:__init__

实现一个协议,通常使用lib为后缀 import smtplib

其他:不带下划线的小写字母

参数

不使用断言实现静态类型检测 python是动态类型语言,断言应该被用于避免函数不被毫无意义的调用

不随意使用*args **kwargs,其参数可能会破坏函数的健壮性。会使签名变得模糊,会使其在不应该的地方构建小的参数解析器

其他

布尔元素:使用has或者is前缀 is_connect = True

序列: 复数形式 numbers = ['user_1', 'user_2']

字典: 显式名称 person_address = {'user_2':'20 street huafu'}.

避免出现现有名称再次使用 如os sys

一些数字

一行列数: 最高79列 满屏编辑器的显示列数

一个函数:不超过30行代码,即不使用游标可看到整个函数代码

一个类:不超过200行,不超过10个方法,一个模块不超过500行

验证脚本

安装一个pep8脚本安装

Linux基础和git

linux命令

1.区分文件和文件夹: ls -F : 显示名称的时候会在文件夹后面加’/‘, 文件后面加’*’

2.日志存放路径:以文本存放在’var/log/‘文件夹下,后缀为.log

3.查看服务器端口:netstat -anp | grep service_name

4.ubuntu设置开机自启动程序

查看某一服务是否开机自启动:chkconfig –list|服务名

单独开启/关闭某一服务:chkconfig 服务吗 on/off

查看服务状态:service 服务名 status

5.find/grep区别

find: 根据文件属性进行查找,如文件名,文件大小,所有者,是否为空,访问时间,修改时间
find 路径 -name filename[可使用tong p]
find / [-文件特征:amin atime empty group user]
grep:根据文件的内容进行查找,会对文件的每一行按照给定的模式进行匹配查找
grep 'test' d* :显示所有以d开头文件中包含test的行

6.重定向

>:Linux允许将命令执行结果重定向到一个文件,本应该显示在终端上的内容保存到指定文件中,若文件不存在会创建一个,存在则覆盖其内容。 例如:ls > test.txt

>>:将输出内容追加到目标文件中。文件不存在则创建,存在则将内容追加到文件末尾,原文件内容不受影响

7.软连接和硬链接

软连接相当于快捷方式,硬连接可理解为源文件的一个别名,可以存在多个别名,当rm一个文件的时候,此文件的硬连接数减1,为0时文件被删除

8.10个常用Linux命令

pwd     显示工作路径
ls         查看目录中的文件
cd filename/        进入filename目录
cd ..     回到上级目录
cd ../..         回到上两级目录
mkdir dir1    创建一个名叫dir1的目录
rm -f file1    删除file1文件, -f 忽略不存在的文件,不给出提示
rmdir dir1    删除dir1目录
groupadd group_name     创建一个新用户组
groupdel group_name    删除一个用户组
tar -cvf archive.tar file1     创建一个非压缩的tar
tar -cvf archive.tar file1 file2 dir1         创建一个包含file1 file2 dir1的档案文件
tar -tf archive.tar    显示一个包中内容
tar -xvf archive.tar        释放一个包
tar -xvf archive.tar -C /tmp        将压缩包释放到tmp/目录下

9.Linux关机命令

命令 含义
reboot 重新启动操作系统
shutdown -r now 重新启动,shutdown会给其他用户提示
shutdown -h now/20:24 立刻关机/在20:24关机
shutdown -h +10 系统在10分钟后自动关机
init 0 关机
init 6 重启

git冲突

git合并文件出现冲突解决办法:

  1. git merge 冲突,根据提示找到冲突文件,解决冲突,文件存在冲突会有提示
  2. 修改完成后,git add 冲突文件名
  3. git commit 注意:没有 -m 选项 进去后类似于vim编辑器,把confict相关的行删除,直接push就可以了

数据类型

字典

dict 字典:字典是一组键(key)值(value)对。通过key查找,没有顺序,使用{}
主要应用于使用键和值进行关联的数据

  • 1.1 现有字典 d={‘a’:24, ‘g’:52, ‘i’: 12, ‘k’:33} 按照字典的value进行排序

    sorted(d.items(), key=lamada x:x[1])

    sorted(iterable, cmp=None, key=None, reverse=False)

    参数说明:

    * iterable -- 可迭代对象。

    * cmp -- 比较的函数,这个具有两个参数,参数的值都是从可迭代对象中取出,此函数必须遵守的规则 为,大于则返回1,小于则返回-1,等于则返回0

    * key -- 主要是用来进行比较的元素,只有一个参数,具体的函数的参数就是取自于可迭代对象中,
    指定可迭代对象中的一个元素来进行排序。

    * reverse -- 排序规则,reverse = True 降序 , reverse = False 升序(默认)。
  • 1.2 字典和json的区别

    字典是一种数据结构,json是一种数据的表现形式,字典中的key只要能hash就行,而json必须是字符串

  • 1.3 可变类型和不可变类型

    可变与不可变是指内存中的值是否可以被改变,不可变类型指的是对象所在内存块中的值不可以改变,有数值、字符串、元祖;可变类型则是可以改变,主要有字典、列表

  • 1.4 存入字典中的数据有没有先后顺序

    存入的数据不会自动排序,可以使用sort函数对字典进行排序

  • 1.5 字典推导式 {}

    d = {key:value for (key, value) in iterable}

    d = {key: value for (key, value) in {'a': 1, 'b': 2}.items()}

    # 将同一个字母大小写对应的值相加
    mcase = {'a': 10, 'b': 34, 'A': 7, 'B': 3}

    mcase_frequency = {
    k.lower(): mcase.get(k.lower(), 0) + mcase.get(k.upper(), 0)
    for k in mcase.keys()
    if k.lower() in ['a', 'b']
    }

    # 交换key和value

    mcase_frequency1 = {v: k for (k, v) in mcase.items()}
  • 1.6 列表推导式 []

    variable = [out_exp_res for out_exp in input_list if out_exp == 2]

    multiples = [i for i in range(30) if i % 3 is 0]
    def func(x):
    return x**2
    multiples1 = [func(i) for i in range(10) if i % 2 == 0]
  • 1.7 生成器generator采用()修改列表推导式即可

    multiples = (i for i in range(30) if i % 3 is 0)

  • 1.8 集合推导式 与列表推导式差不多 采用 { }

    squared = {x**2 for x in [1, 2, 2]}

字符串

  • 2.1 python 字符串中的\有三种含义:转移字符、连接路径名、太长代码换行编写

  • 2.2 反转字符串: print('astart' [::-1])

  • 2.3 将一个字符串处理成一个字典

    def str1dict(str1):
    dict1 = {}
    for iterms in str1.split('|'):
    key, value = iterms.split(':')
    dict1[key] = value
    return dict1


    str1 = "k:1|k1:2|k2:3"
  • 2.4 将alist中元素的age由大到小排序

    alist = [{‘name’: ‘a’, ‘age’: 20}, {‘name’: ‘b’, ‘age’: 24}, {name’: ‘c’, ‘age’: 19}]
    sorted(alist, key= lambda x:x[‘age’], reverse = True)

  • 2.5 lambda 匿名函数 lambda 函数可以接收任意多个参数 (包括可选参数) 并且返回单个表达式的值。

    lambda匿名函数的格式:冒号前是参数,可以有多个,用逗号隔开,冒号右边的为表达式。其实lambda返回值是一个函数的地址,也就是函数对象。

列表

  • 3.1 列表取值超过索引范围时,程序会产生异常 indexError:list index out of range

  • 3.2 列表常用操作

    增加

    列表名.insert(index, 数据): 指定位置插入数据,index超过索引会补位

    列表名.append(数据): 在列表的末尾增加数据

    列表.extend(iterable): 将可迭代对象追加到列表

    取值和修改:根据下标取值和修改

    删除

    del 列表名[index]:删除指定索引的数据

    列表名.remove(数据):删除第一次出现的指定数据

    列表名.pop():删除末尾数据,返回被删除的元素

    列表名.pop(index):删除指定索引的数据,返回被删除的元素

    列表名.clear():清空整个列表的元素

    排序

    列表名.sort():升序排序,从小到大

    列表名.sort(reverse=True):降序排序,从大到小

    列表名.reverse():列表逆序,反转

    统计相关

    len()、列表名.count(数据)、列表名.index(数据)–数据首次出现的索引,无则报错

    循环遍历 while for

  • 3.3 a = [1,2,3,4,5,3,2,1] print(list[40:]) 不会报错indexError,将输出[]

  • 3.4 列表生成式,生成一个公差为11的列表 [x*11 for x in range(10)]

  • 3.5 有两个列表找出其中相同和不同的元素(集合处理)

    list1 = [1,2,3,4,5]
    list2 = [2,3,41,,2]
    set1 = set(list1)
    set2 = set(list2)
    set1&set2
    set1^set2
  • 3.5 分析代码性能 使用cProfile.run(‘函数名’)

元祖

  • 元祖将多样的对象集合在一起,不能修改,通过索引进行查找,使用(),应用于把一些数据当作一个整体去使用,不能修改

集合

  • 5.1 集合与列表、元祖类似,可以存储多个数据,但是这些数据不重复。集合支持union(联合) intersection(交) difference sysmmetric_difference(对称差集)等数学运算
  • 5.2 应用:快速去除列表中的重复元素,交集&、并集|、差集-(另一个集合中没有的)、对称差集^(在A或者B中,但不会同时出现在二者中)

python 高级

元类

  • Python中类方法、类实例方法、静态方法有何区别?

    类方法:是类对象的方法,在定义时需要在上方使用“@classmethod”进行装饰,形参为 cls,表示类对象,类对象和实例对象都可调用;

    类实例方法:是类实例化对象的方法,只有实例对象可以调用,形参为self,指代对象本身;

    静态方法:是一个任意函数,在其上方使用“@staticmethod”进行装饰,可以用对象直接调用,静态方法实际上跟该类没有太大关系。

  • Python中如何动态获取和设置对象的属性

    if hasattr(Parent,'x'):
    print(getattr(Parent,'x'))
    setattr(Parent,'x'3)
    print(getattr(Parent,'x'))

内存管理与垃圾回收机制

  • Python的内存管理机制及调优手段?

    内存管理机制:引用计数、垃圾回收、内存池。

  • 引用计数:

    引用计数是一种非常高效的内存管理手段, 当一个 Python 对象被引用时其引用计数增加1, 当其不再被一个变量引用时则计数减 1. 当引用计数等于0时对象被删除。

  • 垃圾回收 :

    引用计数

    引用计数也是一种垃圾收集机制,而且也是一种最直观,最简单的垃圾收集技术。当 Python 的某个对象的引用计数降为 0 时,说明没有任何引用指向该对象,该对象就成为要被回收的垃圾了。比如某个新建对象,它被分配给某个引用,对象的引用计数变为 1。如果引用被删除,对象的引用计数为 0,那么该对象就可以被垃圾回收。不过如果出现循环引用的话,引用计数机制就不再起有效的作用了

    标记清除

    如果两个对象的引用计数都为 1,但是仅仅存在他们之间的循环引用,那么这两个对象都是需要被回收的,也就是说,它们的引用计数虽然表现为非 0,但实际上有效的引用计数为 0。所以先将循环引用摘掉,就会得出这两个对象的有效计数。

    分代回收

    从前面“标记-清除”这样的垃圾收集机制来看,这种垃圾收集机制所带来的额外操作实际上与系统中总的内存块的数量是相关的,当需要回收的内存块越多时,垃圾检测带来的额外操作就越多,而垃圾回收带来的额外操作就越少;反之,当需回收的内存块越少时,垃圾检测就将比垃圾回收带来更少的额外操作。

    举个例子:
    当某些内存块 M 经过了 3 次垃圾收集的清洗之后还存活时,我们就将内存块 M 划到一个集合 A 中去,而新分配的内存都划分到集合 B 中去。当垃圾收集开始工作时,大多数情况都只对集合 B 进行垃圾回收,而对集合 A 进行垃圾回收要隔相当长一段时间后才进行,这就使得垃圾收集机制需要处理的内存少了,效率自然就提高了。在这个过程中,集合 B 中的某些内存块由于存活时间长而会被转移到集合 A 中,当然,集合 A 中实际上也存在一些垃圾,这些垃圾的回收会因为这种分代的机制而被延迟。

  • 内存池

    Python 的内存机制呈现金字塔形状,-1,-2 层主要有操作系统进行操作;

    第 0 层是 C 中的 malloc,free 等内存分配和释放函数进行操作;

    第1 层和第 2 层是内存池,有 Python 的接口函数 PyMem_Malloc 函数实现,当对象小于 256K 时有该层直接分配内存;

    第3层是最上层,也就是我们对 Python 对象的直接操作;

    Python 在运行期间会大量地执行 malloc 和 free 的操作,频繁地在用户态和核心态之间进行切换,这将严重影响 Python 的执行效率。为了加速Python 的执行效率,Python 引入了一个内存池机制,用于管理对小块内存的申请和释放。

    Python 内部默认的小块内存与大块内存的分界点定在 256 个字节,当申请的内存小于 256 字节时,PyObject_Malloc会在内存池中申请内存;当申请的内存大于 256 字节时,PyObject_Malloc 的行为将蜕化为 malloc 的行为。当然,通过修改 Python 源代码,我们可以改变这个默认值,从而改变 Python 的默认内存管理行为。

  • 调优手段(了解)

    1.手动垃圾回收

    2.调高垃圾回收阈值

    3.避免循环引用(手动解循环引用和使用弱引用)

  • 内存泄露是什么?如何避免?

    指由于疏忽或错误造成程序未能释放已经不再使用的内存的情况。内存泄漏并非指内存在物理上的消失,而是应用程序分配某段内存后,由于设计错误,失去了对该段内存的控制,因而造成了内存的浪费。导致程序运行速度减慢甚至系统崩溃等严重后果。
    del() 函数的对象间的循环引用是导致内存泄漏的主凶。

    不使用一个对象时使用:del object 来删除一个对象的引用计数就可以有效防止内存泄漏问题。

    通过Python 扩展模块 gc 来查看不能回收的对象的详细信息。

    可以通过 sys.getrefcount(obj) 来获取对象的引用计数,并根据返回值是否为 0 来判断是否内存泄漏。

函数

函数参数

  • Python函数调用的时候参数的传递方式是值传递还是引用传递

    Python的参数传递有:位置参数、默认参数、可变参数、关键字参数。

    函数的传值到底是值传递还是引用传递,要分情况:

    不可变参数用值传递:

    像整数和字符串这样的不可变对象,是通过拷贝进行传递的,因为你无论如何都不可能在原处改变不可变对象

    可变参数是引用传递的:

    比如像列表,字典这样的对象是通过引用传递、和C语言里面的用指针传递数组很相似,可变对象能在函数内部改变。

  • 对缺省参数的理解

    缺省参数指在调用函数的时候没有传入参数的情况下,调用默认的参数,在调用函数的同时赋值时,所传入的参数会替代默认参数。

    *args 是不定长参数,他可以表示输入参数是不确定的,可以是任意多个。

    **kwargs 是关键字参数,赋值的时候是以键 = 值的方式,参数是可以任意多对在定义函数的时候不确定会有多少参数会传入时,就可以使用两个参数。

  • 为什么函数名字可以当做参数用

    Python中一切皆对象,函数名是函数在内存中的空间,也是一个对象。

  • Python中pass语句的作用是什么?

    在编写代码时只写框架思路,具体实现还未编写就可以用 pass 进行占位,使程序不报错,不会进行任何操作。

  • 有这样一段代码,print c会输出什么,为什么

    a = 10
    b = 20
    c = [a]
    a = 15

    10对于字符串、数字,传递是相应的值。

内建函数

  • map函数和reduce函数?

    ①从参数方面来讲:
    map()包含两个参数,第一个参数是一个函数,第二个是序列(列表 或元组)。其中,函数(即 map 的第一个参数位置的函数)可以接收一个或多个参数。
    reduce()第一个参数是函数,第二个是序列(列表或元组)。但是,其函数必须接收两个参数。

    ②从对传进去的数值作用来讲:
    map()是将传入的函数依次作用到序列的每个元素,每个元素都是独自被函数“作用”一次 。
    reduce()是将传人的函数作用在序列的第一个元素得到结果后,把这个结果继续与下一个元素作用(累积计算)。

  • 递归函数停止的条件?

    递归的终止条件一般定义在递归函数内部,在递归调用前要做一个条件判断,根据判断的结果选择是继续调用自身,还是 return;返回终止递归。
    终止的条件:

    判断递归的次数是否达到某一限定值

判断运算的结果是否达到某个范围等,根据设计的目的来选择

  • 回调函数,如何通信的?

    回调函数是把函数的指针(地址)作为参数传递给另一个函数,将整个函数当作一个对象,赋值给调用的函数。

  • Python主要的内置数据类型都有哪些? print dir( ‘a ’) 的输出

    内建类型:布尔类型、数字、字符串、列表、元组、字典、集合;

    输出字符串‘a’的内建方法;

  • print(list(map(lambda x: x * x, [y for y in range(3)])))的输出?

    [0, 1, 4]

  • hasattr() getattr() setattr() 函数使用详解?(2018-4-16-lxy)

    hasattr(object, name)函数:

    判断一个对象里面是否有name属性或者name方法,返回bool值,有name属性(方法)返回True,否则返回False。注意:name要使用引号括起来。

    class function_demo(object):
    name = 'demo'
    def run(self):
    return "hello function"
    functiondemo = function_demo()
    res = hasattr(functiondemo, 'name') #判断对象是否有name属性,True
    res = hasattr(functiondemo, "run") #判断对象是否有run方法,True
    res = hasattr(functiondemo, "age") #判断对象是否有age属性,Falsw
    print(res)

    getattr(object, name[,default]) 函数:

    获取对象object的属性或者方法,如果存在则打印出来,如果不存在,打印默认值,默认值可选。注意:如果返回的是对象的方法,则打印结果是:方法的内存地址,如果需要运行这个方法,可以在后面添加括号()。

    functiondemo = function_demo()
    getattr(functiondemo, 'name')
    #获取name属性,存在就打印出来--- demo
    getattr(functiondemo, "run")
    #获取run方法,存在打印出 方法的内存地址---

    n_demo object at 0x10244f320>>
    getattr(functiondemo, "age")
    #获取不存在的属性,报错如下:
    Traceback (most recent call last):
    File "/Users/liuhuiling/Desktop/MT_code/OpAPIDemo/
    conf/OPCommUtil.py", line 39, in
    res = getattr(functiondemo, "age")
    AttributeError: 'function_demo' object has no attribute 'age'
    getattr(functiondemo, "age", 18)
    #获取不存在的属性,返回一个默认值

    setattr(object,name,values)函数:

    给对象的属性赋值,若属性不存在,先创建再赋值

    class function_demo(object):
    name = 'demo'
    def run(self):
    return "hello function"
    functiondemo = function_demo()
    res = hasattr(functiondemo, 'age') # 判断age属性是否存在,False
    print(res)
    setattr(functiondemo, 'age', 18 ) #对age属性进行赋值,无返回值
    res1 = hasattr(functiondemo, 'age') #再次判断属性是否存在,True

    综合使用:

    class function_demo(object):
    name = 'demo'
    def run(self):
    return "hello function"
    functiondemo = function_demo()
    res = hasattr(functiondemo, 'addr') # 先判断是否存在if res:
    addr = getattr(functiondemo, 'addr')
    print(addr)else:
    addr = getattr(functiondemo, 'addr', setattr(functiondemo,
    'addr', '北京首都'))
    #addr = getattr(functiondemo, 'addr', '美国纽约')
    print(addr)

Lambda

  • 什么是lambda函数? 有什么好处?
    lambda 函数是一个可以接收任意多个参数(包括可选参数)并且返回单个表达式值的函数

    1、lambda 函数比较轻便,即用即仍,很适合需要完成一项功能,但是此功能只在此一处使用,连名字都很随意的情况下;

    2、匿名函数,一般用来给 filter, map 这样的函数式编程服务;

    3、作为回调函数,传递给某些应用,比如消息处理

  • 下面这段代码的输出结果将是什么?请解释。

    def multipliers():
    return [lambda x : i * x for i in range(4)]
    print [m(2for m in multipliers()]

    上面代码输出的结果是[6, 6, 6, 6] (不是我们想的[0, 2, 4, 6])。

  • 你如何修改上面的multipliers的定义产生想要的结果?

    上述问题产生的原因是Python闭包的延迟绑定。这意味着内部函数被调用时,参数的值在闭包内进行查找。因此,当任何由multipliers()返回的函数被调用时,i的值将在附近的范围进行查找。那时,不管返回的函数是否被调用,for循环已经完成,i被赋予了最终的值3。

    因此,每次返回的函数乘以传递过来的值3,因为上段代码传过来的值是2,它们最终返回的都是6。(3*2)碰巧的是,《The Hitchhiker’s Guide to Python》也指出,在与lambdas函数相关也有一个被广泛被误解的知识点,不过跟这个case不一样。由lambda表达式创造的函数没有什么特殊的地方,它其实是和def创造的函数式一样的。
    下面是解决这一问题的一些方法。

    一种解决方法就是用Python生成器。

    def multipliers():
    for i in range(4): yield lambda x : i * x

    另外一个解决方案就是创造一个闭包,利用默认函数立即绑定。

    def  multipliers():
    return [lambda x, i=i : i * x for i in range(4)]
  • 什么是lambda函数?它有什么好处?写一个匿名函数求两个数的和?

lambda 函数是匿名函数;使用 lambda 函数能创建小型匿名函数。这种函数得名于省略了用 def 声明函数的标准步骤;

f = lambda x,y:x+y
print(f(2017,2018))

设计模式

单例

  • 请手写一个单例(2018-3-30-lxy)

    class A(object):
    __instance = None
    def __new__(cls, *args, **kwargs):
    if cls.__instance is None:
    cls.__instance = object.__new__(cls)
    return cls.__instance
    else:
    return cls.__instance
  • 单例模式的应用场景有哪些?

    单例模式应用的场景一般发现在以下条件下:

    资源共享的情况下,避免由于资源操作时导致的性能或损耗等。如日志文件,应用配置。   

    控制资源的情况下,方便资源之间的互相通信。如线程池等。 1.网站的计数器 2.应用配置 3.多线程池 4.数据库配置,数据库连接池 5.应用程序的日志应用….

工厂

装饰器

  • 对装饰器的理解 ,并写出一个计时器记录方法执行性能的装饰器?

    装饰器本质上是一个 Python 函数,它可以让其他函数在不需要做任何代码变动的前提下增加额外功能,装饰器的返回值也是一个函数对象。

    import time
    def timeit(func):
      def wrapper():
         start = time.clock()
         func() end =time.clock()
         print 'used:', end - start
         return wrapper
    @timeit
    def foo():
    print 'in foo()'foo()
  • 解释一下什么是闭包?

    在函数内部再定义一个函数,并且这个函数用到了外边函数的变量,那么将这个函数以及用到的一些变量称之为闭包。

  • 函数装饰器有什么作用?

    装饰器本质上是一个Python函数,它可以在让其他函数在不需要做任何代码的变动的前提下增加额外的功能。 装饰器的返回值也是一个函数的对象,它经常用于有切面需求的场景。 比如:插入日志、性能测试、事务处理、缓存、权限的校验等场景 有了装饰器就可以抽离出大量的与函数功能本身无关的雷同代码并发并继续使用。

生成器

  • 生成器、迭代器的区别?

    迭代器是一个更抽象的概念,任何对象,如果它的类有next方法和iter方法返回自己本身,对于string、list、dict、tuple等这类容器对象,使用for循环遍历是很方便的。在后台for语句对容器对象调用iter()函数,iter()是python的内置函数。iter()会返回一个定义了next()方法的迭代器对象,它在容器中逐个访问容器内元素,next()也是python的内置函数。在没有后续元素时,next()会抛出一个StopIteration异常。

    生成器(Generator)是创建迭代器的简单而强大的工具。它们写起来就像是正规的函数,只是在需要返回数据的时候使用 yield 语句。每次 next()被调用时,生成器会返回它脱离的位置(它记忆语句最后一次执行的位置和所有的数据值)

    区别:生成器能做到迭代器能做的所有事,而且因为自动创建了iter()和 next()方法,生成器显得特别简洁,而且生成器也是高效的,使用生成器表达式取代列表解析可以同时节省内存。除了创建和保存程序状态的自动方法,当发生器终结时,还会自动抛出 StopIteration 异常。

  • X是什么类型?
    X = (for i in ramg(10))
    答:X是generator 类型。

  • 请尝试用“一行代码”实现将1-N 的整数列表以 3为单位分组,比如 1-100分组后为?

    print([[x for x in range(1,100)][i:i+3] 
    
    for i in range(0,len(list_a),3)])
  • Python中yield的用法?

    yield就是保存当前程序执行状态。你用for循环的时候,每次取一个元素的时候就会计算一次。用yield的函数叫generator,和iterator一样,它的好处是不用一次计算所有元素,而是用一次算一次,可以节省很多空间。generator每次计算需要上一次计算结果,所以用yield,否则一return,上次计算结果就没了。

    >>> def createGenerator():
    ... mylist = range(3)
    ... for i in mylist:
    ... yield i*i
    ...
    >>> mygenerator = createGenerator() # create a generator
    >>> print(mygenerator) # mygenerator is an object!
    0xb7555c34>
    >>> for i in mygenerator:
    ... print(i)
    0
    1

面向对象

  • Python中的可变对象和不可变对象?

    不可变对象,该对象所指向的内存中的值不能被改变。当改变某个变量时候,由于其所指的值不能被改变,相当于把原来的值复制一份后再改变,这会开辟一个新的地址,变量再指向这个新的地址。

    可变对象,该对象所指向的内存中的值可以被改变。变量(准确的说是引用)改变后,实际上是其所指的值直接发生改变,并没有发生复制行为,也没有开辟新的出地址,通俗点说就是原地改变。

    Python中,数值类型(int和float)、字符串str、元组tuple都是不可变类型。而列表list、字典dict、集合set是可变类型。

  • Python中is和==的区别?

    is判断的是a对象是否就是b对象,是通过id来判断的。

    ==判断的是a对象的值是否和b对象的值相等,是通过value来判断的。

  • Python的魔法方法

    魔法方法就是可以给你的类增加魔力的特殊方法,如果你的对象实现 (重载)了这些方法中的某一个,那么这个方法就会在特殊的情况下被 Python 所调用,你可以定义自己想要的行为,而这一切都是自动发生的。 它们经常是两个下划线包围来命名的(比如 __init____lt__),Python 的魔法方法是非常强大的,所以了解其使用方法也变得尤为重要!

    __init__ 构造器,当一个实例被创建的时候初始化的方法。但是它并 不是实例化调用的第一个方法。

    __new__才是实例化对象调用的第一个方法,它只取下 cls 参数,并把 其他参数传给__init____new__很少使用,但是也有它适合的场景,尤其 是当类继承自一个像元组或者字符串这样不经常改变的类型的时候。

    __call__ 允许一个类的实例像函数一样被调用 。

    __getitem__定义获取容器中指定元素的行为,相当于 self[key] 。

    __getattr__定义当用户试图访问一个不存在属性的时候的行为 。

    __setattr__ 定义当一个属性被设置的时候的行为 。

    __getattribute__定义当一个属性被访问的时候的行为 。

  • 面向对象中怎么实现只读属性?

    将对象私有化,通过共有方法提供一个读取数据的接口。

    class person:     
        def __init__(self,x):  
            self.__age = 10;  
        def age(self):  
            return self.__age;  
    t = person(22)  
    # t.__age = 100  
    print(t.age())  

    最好的方法
    class MyCls(object):  
        __weight = 50  
     
        @property  #以访问属性的方式来访问weight方法  
        def weight(self):  
            return self.__weight  
      
    if __name__ == '__main__':  
        obj = MyCls()  
        print(obj.weight)  
        obj.weight = 12  

    Traceback (most recent call last):  
    50  
      File "C:/PythonTest/test.py", line 11in   
        obj.weight = 12  
    AttributeError: can't set attribute
  • 谈谈你对面向对象的理解

    面向对象是相对于面向过程而言的。面向过程语言是一种基于功能分析的、以算法为中心的程序设计方法;而面向对象是一种基于结构分析的、以数据为中心的程序设计思想。在面向对象语言中有一个有很重要东西,叫做类。
    面向对象有三大特性:封装、继承、多态。

系统编程

进程总结

进程:程序运行在操作系统上的一个实例,就称之为进程。进程需要相应的系统资源:内存、时间片、pid。

创建进程

  • 1.首先要导入multiprocessing中的Process;

  • 2.创建一个Process对象;

  • 3.创建Process对象时,可以传递参数;

    p = Process(target=XXX, args=(元组,) , kwargs={key:value})
    target = XXX 指定的任务函数,不用加()
    args=(元组,) , kwargs={key:value} 给任务函数传递的参数
  • 4.使用start()启动进程;

  • 5.结束进程。

Process语法结构

  • Process([group [, target [, name [, args [, kwargs]]]]])

    target:如果传递了函数的引用,可以让这个子进程就执行函数中的代码

    args:给target指定的函数传递的参数,以元组的形式进行传递

    kwargs:给target指定的函数传递参数,以字典的形式进行传递

    name:给进程设定一个名字,可以省略

    group:指定进程组,大多数情况下用不到

  • Process创建的实例对象的常用方法有

    start():启动子进程实例(创建子进程)

    is_alive():判断进程子进程是否还在活着

    join(timeout):是否等待子进程执行结束,或者等待多少秒

    terminate():不管任务是否完成,立即终止子进程

  • Process创建的实例对象的常用属性

    name:当前进程的别名,默认为Process-N,N为从1开始递增的整数

    pid:当前进程的pid(进程号) ppid 父进程

  • 给子进程指定函数传递参数Demo

    import osfrom multiprocessing import Process
    import time

    def pro_func(name, age, **kwargs):
    for i in range(5):
    print("子进程正在运行中,name=%s, age=%d, pid=%d" %(name, age,
    os.getpid()))
    print(kwargs)
    time.sleep(0.2)

    if __name__ == '__main__':
    # 创建Process对象
    p = Process(target=pro_func, args=('小明',18), kwargs={'m': 20})
    # 启动进程
    p.start()
    time.sleep(1)
    # 1秒钟之后,立刻结束子进程
    p.terminate()
    p.join()

    注意:进程间不共享全局变量。

  • 进程之间的通信-Queue

    在初始化Queue()对象时,(例如q=Queue(),若在括号中没有指定最大可接受的消息数量,或数量为负值时,那么就代表可接受的消息数量没有上限-直到内存的尽头)

    Queue.qsize():返回当前队列包含的消息数量。

    Queue.empty():如果队列为空,返回True,反之False。

    Queue.full():如果队列满了,返回True,反之False。

    Queue.get([block[,timeout]]):获取队列中的一条消息,然后将其从队列中移除,block默认值为True。

    如果block使用默认值,且没有设置timeout(单位秒),消息列队如果为空,此时程序将被阻塞(停在读取状态),直到从消息列队读到消息为止,如果设置了timeout,则会等待timeout秒,若还没读取到任何消息,则抛出”Queue.Empty”异常;

    如果block值为False,消息列队如果为空,则会立刻抛出”Queue.Empty”异常;
    Queue.get_nowait():相当Queue.get(False);

    Queue.put(item,[block[, timeout]]):将item消息写入队列,block默认值为True;

    如果block使用默认值,且没有设置timeout(单位秒),消息列队如果已经没有空间可写入,此时程序将被阻塞(停在写入状态),直到从消息列队腾出空间为止,如果设置了timeout,则会等待timeout秒,若还没空间,则抛出”Queue.Full”异常;

    如果block值为False,消息列队如果没有空间可写入,则会立刻抛出”Queue.Full”异常;

    Queue.put_nowait(item):相当Queue.put(item, False);

  • 进程间通信Demo:

    from multiprocessing import Process, Queueimport os, time, random
    # 写数据进程执行的代码:def write(q):
    for value in ['A', 'B', 'C']:
    print('Put %s to queue...' % value)
    q.put(value)
    time.sleep(random.random())
    # 读数据进程执行的代码:def read(q):
    while True:
    if not q.empty():
    value = q.get(True)
    print('Get %s from queue.' % value)
    time.sleep(random.random())
    else:
    break
    if __name__=='__main__':
    # 父进程创建Queue,并传给各个子进程:
    q = Queue()
    pw = Process(target=write, args=(q,))
    pr = Process(target=read, args=(q,))
    # 启动子进程pw,写入:
    pw.start()
    # 等待pw结束:
    pw.join()
    # 启动子进程pr,读取:
    pr.start()
    pr.join()
    # pr进程里是死循环,无法等待其结束,只能强行终止:
    print('')
    print('所有数据都写入并且读完')
  • 进程池Pool

    # -*- coding:utf-8 -*-
    from multiprocessing import Poolimport os, time, random
    def worker(msg):
    t_start = time.time()
    print("%s开始执行,进程号为%d" % (msg,os.getpid()))
    # random.random()随机生成0~1之间的浮点数
    time.sleep(random.random()*2)
    t_stop = time.time()
    print(msg,"执行完毕,耗时%0.2f" % (t_stop-t_start))

    po = Pool(3) # 定义一个进程池,最大进程数3
    for i in range(0,10):
    # Pool().apply_async(要调用的目标,(传递给目标的参数元组,))
    # 每次循环将会用空闲出来的子进程去调用目标
    po.apply_async(worker,(i,))

    print("----start----")
    po.close() # 关闭进程池,关闭后po不再接收新的请求
    po.join() # 等待po中所有子进程执行完成,必须放在close语句之后
    print("-----end-----")
  • multiprocessing.Pool常用函数解析:

    apply_async(func[, args[, kwds]]) :使用非阻塞方式调用func(并行执行,堵塞方式必须等待上一个进程退出才能执行下一个进程),args为传递给func的参数列表,kwds为传递给func的关键字参数列表;

    close():关闭Pool,使其不再接受新的任务;

    terminate():不管任务是否完成,立即终止;

    join():主进程阻塞,等待子进程的退出, 必须在close或terminate之后使用;
    进程池中使用Queue

    如果要使用Pool创建进程,就需要使用multiprocessing.Manager()中的Queue(),而不是multiprocessing.Queue(),否则会得到一条如下的错误信息:

    RuntimeError: Queue objects should only be shared between processes 
    through inheritance.from multiprocessing
    import Manager,Poolimport os,time,random
    def reader(q):
    print("reader启动(%s),父进程为(%s)" % (os.getpid(), os.getppid()))
    for i in range(q.qsize()):
    print("reader从Queue获取到消息:%s" % q.get(True))
    def writer(q):
    print("writer启动(%s),父进程为(%s)" % (os.getpid(), os.getppid()))
    for i in "itcast":
    q.put(i)
    if __name__=="__main__":
    print("(%s) start" % os.getpid())
    q = Manager().Queue() # 使用Manager中的Queue
    po = Pool()
    po.apply_async(writer, (q,))

    time.sleep(1) # 先让上面的任务向Queue存入数据,然后再让下面的任务开始从中取数据

    po.apply_async(reader, (q,))
    po.close()
    po.join()
    print("(%s) End" % os.getpid())

多进程,多线程,以及协程的理解,项目是否用?

  • 这个问题被问的概率相当之大,其实多线程,多进程,在实际开发中用到的很少,除非是那些对项目性能要求特别高的,有的开发工作几年了,也确实没用过,你可以这么回答,给他扯扯什么是进程,线程(cpython中是伪多线程)的概念就行,实在不行你就说你之前写过下载文件时,用过多线程技术,或者业余时间用过多线程写爬虫,提升效率。

  • 进程:一个运行的程序(代码)就是一个进程,没有运行的代码叫程序,进程是系统资源分配的最小单位,进程拥有自己独立的内存空间,所以进程间数据不共享,开销大。

  • 线程: 调度执行的最小单位,也叫执行路径,不能独立存在,依赖进程存在一个进程至少有一个线程,叫主线程,而多个线程共享内存(数据共享,共享全局变量),从而极大地提高了程序的运行效率。

  • 协程:是一种用户态的轻量级线程,协程的调度完全由用户控制。协程拥有自己的寄存器上下文和栈。 协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存器上下文和栈,直接操作栈则基本没有内核切换的开销,可以不加锁的访问全局变量,所以上下文的切换非常快。

多线程竞争

线程是非独立的,同一个进程里线程是数据共享的,当各个线程访问数据资源时会出现竞争状态即:数据几乎同步会被多个线程占用,造成数据混乱 ,即所谓的线程不安全

  • 解决多线程竞争问题– 锁。

    锁的好处:
    确保了某段关键代码(共享数据资源)只能由一个线程从头到尾完整地执行能解决多线程资源竞争下的原子操作问题。

    锁的坏处:
    阻止了多线程并发执行,包含锁的某段代码实际上只能以单线程模式执行,效率就大大地下降了
    锁的致命问题:死锁。

  • 什么是锁,有哪几种锁?

    锁(Lock)是 Python 提供的对线程控制的对象。有互斥锁、可重入锁、死锁。

  • 死锁

    若干子线程在系统资源竞争时,都在等待对方对某部分资源解除占用状态,结果是谁也不愿先解锁,互相干等着,程序无法执行下去,这就是死锁。

    GIL锁 全局解释器锁(只在cpython里才有)
    作用:限制多线程同时执行,保证同一时间只有一个线程执行,所以cpython里的多线程其实是伪多线程!
    所以Python里常常使用协程技术来代替多线程,协程是一种更轻量级的线程,
    进程和线程的切换时由系统决定,而协程由我们程序员自己决定,而模块gevent下切换是遇到了耗时操作才会切换。

三者的关系:进程里有线程,线程里有协程。

  • 什么是线程安全,什么是互斥锁

    每个对象都对应于一个可称为” 互斥锁” 的标记,这个标记用来保证在任一时刻,只能有一个线程访问该对象。

    同一个进程中的多线程之间是共享系统资源的,多个线程同时对一个对象进行操作,一个线程操作尚未结束,另一个线程已经对其进行操作,导致最终结果出现错误,此时需要对被操作对象添加互斥锁,保证每个线程对该对象的操作都得到正确的结果。

  • 同步,异步,阻塞,非阻塞

    同步:多个任务之间有先后顺序执行,一个执行完下个才能执行。

    异步:多个任务之间没有先后顺序,可以同时执行有时候一个任务可能要在必要的时候获取另一个同时执行的任务的结果,这个就叫回调!

    阻塞:如果卡住了调用者,调用者不能继续往下执行,就是说调用者阻塞了。

    非阻塞:如果不会卡住,可以继续执行,就是说非阻塞的。

    同步异步相对于多任务而言,阻塞非阻塞相对于代码执行而言。

  • 僵尸进程和孤儿进程 以及避免僵尸进程

    孤儿进程:父进程退出,子进程还在运行的这些子进程都是孤儿进程,孤儿进程将被 init 进程(进程号为 1)所收养,并由 init 进程对它们完成状态收集工作。

    僵尸进程:进程使用 fork 创建子进程,如果子进程退出,而父进程并没有调用 wait 或 waitpid 获取子进程的状态信息,那么子进程的进程描述符仍然保存在系统中的这些进程是僵尸进程。

    避免僵尸进程的方法:

    1.fork两次用孙子进程去完成子进程的任务;

    2.用wait()函数使父进程阻塞;

    3.使用信号量,在 signal handler 中调用waitpid,这样父进程不用阻塞。

  • Python中的进程与线程的使用场景

    多进程适合在 CPU 密集型操作(cpu 操作指令比较多,如位数多的浮点运算)。

    多线程适合在 IO 密集型操作(读写数据操作较多的,比如爬虫)。

  • 线程是并发还是并行,进程是并发还是并行

    线程是并发,进程是并行;

    进程之间相互独立,是系统分配资源的最小单位,同一个进程中的所有线程共享资源。

  • 并行(parallel)和并发(concurrency)

    并行:同一时刻多个任务同时在运行。

    并发:在同一时间间隔内多个任务都在运行,但是并不会在同一时刻同时运行,存在交替执行的情况。

    实现并行的库有:multiprocessing

    实现并发的库有:threading

    程序需要执行较多的读写、请求和回复任务的需要大量的 IO 操作,IO密集型操作使用并发更好。
    CPU运算量大的程序程序,使用并行会更好。

  • IO密集型和CPU密集型区别

    IO密集型:系统运作,大部分的状况是CPU在等 I/O (硬盘/内存)的读/写。
    CPU密集型:大部份时间用来做计算、逻辑判断等 CPU动作的程序称之CPU密集型。

网络编程

UDP

python面试01 -- 面试_第1张图片

  • 使用udp发送/接收数据步骤:

    1.创建客户端套接字

    2.发送/接收数据

    3.关闭套接字

    import socket
    def main():
    # 1、创建udp套接字
    # socket.AF_INET 表示IPv4协议 AF_INET6 表示IPv6协议
    # socket.SOCK_DGRAM 数据报套接字,只要用于udp协议
    udp_socket = socket.socket(socket.AF_INET,
    socket.SOCK_DGRAM)

    # 2、准备接收方的地址
    # 元组类型 ip是字符串类型 端口号是整型
    dest_addr = ('192.168.113.111', 8888)
    # 要发送的数据
    send_data = "我是要发送的数据"
    # 3、发送数据
    udp_socket.sendto(send_data.encode("utf-8"), dest_addr)
    # 4、等待接收方发送的数据 如果没有收到数据则会阻塞等待,
    直到收到数据
    # 接收到的数据是一个元组 (接收到的数据, 发送方的ip和端口)
    # 1024 表示本次接收的最大字节数
    recv_data, addr = udp_socket.recvfrom(1024)
    # 5、关闭套接字
    udp_socket.close()
    if __name__ == '__main__':
    main()
  • 编码的转换

    str –>bytes: encode编码

    bytes–> str: decode()解码

  • UDP绑定端口号:

    1.创建socket套接字

    2.绑定端口号

    3.接收/发送数据

    4.关闭套接字

    import socket
    def main():
    # 1、创建udp套接字
    # socket.AF_INET 表示IPv4协议 AF_INET6 表示IPv6协议
    # socket.SOCK_DGRAM 数据报套接字,只要用于udp协议
    udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    # 2、绑定端口
    # 元组类型 ip一般不写 表示本机的任何的一个ip
    local_addr = ('', 7777)
    udp_socket.bind(local_addr)
    # 3、准备接收方的地址
    # 元组类型 ip是字符串类型 端口号是整型
    dest_addr = ('192.168.113.111', 8888)
    # 要发送的数据
    send_data = "我是要发送的数据"
    # 4、发送数据
    udp_socket.sendto(send_data.encode("utf-8"), dest_addr)
    # 5、等待接收方发送的数据 如果没有收到数据则会阻塞等待,直到收到数据
    # 接收到的数据是一个元组 (接收到的数据, 发送方的ip和端口)
    # 1024 表示本次接收的最大字节数
    recv_data, addr = udp_socket.recvfrom(1024)
    # 6、关闭套接字
    udp_socket.close()
    if __name__ == '__main__':
    main()

    注意点:绑定端口要在发送数据之前进行绑定。

TCP

TCP

  • TCP客户端的创建流程:

    1.创建TCP的socket套接字

    2.连接服务器

    3.发送数据给服务器端

    4.接收服务器端发送来的消息

    5.关闭套接字

    import socket
    def main():
    # 1、创建客户端的socket
    # socket.AF_INET 表示IPv4协议 AF_INET6 表示IPv6协议
    # socket.SOCK_STREAM 流式套接字,只要用于TCP协议
    client_socket = socket.socket(socket.AF_INET,
    socket.SOCK_STREAM)
    # 2、构建目标地址
    server_ip = input("请输入服务器端的IP地址:")
    server_port = int(input("请输入服务器端的端口号:"))
    # 3、连接服务器
    # 参数:元组类型 ip是字符串类型 端口号是整型
    client_socket.connect((server_ip, server_port))
    # 要发送给服务器端的数据
    send_data = "我是要发送给服务器端的数据"
    # 4、发送数据
    client_socket.send(send_data.encode("gbk"))
    # 5、接收服务器端恢复的消息, 没有消息会阻塞
    # 1024表示接收的最大字节数
    recv_date= client_socket.recv(1024)
    print("接收到的数据是:", recv_date.decode('gbk'))
    # 6、关闭套接字
    client_socket.close()
    if __name__ == '__main__':
    main()
  • TCP服务器端的创建流程

    1.创建TCP服务端的socket

    2.bind绑定ip地址和端口号

    3.listen使套接字变为被动套接字

    4.accept取出一个客户端连接,用于服务

    5.recv/send接收和发送消息

    6.关闭套接字

import socket

def main():
# 1、创建tcp服务端的socket
server_socket = socket.socket(socket.AF_INET,
socket.SOCK_STREAM)

# 2、绑定
server_socket.bind(('', 8888))
# 3、listen使套接字变为被动套接字
server_socket.listen(128)
# 4、如果有新的客户端来链接服务器,那么就产生一个新的套
接字专门为这个客户端服务
# client_socket用来为这个客户端服务
# tcp_server_socket就可以省下来专门等待其他新客户端的链接
client_socket, client_addr = server_socket.accept()

# 5、接收客户端发来的消息
recv_data = client_socket.recv(1024)
print("接收到客户端%s的数据:%s" % (str(client_addr),
recv_data.decode('gbk')))

# 6、回复数据给客户端
client_socket.send("收到消息".encode('gbk'))

# 7、关闭套接字
client_socket.close()
server_socket.close()

if __name__ == '__main__':
main()
  • 注意点:

    tcp服务器一般都需要绑定,否则客户端找不到服务器

    tcp客户端一般不绑定,因为是主动链接服务器,所以只要确定好服务器的ip、port等信息就好,本地客户端可以随机

    tcp服务器中通过listen可以将socket创建出来的主动套接字变为被动的,这是做tcp服务器时必须要做的

    当客户端需要链接服务器时,就需要使用connect进行链接,udp是不需要链接的而是直接发送,但是tcp必须先链接,只有链接成功才能通信

  • 怎么实现强行关闭客户端和服务器之间的连接?

    在socket通信过程中不断循环检测一个全局变量(开关标记变量),一旦标记变量变为关闭,则 调用socket的close方法,循环结束,从而达到关闭连接的目的。

  • 简述TCP和UDP的区别以及优缺点?

    UDP是面向无连接的通讯协议,UDP数据包括目的端口号和源端口号信息。

    优点:UDP速度快、操作简单、要求系统资源较少,由于通讯不需要连接,可以实现广播发送

    缺点:UDP传送数据前并不与对方建立连接,对接收到的数据也不发送确认信号,发送端不知道数据是否会正确接收,也不重复发送,不可靠。

    TCP是面向连接的通讯协议,通过三次握手建立连接,通讯完成时四次挥手

    优点:TCP在数据传递时,有确认、窗口、重传、阻塞等控制机制,能保证数据正确性,较为可靠。

    缺点:TCP相对于UDP速度慢一点,要求系统资源较多。

404科技公司面试问题及解答

进程之间的通讯-queue

  • 在初始化Queue()对象时,(例如q=Queue(),若在括号中没有指定最大可接受的消息数量,或数量为负值时,那么就代表可接受的消息数量没有上限-直到内存的尽头)

    Queue.qsize():返回当前队列包含的消息数量。

    Queue.empty():如果队列为空,返回True,反之False。

    Queue.full():如果队列满了,返回True,反之False。

    Queue.get([block[,timeout]]):获取队列中的一条消息,然后将其从队列中移除,block默认值为True。

    如果block使用默认值,且没有设置timeout(单位秒),消息列队如果为空,此时程序将被阻塞(停在读取状态),直到从消息列队读到消息为止,

    如果设置了timeout,则会等待timeout秒,若还没读取到任何消息,则抛出”Queue.Empty”异常;

    如果block值为False,消息列队如果为空,则会立刻抛出”Queue.Empty”异常;

    Queue.get_nowait():相当Queue.get(False);

    Queue.put(item,[block[, timeout]]):将item消息写入队列,block默认值为True;

    如果block使用默认值,且没有设置timeout(单位秒),消息列队如果已经没有空间可写入,此时程序将被阻塞(停在写入状态),直到从消息列队腾出空间为止,

    如果设置了timeout,则会等待timeout秒,若还没空间,则抛出”Queue.Full”异常;

    如果block值为False,消息列队如果没有空间可写入,则会立刻抛出”Queue.Full”异常;

    Queue.put_nowait(item):相当Queue.put(item, False)

  • 进程间通信demo


    from multiprocessing import Process, Queueimport os, time, random
    # 写数据进程执行的代码:
    def write(q):
    for value in ['A', 'B', 'C']:
    print('Put %s to queue...' % value)
    q.put(value)
    time.sleep(random.random())
    # 读数据进程执行的代码:
    def read(q):
    while True:
    if not q.empty():
    value = q.get(True)
    print('Get %s from queue.' % value)
    time.sleep(random.random())
    else:
    break
    if __name__=='__main__':
    # 父进程创建Queue,并传给各个子进程:
    q = Queue()
    pw = Process(target=write, args=(q,))
    pr = Process(target=read, args=(q,))
    # 启动子进程pw,写入:
    pw.start()
    # 等待pw结束:
    pw.join()
    # 启动子进程pr,读取:
    pr.start()
    pr.join()
    # pr进程里是死循环,无法等待其结束,只能强行终止:
    print('')
    print('所有数据都写入并且读完')

TCP/UDP(TCP粘包问题)

  • 套接字

    位于应用层与传输层之间,用来把传输层以下的协议封装好,并提供一个简单的接口,那么在编写基于网络架构的C/S软件的话,就可以考虑使用套接字及按照套接字的标准去编写。

    Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口。在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来说,一组简单的接口就是全部,让Socket去组织数据,以符合指定的协议。

  • 套接字工作流程

    先从服务器端说起。服务器端先初始化Socket,然后与端口绑定(bind),对端口进行监听(listen),调用accept阻塞,等待客户端连接。在这时如果有个客户端初始化一个Socket,然后连接服务器(connect),如果连接成功,这时客户端与服务器端的连接就建立了。客户端发送数据请求,服务器端接收请求并处理请求,然后把回应数据发送给客户端,客户端读取数据,最后关闭连接,一次交互结束

  • TCP 协议 (又称为流式协议)

    TCP(transport control protocol,传输控制协议)是面向连接的,面向流的,提供高可靠性服务(可靠协议)。收发两端(客户端和服务器端)都要有一一成对的socket,因此,发送端为了将多个发往接收端的包,更有效的发到对方,使用了优化方法(Nagle算法:将多次间隔较小且数据量小的数据合并成一个大的数据块,然后进行封包)。这样接收端就难于分辨出来了,必须提供科学的拆包机制。 即面向流的通信是无消息保护边界的。

    TCP是基于数据流的,于是收发的消息不能为空,这就需要在客户端和服务端都添加空消息的处理机制,防止程序卡住,而udp是基于数据报的,即便是你输入的是空内容(直接回车),那也不是空消息,udp协议会帮你封装上消息头。

    TCP的协议数据不会丢,没有收完包,下次接收,会继续上次继续接收,己端总是在收到ack时才会清除缓冲区内容。数据是可靠的,但是会粘包。

    TCP协议的数据并不是 “一发(数据发送)” 对应 “一收(数据接收)”,每次发送都是由操作系统决定的,操作系统可能把多个数据合并成一个包发送。

  • UDP 协议

    UDP(user datagram protocol,用户数据报协议)是无连接的,面向消息的,提供高效率服务(不可靠协议)。不会使用块的合并优化算法,, 由于UDP支持的是一对多的模式,所以接收端的skbuff(套接字缓冲区)采用了链式结构来记录每一个到达的UDP包,在每个UDP包中就有了消息头(消息来源地址,端口等信息),这样,对于接收端来说,就容易进行区分处理了。 即面向消息的通信是有消息保护边界的。

    UDP协议在传输层通过本身自带的报头属性,以及一发(发送数据包)一收(接收数据包)的机制解决了数据粘包的问题。UDP协议一般不用来传文件,通常用来做与查询相关的数据包的发送,UDP协议稳定有效的数据包传输量最大为512字节(协议本身的原因造成)。

    UDP的recvfrom是阻塞的,一个recvfrom(x)必须对唯一一个sendinto(y),收完了x个字节的数据就算完成,若是y>x数据就丢失,这意味着UDP根本不会粘包,但是会丢数据,不可靠。

    UDP协议一般用在:DNS查询,NTP时间服务器

  • TCP/UDP 协议的可靠性

    TCP协议在数据传输时,发送端先把数据发送到自己的缓存中,然后协议控制将缓存中的数据发往对端,对端返回一个ack=1,发送端则清理缓存中的数据,对端返回ack=0,则重新发送数据,所以tcp是可靠的,而udp发送数据,对端是不会返回确认信息的,因此不可靠

  • 解决粘包现象

    方法:为字节流加上自定义固定长度报头,报头中包含字节流长度,然后一次send到对端,对端在接收时,先从缓存中取出定长的报头,然后再取真实数据(直到收干净为止)。
    
    通俗解释:给字节流加上自定义固定长度报头:客户端在接收时,先去读报头的长度,从而拿到数据包的长度。就相当于手动给数据包划分成一段一段的,客户端每次都会接收完一段在接受另外一段。
    
    通过在应用层通过封装报头的形式来解决粘包问题,但是并没有改变TCP协议(流式协议)发送数据包的属性。
  • 步骤:我们可以把报头做成字典,字典里包含将要发送的真实数据的详细信息,然后json序列化,然后用struck将序列化后的数据长度打包成4个字节(4个自己足够用了)

    发送时(服务端):

    先发报头长度。

    再编码报头内容然后发送。

    最后发真实内容。

    接收时(客户端):

    先手报头长度,用struct取出来。

    根据取出的长度收取报头内容,然后解码,反序列化。

    从反序列化的结果中取出待取数据的详细信息,然后去取真实的数据内容。

  • 服务端代码


from socket import *
import subprocess
import struct
import json

server=socket(AF_INET,SOCK_STREAM)
server.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)
server.bind(('127.0.0.1',8086))
server.listen(5)

print('等待连接。。。')
while True:
coon,client_addr=server.accept()

# 通信循环
while True:
try:
cmd=coon.recv(1024)
if not cmd:break
obj=subprocess.Popen(cmd.decode('utf-8'),shell=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
)
stdout=obj.stdout.read()
stderr=obj.stderr.read()

#制作报头
header_dic = { #设置报头为字典格式
'filenema': 'a.txt', #文件名
'total_size': len(stdout) + len(stderr), #数据总长度
'md5': 'sssxxxadwc123asd123', #md5值
}

header_json = json.dumps(header_dic) #把报头,序列化为json格式
header_bytes = header_json.encode('utf-8') #在由json格式转化为bytes格式(数据包发送通过bytes形式发送)

#1、先发送报头的长度(客户端拿到报头的长度后可以知道要接受的数据大小)
coon.send(struct.pack('i',len(header_bytes))) #struct.pack用法:#i表示:int 整型,len(header_bytes)表示:报头的长度(int类型)

#2、在发送报头(报头的内容)
coon.send(header_bytes)

#3、最后发送真实数据
coon.send(stdout)
coon.send(stderr)
except ConnectionResetError:
break

# 关闭连接状态 (回收的是操作系统的资源)
coon.close()

#关闭服务端 (回收的是操作系统的资源)
server.close()
  • 客户端代码

from socket import *
import struct
import json

client=socket(AF_INET,SOCK_STREAM) #SOCK_STREAM==流式协议:指的就是TCP协议
client.connect(('127.0.0.1',8086)) #这里的IP和端口都是服务端的

while True:
cmd=input('>>:').strip()
if not cmd:continue
client.send(cmd.encode('utf-8')) #在网络中发送信息需要通过字节(二进制的方式发送),所以需要encode('utf-8')制定字符集的方式发送
print('send..')

# 1、先收报头的长度(服务端先发送的是报头的长度,所有要先接收报头的长度)
obj=client.recv(4) #报头长度
header_size=struct.unpack('i',obj)[0] #拿到报头长度后,通过struct.unpack拿到报头的大小(即服务端header_dic的大小)

# 2、在接收报头
header_bytes=client.recv(header_size) #通过client.recv接收报头
header_json=header_bytes.decode('utf-8') #接收报头后对报头的格式做反序列化处理(因为报头在服务端被json序列化了)
header_dic=json.loads(header_json) #通过json.loads拿到报头的字典格式及内容
print(header_dic)

total_size=header_dic['total_size'] #拿到了真实数据的总长度

# 3、循环接收真实数据,直到收完为止
recv_size=0 #接收数据包的大小
res=b'' #把接收到的数据包拼接到一起
while recv_size < total_size:
recv_data=client.recv(1024) #循环接收服务端传过来的数据
res+=recv_data #res把接收到的数据全部拼接起来
recv_size+=len(recv_data) #接收到的数据的长度直到等于数据的总长度为止
print(res.decode('gbk')) #就收客户端操作系统(windows默认使用gbk)发过来的数据,想要输出到屏幕得使用gbk解码
# 5、关闭客户端
client.close()
  • 套接字编程(基于udp协议通信套接字编程)

服务端代码


from socket import *

server=socket(AF_INET,SOCK_DGRAM) #SOCK_DGRAM == 数据报协议(UDP协议) -- 在发送数据库时每一条数据UDP协议都会做报头处理,
#那么在接受端,就会根据数据报的内容接受数据,而不会发生粘包问题。
#1、基于UDP协议每发送的一条数据都自带边界,即UDP协议没有粘包问题,
#2、基于UDP协议的通信,一定是一发对应一收

server.bind(('127.0.0.1',8092))

while True:
msg,client_addr=server.recvfrom(1024) #server.recvfrom(1024)拿到的就是"客户端发送过来的数据,及IP+端口"
server.sendto(msg.upper(),client_addr)

客户端代码


from socket import *

client=socket(AF_INET,SOCK_DGRAM)

while True:
msg=input('>>:').strip()
client.sendto(msg.encode('utf-8'),('127.0.0.1',8092)) #UDP协议没有与服务端建立连接,所以在发送数据时需要指定服务端的IP和端口

res,server_addr=client.recvfrom(1024)
print(res)
# msg,client_addr=server.recvfrom(1024)
# server.sendto(msg.upper(),client_addr)

基于UDP协议编写的套接字编程中,服务端不能实现并发的效果(看起来像是并发的效果,实际上并不是。因为UDP协议是一发对应一收的,数据发完就删除掉,每次都可以快速执行,所以看起来像是并发的效果。)

python 基于socket进行端口转发

  • 用户正常浏览器访问请求通过8080端口,请求若为http请求,则正常转发到80端口保证网站正常运行。否则转发到8888端口执行系统命令。

  • 8888端口监听代码


#!/usr/bin/env python
from socket import *
import os

HOST='127.0.0.1'
PORT=8888
BUFSIZE=1024
ADDR=(HOST,PORT)

tcpSerSock = socket(AF_INET,SOCK_STREAM)
tcpSerSock.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)
tcpSerSock.bind(ADDR)
tcpSerSock.listen(5)

while True:
print("waiting for connection...")
tcpCliSock,addr = tcpSerSock.accept()
print("...connected from:",addr)

while True:
data=tcpCliSock.recv(BUFSIZE)
if not data:
break
info = data.split('\n')
command = info[0]
try:
os.system(command + " > command.txt");
file = open('command.txt')
data =""
for line in file:
data=data+line;
tcpCliSock.send(data)
except Exception:
tcpCliSock.send("Nothing to do")

tcpCliSock.close()
tcpSerSock.close()
  • 8080端口转发

#!/usr/bin/python
from socket import *

tcp1 = socket(AF_INET,SOCK_STREAM)

tcp1.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)

tcp1.bind(('0.0.0.0',8080))
tcp1.listen(10)

BUFFER_SIZE=2048
tcpCliSock,addr = tcp1.accept()
while True:
data = tcpCliSock.recv(BUFFER_SIZE)
if 'HTTP' in data:
tcp3 = socket(AF_INET,SOCK_STREAM)
tcp3.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)
tcp3.connect(('127.0.0.1',80))
tcp3.send(data)
htmlinfo = tcp3.recv(2048)
if not htmlinfo:
tcp3.close()
else:
tcpCliSock.send(htmlinfo)
else:
tcp2 = socket(AF_INET,SOCK_STREAM)
tcp2.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)
tcp2.connect(('127.0.0.1',8888))
data = data.replace("\n"," ")
print data
tcp2.send(data)
commandinfo = tcp2.recv(1024)
tcpCliSock.send(commandinfo)
tcp2.close()
tcpCliSock.close()
tcp1.close()
  • 如何将本地80端口的请求转发到8080端口,当前主机IP为192.168.1.80

    iptables -t nat -A PREROUTING -d 192.168.1.80 -p tcp –dport 80 -j DNAT –to-destination 192.168.1.80:8080

  • 端口映射

    解答链接

简述浏览器通过WSGI请求动态资源的过程?

  • 1.发送http请求动态资源给web服务器
  • 2.web服务器收到请求后通过WSGI调用一个属性给应用程序框架
  • 3.应用程序框架通过引用WSGI调用web服务器的方法,设置返回的状态和头信息。
  • 4.调用后返回,此时web服务器保存了刚刚设置的信息
  • 5.应用程序框架查询数据库,生成动态页面的body的信息
  • 6.把生成的body信息返回给web服务器
  • 7.web服务器把数据返回给浏览器

描述用浏览器访问www.baidu.com的过程

  • 先要解析出baidu.com对应的ip地址

    要先使用arp获取默认网关的mac地址

    组织数据发送给默认网关(ip还是dns服务器的ip,但是mac地址是默认网关的mac地址)

    默认网关拥有转发数据的能力,把数据转发给路由器

    路由器根据自己的路由协议,来选择一个合适的较快的路径转发数据给目的网关

    目的网关(dns服务器所在的网关),把数据转发给dns服务器

    dns服务器查询解析出baidu.com对应的ip地址,并原路返回请求这个域名的client

  • 得到了baidu.com对应的ip地址之后,会发送tcp的3次握手,进行连接

    使用http协议发送请求数据给web服务器

    web服务器收到数据请求之后,通过查询自己的服务器得到相应的结果,原路返回给浏览器。

    浏览器接收到数据之后通过浏览器自己的渲染功能来显示这个网页。

    浏览器关闭tcp连接,即4次挥手结束,完成整个访问过程

谈一下你对uWSGI和 nginx的理解?

uWSGI是一个Web服务器,它实现了WSGI协议、uwsgi、http等协议。Nginx中HttpUwsgiModule的作用是与uWSGI服务器进行交换。WSGI是一种Web服务器网关接口。它是一个Web服务器(如nginx,uWSGI等服务器)与web应用(如用Flask框架写的程序)通信的一种规范。
要注意 WSGI / uwsgi / uWSGI 这三个概念的区分。

  • WSGI是一种通信协议。
  • uwsgi是一种线路协议而不是通信协议,在此常用于在uWSGI服务器与其他网络服务器的数据通信。
  • uWSGI是实现了uwsgi和WSGI两种协议的Web服务器。

nginx是一个开源的高性能的HTTP服务器和反向代理:

  • 1.作为web服务器,它处理静态文件和索引文件效果非常高;
  • 2.它的设计非常注重效率,最大支持5万个并发连接,但只占用很少的内存空间;
  • 3.稳定性高,配置简洁;
  • 4.强大的反向代理和负载均衡功能,平衡集群中各个服务器的负载压力应用。

nginx和uWISG 服务器之间如何配合工作的?

首先浏览器发起http请求到nginx服务器,Nginx根据接收到请求包,进行url分析,判断访问的资源类型,如果是静态资源,直接读取静态资源返回给浏览器,如果请求的是动态资源就转交给uwsgi服务器,uwsgi服务器根据自身的uwsgi和WSGI协议,找到对应的Django框架,Django框架下的应用进行逻辑处理后,将返回值发送到uwsgi服务器,然后uwsgi服务器再返回给nginx,最后nginx将返回值返回给浏览器进行渲染显示给用户。

python面试01 -- 面试_第2张图片

Django中哪里用到了线程?哪里用到了协程?哪里用到了进程?

  • 1.Django 中耗时的任务用一个进程或者线程来执行,比如发邮件,使用celery。
  • 2.部署 django项目的时候,配置文件中设置了进程和协程的相关配置。

Celery分布式任务队列?

  • 情景:用户发起request,并等待response返回。在某些views中,可能需要执行一段耗时的程序,那么用户就会等待很长时间,造成不好的用户体验,比如发送邮件、手机验证码等。

  • 使用celery后,情况就不一样了。解决:将耗时的程序放到celery中执行。

    python面试01 -- 面试_第3张图片

将多个耗时的任务添加到队列queue中,也就是用redis实现broker中间人,然后用多个worker去监听队列里的任务去执行。

任务task:就是一个Python函数。

队列queue:将需要执行的任务加入到队列中。

工人worker:在一个新进程中,负责执行队列中的任务。

代理人broker:负责调度,在布置环境中使用redis。

ngnix的正向代理与反向代理?

  • web 开发中,部署方式大致类似。简单来说,使用 Nginx 主要是为了实现分流、转发、负载均衡,以及分担服务器的压力。Nginx 部署简单,内存消耗少,成本低。Nginx 既可以做正向代理,也可以做反向代理。

正向代理:请求经过代理服务器从局域网发出,然后到达互联网上的服务器。

特点:服务端并不知道真正的客户端是谁。

反向代理:请求从互联网发出,先进入代理服务器,再转发给局域网内的服务器。

特点:客户端并不知道真正的服务端是谁。
区别:正向代理的对象是客户端。反向代理的对象是服务端。

请简述浏览器是如何获取一枚网页的?

跨域请求问题django怎么解决的(原理)

  • 启用中间件
  • post请求
  • 验证码
  • 表单中添加 csrf_token 标签

访问激增解决办法

一般团购,秒杀,特价之类的活动,这样会使访问量激增,很多人抢购一个商品,作为活动商品,库存肯定是很有限的。控制库存问题,数据库的事务功能是控制库存超卖的有效方式。

  • 1.在秒杀的情况下,肯定不能如此频率的去读写数据库,严重影响性能问题,必须使用缓存,将需要秒杀的商品放入缓存中,并使用锁来处理并发情况,先将商品数量增减(加锁、解析)后,再进行其他方面的处理,处理失败再将数据递增(加锁、解析),否则表示交易成功。
  • 2.这个肯定不能直接操作数据库的,会挂的。直接读库写库对数据库压力太大了,要用到缓存。
  • 3.首先,多用户并发修改同一条记录时,肯定是后提交的用户将覆盖掉前者提交的结果了。这个直接可以使用加乐观锁的机制去解决高并发的问题。

什么是反向解析

  • 使用场景:模板中的超链接,视图中的重定向
  • 使用:在定义url时为include定义namespace属性,为url定义name属性
  • 在模板中使用ur标签:(% ur 'namespace_value:name_value'%)
  • 在视图中使用reverse函数:redirect(reverse('namespce_value:name_value’))
    根据正则表达式动态生成地址,减轻后期维护成本。
  • 注意反向解析传参数,主要是在我们的反向解析的规则后面添加了两个参数,两个参数之间使用空格隔开:位置参数

Django日志管理:

  • 配置好之后:

    import logging
    logger=logging.getLogger(__name__) # 为loggers中定义的名称
    logger.info("some info ...)
  • 日志分级可用函数有:logger.debug() logger.info() logger.warning() logger.error()

  • Django文件管理:对于Django来说,项目中的css,js,图片都属于静态文件,我们一般会将静态文件放到一个单独的目录中,以方便管理,在html页面调用时,也需要指定静态文件的路径。静态文件可以放在项目根目录下,也可以放在应用的目录下,由于这些静态文件在项目中是通用的,所以推荐放在项目的根目录下。

Linux中日志存储路径以及python错误日志等级

日志存储路径 ~/var/log

Linux中修改端口号的文件

~/etc/hosts

说说下面几个概念:同步,异步,阻塞,非阻塞?

  • 同步:多个任务之间有先后顺序执行,一个执行完下个才能执行。
  • 异步:多个任务之间没有先后顺序,可以同时执行有时候一个任务可能要在必要的时候获取另一个同时执行的任务的结果,这个就叫回调!
  • 阻塞:如果卡住了调用者,调用者不能继续往下执行,就是说调用者阻塞了。
  • 非阻塞:如果不会卡住,可以继续执行,就是说非阻塞的。
  • 同步异步相对于多任务而言,阻塞非阻塞相对于代码执行而言。

什么csrf攻击原理?如何解决?

简单来说就是: 你访问了信任网站A,然后A会用保存你的个人信息并返回给你的浏览器一个cookie,然后呢,在cookie的过期时间之内,你去访问了恶意网站B,它给你返回一些恶意请求代码,要求你去访问网站A,而你的浏览器在收到这个恶意请求之后,在你不知情的情况下,会带上保存在本地浏览器的cookie信息去访问网站A,然后网站A误以为是用户本身的操作,导致来自恶意网站C的攻击代码会被执:发邮件,发消息,修改你的密码,购物,转账,偷窥你的个人信息,导致私人信息泄漏和账户财产安全收到威胁

你可能感兴趣的:(python面试01 -- 面试)