整理面试题

1.可变与不可变类型:
可变类型:列表,字典,可变集合
不可变类型:数字,字符串,元组,不可变集合
python所声明的变量都以对象的形式存在,存在于机器的固定内存之中。
可以理解为变量名为对象的指针
如声明a=3,则a指向存储3的空间,python通过使用计数器的方式来判断空间的引用情况,当计数器为0时则会将内存回收。
python在声明不可变类型时会在已经声明的对象中寻找该对象是否已经被声明过,若该对象被声明过则,变量会直接指向该对象不会再申请新的内存空间。
如a=3已经声明这时声明b=3,则a,b指向同一块内存

不可变类型不能对其内容直接修改,如字符串’qweqw0’可以对其进行分割加长的处理(经过如此处理之后python其实是将新的字符串储存到新的内存中),但是不能替换改变其字符串中某个字母的内容

python声明可变类型,后在声明同样的内容python会重新发申请空间对其进行存储
这里要注意因为变量都是以指针的形式存在,所以当以=的形式赋值时,其实是将其指向的对象进行改变,并不是改变了其内存中的内容。所以在用=赋值可变类型后,因为两个变量所指向的地址是相同的,当其改变其对象的部分内容时,另外一个变量的内容也会跟着改变。如果要想拷贝数组等对象,需要使用相应的函数来进行操作。

python中可以用is来判段是否指向同一个地址

2.浅拷贝与深拷贝的实现方式、区别
在python中,对象赋值实际上是对象的引用。当创建一个对象,然后把它赋给另一个变量的时候,python并没有拷贝这个对象,而只是拷贝了这个对象的引用

  • 直接赋值,传递对象的引用而已,原始列表改变,被赋值的b也会做相同的改变
    >>> b=alist
    >>> print b
    [1, 2, 3, [‘a’, ‘b’]]
    >>> alist.append(5)
    >>> print alist;print b
    [1, 2, 3, [‘a’, ‘b’], 5]
    [1, 2, 3, [‘a’, ‘b’], 5]

  • copy浅拷贝,没有拷贝子对象,所以原始数据改变,子对象会改变
    >>> import copy

>>> c=copy.copy(alist)
>>> print alist;print c
[1, 2, 3, [‘a’, ‘b’]]
[1, 2, 3, [‘a’, ‘b’]]
>>> alist.append(5)
>>> print alist;print c
[1, 2, 3, [‘a’, ‘b’], 5]
[1, 2, 3, [‘a’, ‘b’]]

>>> alist[3]
[‘a’, ‘b’]
>>> alist[3].append(‘cccc’)
>>> print alist;print c
[1, 2, 3, [‘a’, ‘b’, ‘cccc’], 5]
[1, 2, 3, [‘a’, ‘b’, ‘cccc’]] 里面的子对象被改变了

  • 深拷贝,包含对象里面的自对象的拷贝,所以原始对象的改变不会造成深拷贝里任何子元素的改变
    >>> import copy

>>> d=copy.deepcopy(alist)
>>> print alist;print d
[1, 2, 3, [‘a’, ‘b’]]
[1, 2, 3, [‘a’, ‘b’]]始终没有改变
>>> alist.append(5)
>>> print alist;print d
[1, 2, 3, [‘a’, ‘b’], 5]
[1, 2, 3, [‘a’, ‘b’]]始终没有改变
>>> alist[3]
[‘a’, ‘b’]
>>> alist[3].append(“ccccc”)
>>> print alist;print d
[1, 2, 3, [‘a’, ‘b’, ‘ccccc’], 5]
[1, 2, 3, [‘a’, ‘b’]] 始终没有改变

3.__new__() 与 __init__()的区别
__new__:创建对象时调用,会返回当前对象的一个实例
__init__:创建完对象后调用,对当前对象的一些实例初始化,无返回值

4.编码和解码你了解过么
首先,明确一点,计算机中存储的信息都是二进制的

编码/解码本质上是一种映射(对应关系),比如‘a’用ascii编码则是65,计算机中存储的就是00110101,但是显示的时候不能显示00110101,还是要显示’a’,但计算机怎么知道00110101是’a’呢,这就需要解码,当选择用ascii解码时,当计算机读到00110101时就到对应的ascii表里一查发现是’a’,就显示为’a’

编码:真实字符与二进制串的对应关系,真实字符→二进制串
解码:二进制串与真实字符的对应关系,二进制串→真实字符

在python中,编码解码其实是不同编码系统间的转换,默认情况下,转换目标是Unicode,即编码unicode→str,解码str→unicode,其中str指的是字节流
而str.decode是将字节流str按给定的解码方式解码,并转换成utf-8形式,u.encode是将unicode类按给定的编码方式转换成字节流str
注意调用encode方法的是unicode对象生成的是字节流,调用decode方法的是str对象(字节流)生成的是unicode对象,若str对象调用encode会默认先按系统默认编码方式decode成unicode对象再encode,忽视了中间默认的decode往往导致报错
自己写代码时只需记住str字节流调用decode,unicode对象调用

5.列表推导list comprehension和生成器的优劣

  • 列表推导式是将所有的值一次性加载到内存中
    生成器是将列表推导式的[]改成(),不会将所有的值一次性加载到内存中,延迟计算,一次返回一个结果,它不会一次生成所有的结果,这对大数据量处理,非常有用
    生成器函数: 一个函数中包含了yield关键,那么这个函数就不是普通的函数,是一个生成器函数
    调用生成器函数,不会立马执行该函数里面的代码, 而是会返回一个 生成器

  • sum(x for x in range(10000000000))
    sum([x for x in range(10000000000)])
    第一个几乎没什么内存占用,第二个内存占有很多
    原理:sum函数时python3中的内置函数,该函数使用迭代器协议访问对象,而生成器实现了迭代器协议,所以我们可以直接计算一系列值的和,而不用多此一举先构造一个列表.
    生成器还可以提高代码的可读性

  • 列表推导式可以遍历任意次
    生成器只能遍历一次

generator = (i for i in range(1,5))
print(next(generator))
print(next(generator))
for i in generator:
    print(i)
for i in generator:
    print(i)

打印结果:

1
2
3
4
list1 = [i for i in range(1,5)]
print(list1[0])
print(list1[1])
print(list1[2])
for i in list1:
    print(i)
for i in list1:
    print(i)

打印结果

1
2
3
1
2
3
4
1
2
3
4

6.什么是lambda函数?它有什么好处?
lambda 函数是一个可以接收任意多个参数(包括可选参数)并且返回单个表达式值的函数。 (注意:lambda 函数不能包含命令,它们所包含的表达式不能超过一个)

lamda函数有什么好处?

  • lambda函数比较轻便,即用即仍,很适合需要完成一项功能,但是此功能只在此一处使用,连名字都很随意的情况下
  • 匿名函数,一般用来给filter,map这样的函数式编程服务;
  • 作为回调函数,传递给某些应用,比如消息处理

7.什么是装饰器;如果想在函数之后进行装饰,应该怎么做
装饰器本质上是一个 Python 函数,它可以让其他函数在不需要做任何代码变动的前提下增加额外功能,装饰器的返回值也是一个函数对象。

使用类装饰器,通过调用__call__方法来进行附加功能操作。

装饰方法的函数有property, classmethod, staticmethod; functools模块中的lru_cache, singledispatch, wraps 等等
wraps本身也是一个装饰器,它能把原函数的元信息拷贝到装饰器函数中,这使得装饰器函数也有和原函数一样的元信息了。

8.手写个使用装饰器实现的单例模式


def Singleton(cls):
    _instance = {}

    def _singleton(*args, **kargs):
        if cls not in _instance:
            _instance[cls] = cls(*args, **kargs)
        return _instance[cls]

    return _singleton


@Singleton
class A(object):
    a = 1

    def __init__(self, x=0):
        self.x = x

a1 = A(2)
a2 = A(3)
print(id(a1))
print(id(a2))
print(a1.x)

运行结果是:

140006705680512
140006705680512
2

9.使用装饰器的单例和使用其他方法的单例,在后续使用中,有何区别
所以装饰器单例与new单例的区别在于属性不会被覆盖
因为new会重新调用init方法,为实例重新初始化属性,而装饰器单例模式则是直接返回之前生成的对象,并不会重新初始化对象,所以new单例从某种程度上也可以视作伪单例模式

10.ecb和cbc模式有什么区别?
ECB:是一种基础的加密方式,密文被分割成分组长度相等的块(不足补齐),然后单独一个个加密,一个个输出组成密文。

CBC:是一种循环模式,前一个分组的密文和当前分组的明文异或操作后再加密,这样做的目的是增强破解难度。ECB和CBC的加密结果是不一样的,两者的模式不同,而且CBC会在第一个密码块运算时加入一个初始化向量。

11.对称加密与非对称加密的区别?
对称加密,需要对加密和解密使用相同密钥的加密算法。由于其速度快,对称性加密通常在消息发送方需要加密大量数据时使用。所以,对称性加密也称为密钥加密。

而非对称加密算法需要两个密钥:公开密钥和私有密钥。公开密钥与私有密钥是一对,如果用公开密钥对数据进行加密,只有用对应的私有密钥才能解密;如果用私有密钥对数据进行加密,那么只有用对应的公开密钥才能解密。

  1. Xrange和range的区别?
    range([start,] stop[, step]),根据start与stop指定的范围以及step设定的步长,生成一个序列。xrange 用法与 range 完全相同,所不同的是生成的不是一个list对象,而是一个生成器。要生成很大的数字序列的时候,用xrange会比range性能优很多,因为不需要一上来就开辟一块很大的内存空间。range会直接生成一个list对象,而xrange则不会直接生成一个list,而是每次调用返回其中的一个值。

13.什么叫闭包函数,有什么作用?

def foo():
    m, n=3, 5
    def bar():
        a=4
        return m+n+a
    return bar
>>>bar =  foo()
>>>bar()
12

bar在foo函数的代码块中定义。我们称bar是foo的内部函数。

在bar的局部作用域中可以直接访问foo局部作用域中定义的m、n变量。
简单的说,这种内部函数可以使用外部函数变量的行为,就叫闭包。

  • 闭包的意义与应用: 延迟计算;
  • 闭包的意义: 返回的函数对象,不仅仅是一个函数对象,在该函数外还包裹了一层作用域,这使得,该函数无论在何处调用,优先使用自己外层包裹的作用域
    应用领域:延迟计算(原来我们是传参,现在我们是包起来)
    装饰器就是闭包函数的一种应用场景

14.内置函数map
介绍:
会根据提供的函数对指定序列做映射。
第一个参数 function 以参数序列中的每一个元素调用 function 函数,返回包含每次 function 函数返回值的新列表。
语法:
map(function, iterable, …)
- function – 函数,有两个参数
- iterable – 一个或多个序列
应用示例:


>>>def square(x) :            # 计算平方数
...     return x ** 2
... 
>>> map(square, [1,2,3,4,5])   # 计算列表各个元素的平方
[1, 4, 9, 16, 25]
>>> map(lambda x: x ** 2, [1, 2, 3, 4, 5])  # 使用 lambda 匿名函数
[1, 4, 9, 16, 25]

# 提供了两个列表,对相同位置的列表数据进行相加
>>> map(lambda x, y: x + y, [1, 3, 5, 7, 9], [2, 4, 6, 8, 10])
[3, 7, 11, 15, 19]

15.必会内置函数 - filter
介绍:
函数用于过滤序列,过滤掉不符合条件的元素,返回由符合条件元素组成的新列表。
该接收两个参数,第一个为函数,第二个为序列,序列的每个元素作为参数传递给函数进行判,然后返回 True 或 False,最后将返回 True 的元素放到新列表中。
语法:
filter(function, iterable)
- function – 判断函数。
- iterable – 可迭代对象。
应用示例1:过滤出列表中的所有奇数:

 def is_odd(n):
        return n % 2 == 1
    newlist = filter(is_odd, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
        print(newlist)
    >>>[1, 3, 5, 7, 9]

应用示例2:过滤出1~100中平方根是整数的数

 import math
    def is_sqr(x):
        return math.sqrt(x) % 1 == 0
    newlist = filter(is_sqr, range(1, 101))
    print(newlist)
    >>>[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]

应用示例3:filter相较于py2的区别

python2中返回的是过滤后的列表, 而python3中返回到是一个filter类
    filter类实现了__iter__和__next__方法, 可以看成是一个迭代器, 有惰性运算的特性, 相对python2提升了性能, 可以节约内存。
    a = filter(lambda x: x % 2 == 0, range(10))
    print(a)
    >>><filter object at 0x000001CC57668518>

16.必会内置函数 - zip
介绍:
函数用于将可迭代的对象作为参数,将对象中对应的元素打包成一个个元组,然后返回由这些元组组成的列表。
如果各个迭代器的元素个数不一致,则返回列表长度与最短的对象相同,利用 * 号操作符,可以将元组解压为列表。
语法:
zip([iterable, …])
- iterabl – 一个或多个迭代器;
返回值:
- 返回元组列表。

应用示例:

    >>>a = [1,2,3]
    >>> b = [4,5,6]
    >>> c = [4,5,6,7,8]
    >>> zipped = zip(a,b)     # 打包为元组的列表
    [(1, 4), (2, 5), (3, 6)]
    >>> zip(a,c)              # 元素个数与最短的列表一致
    [(1, 4), (2, 5), (3, 6)]
    >>> zip(*zipped)          # 与 zip 相反,可理解为解压,返回二维矩阵式
    [(1, 2, 3), (4, 5, 6)]

17.必会内置函数 - isinstance
介绍:
函数来判断一个对象是否是一个已知的类型,类似 type()。
语法:
isinstance(object, classinfo)
- object – 实例对象。
- classinfo – 可以是直接或间接类名、基本类型或者由它们组成的元组。
返回值:
如果对象的类型与参数二的类型(classinfo)相同则返回 True,否则返回 False。。
应用示例:

    >>>a = 2
    >>> isinstance (a,int)
    True
    >>> isinstance (a,str)
    False
    >>> isinstance (a,(str,int,list))    # 是元组中的一个返回 True
    True

18.isinstance() 与 type()的区别
介绍
1. type() 不会认为子类是一种父类类型,不考虑继承关系。
2. isinstance() 会认为子类是一种父类类型,考虑继承关系。
如果要判断两个类型是否相同推荐使用 isinstance()。
示例:

 class A:
        pass
    class B(A):
        pass

    isinstance(A(), A)    # returns True
    type(A()) == A        # returns True
    isinstance(B(), A)    # returns True
    type(B()) == A        # returns False

19.s和==的区别

  • is 比较的是两个实例对象是不是完全相同,它们是不是同一个对象,占用的内存地址是否相同。莱布尼茨说过:“世界上没有两片完全相同的叶子”,这个is正是这样的比较,
    比较是不是同一片叶子(即比较的id是否相同,这id类似于人的身份证标识)。
  • == 比较的是两个对象的内容是否相等,即内存地址可以不一样,内容一样就可以了。这里比较的并非是同一片叶子,可能叶子的种类或者脉络相同就可以了。
    默认会调用对象的 __eq__()方法。

20.请编写一个函数实现将IP地址转换成一个整数
如 10.3.9.12 转换规则为:
10 00001010
3 00000011
9 00001001
12 00001100
再将以上二进制拼接起来计算十进制结果:00001010 00000011 00001001 00001100 = ?

a = "10.3.9.12"
def func(ip):
    Iplist = ip.split(".")  # ['10', '3', '9', '12']
    res = " "
    temp = []
    for i in Iplist:        # <class 'str'>
        i = int(i)          # <class 'int'>
        i = bin(i)[2:]      # <class 'str'>
        temp.append(i.rjust(8, "0"))   # 右对齐,向左填充数据
    res = res.join(temp)
    return res

# 一行代码实现:
b = "".join([" ".join(str(bin(int(i))[2:]).rjust(8,"0") for i in a.split("."))])
print(func(a))

- "00001010 00000011 00001001 00001100"

21.用Python实现⼀个⼆分查找的函数 二分查找算法
简单的说,就是将一个列表先排序好,比如按照从小到大的顺序排列好,当给定一个数据,比如3,查找3在列表中的位置时,可以先找到列表中间的数li[middle]和3进行比较,当它比3小时,那么3一定是在列表的右边,反之,则3在列表的左边,比如它比3小,则下次就可以只比较[middle+1, end]的数,继续使用二分法,将它一分为二,直到找到3这个数返回或者列表全部遍历完成(3不在列表中)
优点:效率高,时间复杂度为O(logN);
缺点:数据要是有序的,顺序存储。

li = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

def search(someone, li):
    l = -1
    h = len(li)

    while l + 1 != h:
        m = int((l + h) / 2)
        if li[m] < someone:
            l = m
        else:
            h = m
    p = h
    if p >= len(li) or li[p] != someone:
        print("元素不存在")
    else:
        str = "元素索引为%d" % p
        print(str)

search(3, li)  # 元素索引为2

22.请⽤代码简答实现stack

1. Stack() 创建一个新的空栈
2. push(item) 添加一个新的元素item到栈顶
3. pop() 弹出栈顶元素
4. peek() 返回栈顶元素
5. is_empty() 判断栈是否为空
6. size() 返回栈的元素个数

class Stack(object):
    """栈"""
    def __init__(self):
         self.items = []

    def is_empty(self):
        """判断是否为空"""
        return self.items == []

    def push(self, item):
        """加入元素"""
        self.items.append(item)

    def pop(self):
        """弹出元素"""
        return self.items.pop()

    def peek(self):
        """返回栈顶元素"""
        return self.items[len(self.items)-1]

    def size(self):
        """返回栈的大小"""
        return len(self.items)

if __name__ == "__main__":
    stack = Stack()
    stack.push("hello")
    stack.push("world")
    stack.push("lcg")
    print stack.size()
    print stack.peek()
    print stack.pop()
    print stack.pop()
    print stack.pop()

23.内置函数:map、reduce、filter的用法和区别

map:遍历序列,对序列中每个元素进行操作,最终获取新的序列。
  - 每个元素增加100:
    - li = [11, 22, 33]
    - new_list = map(lambda a: a + 100, li)
  - 两个列表对应元素相加
    - li = [11, 22, 33]
    - sl = [1, 2, 3, 4]
    - new_list = map(lambda a, b: a + b, li, sl)

filter:对于序列中的元素进行筛选,最终获取符合条件的序列。
  - 获取列表中大于12的所有元素集合
    - li = [11, 22, 33]
    - new_list = filter(lambda arg: arg > 22, li)
    - # filter第一个参数为空,将获取原来序列

reduce:对于序列内所有元素进行累计操作。
#python3已经将reduce移到functools中
  - 获取序列所有元素的和
    - li = [11, 22, 33]
    - result = reduce(lambda arg1, arg2: arg1 + arg2, li)
  - # reduce的第一个参数,函数必须要有两个参数
  - # reduce的第二个参数,要循环的序列
  - # reduce的第三个参数,初始值

24.手写:列表推导式 + lambda表达式 :# 一行代码写出30以内所有能被3整除的数的平方

# 错误示例:不能使用列表生成式
    a = [lambda :i*i for i in range(31) if i%3 is 0]
# 错误调用方式: # 每次只会返回最后一个被循环的range(30)!
    >>>a
    [. at 0x000002C97... ,>> a[0]
    . at 0x000002C977B96BF8>
    >>> a[0]()
    900
# 正确示例:使用生成器迭代执行   # 注意括号!
    a = (lambda :i*i for i in range(31) if i%3 is 0) 
# 调用方式:
>>> a.__iter__
<method-wrapper '__iter__' of generator object at 0x000002C977AF5938>
>>> a.__iter__()
 at 0x000002C977AF5938>
>>> a.__iter__().__next__
<method-wrapper '__next__' of generator object at 0x000002C977AF5938>
>>> a.__iter__().__next__()
. at 0x000002C977B8CF28>
>>> a.__iter__().__next__()()
9

25.手写字典推导式

>>> mcase = {'a': 10, 'b': 34}
>>> mcase_frequency = {mcase[k]: k for k in mcase}
>>> print(mcase_frequency)
{10: 'a', 34: 'b'}

26.你知道哪些双下划线方法

- 双下划线:
  1. __getattr__:反射
     应用场景:
       - CBV
       - Django 配置文件
       - wtforms中的Form()实例化中 将"_fields中的数据封装到Form类中"
  2. __mro__:定义解析类继承的顺序
     应用场景:wtforms中 FormMeta中继承的优先级
  3. __dict__:用来存储对象属性的一个字典,其键为属性名,值为属性的值
     - __dict__ 与 dir()的区别:
       1. dir()是一个函数,返回值是list
       2. dir用来寻找一个对象的所有属性值,包括__dict__中的属性,__dict__是dir()的子集
  4. __new__ :
     - 当你继承一些不可变的class(比如int, str, tuple), 提供给你一个自定义这些类的实例化过程的途径。
     - 实现自定义 metaclass 
     应用场景:
       - wtforms 字段实例化时返回:不是StringField,而是UNboundField
       - rest_frameworkmany=Ture 中的序列化
       - 单例模式
  5. __call__:作用是使实例能够像函数一样被调用,同时不影响实例本身的生命周期, (__call__()不影响一个实例的构造和析构)
               但是__call__()可以用来改变实例的内部成员。
     __call____init__的区别
     应用场景:
       - FLask 请求的入口app.run()
       - 字段生成标签时:字段.__str__ ==> 字段.__call__ ==> 插件.__call__
  6. __iter__:
     迭代器为什么要一定实现__iter__方法(为什么要返回自身)
     应用场景:wtformsBaseForm中循环所有字段时自定义了__iter__方法

27.新式类与经典类的区别

新式类跟经典类的差别主要是以下几点:
  1. 新式类对象可以直接通过__class__属性获取自身类型:type
  2. 继承搜索的顺序发生了改变,经典类多继承属性搜索顺序 :
      - 先深入继承树左侧,再返回,开始找右侧;
      - 新式类多继承属性搜索顺序: 先水平搜索,然后再向上移动。
        ps:(经典类深度优先,新式类广度优先)
  3. 新式类增加了__slots__内置属性, 可以把实例属性的种类锁定到__slots__规定的范围之中。
  4. 新式类增加了__getattribute__方法

Python 2.x中默认都是经典类,只有显式继承了object才是新式类
Python 3.x中默认都是新式类,不必显式的继承object

28.深度优先和广度优先是什么

python的类可以继承多个类,python的类如果继承了多个类,那么其寻找的方法有两种:
  - 当类是经典类时:多继承情况下,会按照深度优先的方式查找
  - 当类是新式类时:多继承情况下,会按照广度优先的方式查找
简单点说就是:经典类是纵向查找,新式类是横向查找

29.match和search的区别

- re.match :只匹配字符串的开始,如果字符串开始不符合正则表达式,则匹配失败,函数返回None- re.search:匹配整个字符串,直到找到一个匹配。

30.贪婪匹配与非贪婪匹配

- 匹配0次或多次 <.*>
- 非贪婪匹配:匹配0次或1次 <.?>

31.介绍下垃圾回收机制:引用计数 / 分代回收 / 孤立引用环


引⽤计数机制的优点:
1、简单
2、实时性:⼀旦没有引⽤,内存就直接释放了。不⽤像其他机制等到特定时机。实时性还带来⼀个好处:处理回收内存的时间分摊到了平时。
引⽤计数机制的缺点:
1、维护引⽤计数
2、消耗资源循环引⽤
list1 = []; list2 =[]
list1.append(list2); list2.append(list1)
3、list1与list2相互引⽤,如果不存在其他对象对他们的引用,list1与list2的引用计数也仍然1,所占⽤的内存永远无法被回收,这将是致命的。
     对于如今的强⼤硬件,缺点1尚可接受,但是循环引⽤导致内存泄露,注定python会将引⼊新的回收机制。(分代收集)
有三种情况会触发垃圾回收:
1、当 gc 模块的计数器达到阀值的时候,自动回收垃圾
2、调⽤ gc.collect(),手动回收垃圾
3、程序退出的时候,python解释器来回收垃圾

32.从浏览器输入域名网址到看到页面都发生了什么

1.浏览器向DNS服务器询问域名对应的IP地址

2.得到域名对应的IP地址后,组装HTTP请求报文

3.由TCP协议将请求报文分割成多个报文段,可靠的传给对方

4.由IP协议搜索对方地址,并传送报文,此过程可能经过多个路由器

5.服务器端的TCP从对方接收到报文

6.服务器端的HTTP解析请求报文,处理并生成响应报文

7.按照相反的顺序,将响应报文发回给浏览器

8.浏览器根据响应报文解析数据(一般为html文档),并渲染页面,文档中也很可能引用了其他资源,这些资源同样使用HTTP协议想服务器请求

猜你喜欢

转载自blog.csdn.net/huang_yong_peng/article/details/82464077