美文网首页
Python面试题合集

Python面试题合集

作者: LandHu | 来源:发表于2018-08-23 08:38 被阅读0次

    Python基础

    1. 简述解释型和编译型编程语言?
    解释型语言编写的程序不需要编译,在执行的时候,专门有一个解释器能够将VB语言翻译成机器语言,每个语句都是执行的时候才翻译。这样解释型语言每执行一次就要翻译一次,效率比较低。

    用编译型语言写的程序执行之前,需要一个专门的编译过程,通过编译系统,把源高级程序编译成为机器语言文件,翻译只做了一次,运行时不需要翻译,所以编译型语言的程序执行效率高,但也不能一概而论,部分解释型语言的解释器通过在运行时动态优化代码,甚至能够使解释型语言的性能超过编译型语言。

    2 .Python解释器种类以及特点?

    • CPython
      当 从Python官方网站下载并安装好Python2.7后,就直接获得了一个官方版本的解释器:Cpython,这个解释器是用C语言开发的,所以叫 CPython,在命名行下运行python,就是启动CPython解释器,CPython是使用最广的Python解释器。

    • IPython
      IPython是基于CPython之上的一个交互式解释器,也就是说,IPython只是在交互方式上有所增强,但是执行Python代码的功能和CPython是完全一样的,好比很多国产浏览器虽然外观不同,但内核其实是调用了IE。

    • PyPy
      PyPy是另一个Python解释器,它的目标是执行速度,PyPy采用JIT技术,对Python代码进行动态编译,所以可以显著提高Python代码的执行速度。

    • Jython
      Jython是运行在Java平台上的Python解释器,可以直接把Python代码编译成Java字节码执行。

    • IronPython
      IronPython和Jython类似,只不过IronPython是运行在微软.Net平台上的Python解释器,可以直接把Python代码编译成.Net的字节码。

    在Python的解释器中,使用广泛的是CPython,对于Python的编译,除了可以采用以上解释器进行编译外,技术高超的开发者还可以按照自己的需求自行编写Python解释器来执行Python代码,十分的方便!

    3.PEP8 编码规范
    a.不要在行尾加分号, 也不要用分号将两条命令放在同一行。
    b.每行不超过80个字符(长的导入模块语句和注释里的URL除外)
    c.不要使用反斜杠连接行。Python会将圆括号, 中括号和花括号中的行隐式的连接起来
    d.宁缺毋滥的使用括号,除非是用于实现行连接, 否则不要在返回语句或条件语句中使用括号. 不过在元组两边使用括号是可以的.
    e.用4个空格来缩进代码,不要用tab, 也不要tab和空格混用. 对于行连接的情况, 你应该要么垂直对齐换行的元素,或者使用4空格的悬挂式缩进
    f.顶级定义之间空2行, 方法定义之间空1行,顶级定义之间空两行, 比如函数或者类定义. 方法定义, 类定义与第一个方法之间, 都应该空一行. 函数或方法中, 某些地方要是你觉得合适, 就空一行

    4. 三元运算规则以及应用场景?

    表达式格式为真时的结果 if 判定条件 else 为假时的结果事例1 if 3>2 else 0
    

    5.Python3和Python2中 int 和 long的区别?

    Python 2有为非浮点数准备的int和long类型。int类型的最大值不能超过sys.maxint,而且这个最大值是平台相关的。可以通过在数字的末尾附上一个L来定义长整型,显然,它比int类型表示的数字范围更大。在Python 3里,只有一种整数类型int,大多数情况下,它很像Python 2里的长整型。由于已经不存在两种类型的整数,所以就没有必要使用特殊的语法去区别他们。

    6.文件操作时:xreadlines和readlines的区别?
    read(size)
    读入指定大小的内容,以byte为单位,size为读入的字符数,返回str类型
    readline()
    readline()读取一行内容,放到一个字符串变量,返回str类型。
    readlines()
    readlines() 读取文件所有内容,按行为单位放到一个列表中,返回list类型。
    xreadlines()
    返回一个生成器,来循环操作文件的每一行。循环使用时和readlines基本一样,但是直接打印就不同

    7 简述Python的深浅拷贝以及应用场景?
    可变对象-不可变对象
    在Python中不可变对象指:一旦创建就不可修改的对象,包括字符串,元祖,数字
    在Python中可变对象是指:可以修改的对象,包括:列表、字典

    >>> L1 = [2,3,4]      #L1变量指向的是一个可变对象:列表  
    >>> L2 = L1           #将L1值赋给L2后,两者共享引用同一个列表对象[1,2,3,4]  
    >>> L1[0] = 200       #因为列表可变,改变L1中第一个元素的值  
    >>> L1; L2            #改变后,L1,L2同时改变,因为对象本身值变了  
    [200, 3, 4]  
    [200, 3, 4]
    

    如果不想改变列表L2的值,有两种方法:切片 和 copy模块

    >>> L1 = [2,3,4]   
    >>> L2 = L1  
    >>> id(L1);id(L2)     #共享引用一个可变对象  
    45811784L  
    45811784L  
    >>> L2 = L1[:]        #切片操作  
    >>> id(L1);id(L2)     #切片后,对象就不一样了  
    45811784L  
    45806920L  
    >>> L1[0] = 200  
    >>> L1;L2             #L1发生改变,L2没有变化  
    [200, 3, 4]  
    [2,   3, 4]
    

    拷贝
    切片技术应用于所有的序列,包括:列表、字符串、元祖
    但切片不能应用于字典。对字典只能使用D.copy()方法或D.deepcopy()方法.

    image
    深浅拷贝,即可用于序列,也可用于字典
    import copy
    X = copy.copy(Y)      #浅拷贝:只拷贝顶级的对象,或者说:父级对象
    X = copy.deepcopy(Y)  #深拷贝:拷贝所有对象,顶级对象及其嵌套对象。或者说:父级对象及其子对象
    

    如果字典只有顶级对象:


    image

    如果字典中嵌套对象:


    image

    结论

    • 深浅拷贝都是对源对象的复制,占用不同的内存空间
    • 如果源对象只有一级目录的话,源做任何改动,不影响深浅拷贝对象
    • 如果源对象不止一级目录的话,源做任何改动,都要影响浅拷贝,但不影响深拷贝
    • 序列对象的切片其实是浅拷贝,即只拷贝顶级的对象

    8.什么是正则的贪婪匹配?
    如:String str="abcaxc";
       Patter p="ab.c";
      贪婪匹配:正则表达式一般趋向于最大长度匹配,也就是所谓的贪婪匹配。如上面使用模式p匹配字符串str,结果就是匹配到:abcaxc(ab.
    c)。
      非贪婪匹配:就是匹配到结果就好,就少的匹配字符。如上面使用模式p匹配字符串str,结果就是匹配到:abc(ab.c)。
    编程中如何区分两种模式
      默认是贪婪模式;在量词后面直接加上一个问号?就是非贪婪模式。
      量词:{m,n}:m到n个
         
    :任意多个
         +:一个到多个
         ?:0或一个

    算法

    1. 通过代码实现如下转换:

    二进制转换成十进制:v = “0b1111011”
    #先将其转换为字符串,再使用int函数,指定进制转换为十进制。
    print(int("0b1111011",2))
    值为123
    
    十进制转换成二进制:v = 18 
    print("转换为二进制为:", bin(18))
    #转换为二进制为: 0b10010
    
    八进制转换成十进制:v = “011” 
    print(int("011",8))
    #9
    
    十进制转换成八进制:v = 30
    print("转换为八进制为:", oct(30))
    #转换为八进制为: 0o36
    
    十六进制转换成十进制:v = “0x12” 
    print(int("0x12",16))
    #18
    
    十进制转换成十六进制:v = 87
    print("转换为十六进制为:", hex(87))
    转换为十六进制为: 0x57
    

    2. python递归的最大层数?

    def fab(n):if n == 1:return 1else:return fab(n-1)+ nprint (fab(998))
    #得到的最大数为998,以后就是报错了,998这个数值莫名想起广告词····
    
    import syssys.setrecursionlimit(100000)
    def foo(n):   
    print(n)   
    n += 1  
    foo(n)
    
    if __name__ == '__main__':   
    foo(1)
    #得到的最大数字在3922-3929之间浮动,这个是和计算机有关系的,将数字调到足够大了,已经大于系统堆栈,python已经无法支撑到太大的递归崩了。
    

    3. 一行代码实现99乘法表*

    print ("\n".join("\t".join(["%s*%s=%s" %(x,y,x*y) for y in range(1, x+1)]) for x in range(1, 10)))
    ==================================================
    1*1=1
    2*1=2  2*2=4
    3*1=3  3*2=6  3*3=9
    4*1=4  4*2=8  4*3=12  4*4=16
    5*1=5  5*2=10  5*3=15  5*4=20  5*5=25
    6*1=6  6*2=12  6*3=18  6*4=24  6*5=30  6*6=36
    7*1=7  7*2=14  7*3=21  7*4=28  7*5=35  7*6=42  7*7=49
    8*1=8  8*2=16  8*3=24  8*4=32  8*5=40  8*6=48  8*7=56  8*8=64
    9*1=9  9*2=18  9*3=27  9*4=36  9*5=45  9*6=54  9*7=63  9*8=72  9*9=81
    

    4.求结果

    l = [i % 2 for i in range(10)]
    print(l)
    t = (i % 2 for i in range(10))
    print(t)
    =============================
    [0, 1, 0, 1, 0, 1, 0, 1, 0, 1]
    <generator object <genexpr> at 0x000000000271CB10>
    

    5.求结果

    print(1 or 2)
    print(1 and 2)
    print(1 < (2==2))
    print(1 < 3 == 3)
    print(True == 3)
    print(True == 1)
    =============================
    1
    2
    False
    True
    False
    True
    

    6.求结果:

    v = dict.fromkeys(['k1','k2'],[])
    v['k1'].append('000')
    print(v)
    v['k1']=666
    print(v)
    
    {'k1': ['000'], 'k2': ['000']}
    {'k1': 666, 'k2': ['000']}
    

    7.求结果

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

    以上代码的输出是 [6, 6, 6, 6] (而不是 [0, 2, 4, 6])。
    这个的原因是 Python 的闭包的后期绑定导致的 late binding,这意味着在闭包中的变量是在内部函数被调用的时候被查找。所以结果是,当任何 multipliers() 返回的函数被调用,在那时,i 的值是在它被调用时的周围作用域中查找,到那时,无论哪个返回的函数被调用,for 循环都已经完成了,i 最后的值是 3,因此,每个返回的函数 multiplies 的值都是 3。因此一个等于 2 的值被传递进以上代码,它们将返回一个值 6 (比如: 3 x 2)。
    (顺便说下,正如在 The Hitchhiker’s Guide to Python 中指出的,这里有一点普遍的误解,是关于 lambda 表达式的一些东西。一个 lambda 表达式创建的函数不是特殊的,和使用一个普通的 def 创建的函数展示的表现是一样的。)
    这里有两种方法解决这个问题。
    最普遍的解决方案是创建一个闭包,通过使用默认参数立即绑定它的参数。例如:

    def num():       
    return [lambda x, i=i : i * x for i in range(4)]
    

    另外一个选择是,你可以使用 functools.partial 函数:

    from functools 
    import partialfrom operator import mul
    def num():   
    return [partial(mul, i) for i in range(4)]
    

    9 如何实现 “1,2,3” 变成 [‘1’,’2’,’3’] ?

    L = [1,2,3]NL =list(map(str,L))
    print(NL)
    

    10 1、2、3、4、5 能组成多少个互不相同且无重复的三位数

    for x in range(1,5):
       for y in range(1,5):
           for z in range(1,5):
                   if (x!=y) and (y!=z) and (z!=x):
                           print("%d%d%d" % (x, y, z))
    

    11.用一行代码实现数值交换:

    a =1
    b =2
    a,b=b,a
    print(a,b)
    

    函数

    1.列举布尔值为False的常见值?
    下面的值在作为布尔表达式的时候,会被解释器看作假(false)
    False None 0 "" () [] {}
    换句话说,也就是标准值False和None,所有类型的数字0(包括浮点型,长整型和其他类型),空序列(比如空字符串、元组和列表)以及空的字典都为假。其他的一切都被解释为真,包括特殊值True.
    也就是说Python中的所有值都能被解释为真值。”标准的“布尔值为True和False。事实上,True和False只不过是1和0的一种“华丽”的说法而已----看起来不同,但是作用相同。

    2.lambda表达式格式以及应用场景?
    对于简单的函数,也存在一种简便的表示方式,即:lambda表达式

    #普通函数
    def func(a):
       return a+1
    print 'test1_func0:',func(1000)
    #lambda表达式 
    func0 = lambda a:a+1
    print 'test2_func0:',func0(1000)
    

    上面这种方法,都实现了将1000+1的结果打印出来这个功能,但是用下面
    lambda存在意义就是对简单函数的简洁表示。
    说道lambda,这里再赠送一些可以给lambda加buff小伙伴:
    map函数
    我们使用map函数将会对列表中的所有元素进行操作。map有两个参数(函数,列表),它会在内部遍历列表中的每一个元素,执行传递过来的函数参数。在输出到新列表中。

    li = [11, 22, 33]
    new_list = map(lambda a: a + 100, li)
    输出:[111, 122, 133]
    

    reduce函数
    对于序列内所有元素进行累计操作:

    lst = [11,22,33]
    func2 = reduce(lambda arg1,arg2:arg1+arg2,lst)
    print 'func2:',func2
    输出:func2: 66
    

    filter函数
    他可以根据条件对数据进行过滤:

    li = [11, 22, 33]
    new_list = filter(lambda arg: arg > 22, li)
    print new_list
    输出:[33]
    

    divmod()
    函数把除数和余数运算结果结合起来,返回一个包含商和余数的元组(a // b, a % b)。

    >>>divmod(7, 2)
    (3, 1)
    >>> divmod(8, 2)
    (4, 0)
    

    zip() 函数
    用于将可迭代的对象作为参数,将对象中对应的元素打包成一个个元组,然后返回由这些元组组成的列表。

    >>>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 相反,*zipped 可理解为解压,返回二维矩阵式
    [(1, 2, 3), (4, 5, 6)]
    

    3.arg和kwarg作用

    首先我们可以定一个简单的函数, 函数内部只考虑required_arg这一个形参(位置参数)

    def exmaple(required_arg):
       print required_arg
    exmaple("Hello, World!")
    >> Hello, World!
    

    那么,如果我们调用函数式传入了不止一个位置参数会出现什么情况?当然是会报错!
    arg和*kwarg 可以帮助我们处理上面这种情况,允许我们在调用函数的时候传入多个实参

    def exmaple2(required_arg, *arg, **kwarg):
       if arg:
           print "arg: ", arg
       if kwarg:
           print "kwarg: ", kwarg
    exmaple2("Hi", 1, 2, 3, keyword1 = "bar", keyword2 = "foo")
    >> arg:  (1, 2, 3)
    >> kwarg:  {'keyword2': 'foo', 'keyword1': 'bar'}
    

    从上面的例子可以看到,当我传入了更多实参的时候

    • *arg会把多出来的位置参数转化为tuple
    • **kwarg会把关键字参数转化为dict

    4. is和==的区别
    在讲is和==这两种运算符区别之前,首先要知道Python中对象包含的三个基本要素,分别是:id(身份标识)、type(数据类型)和value(值)。
    is和==都是对对象进行比较判断作用的,但对对象比较判断的内容并不相同。下面来看看具体区别在哪。
    ==比较操作符和is同一性运算符区别
    ==是python标准操作符中的比较操作符,用来比较判断两个对象的value(值)是否相等,例如下面两个字符串间的比较:

    >>> a = 'cheesezh'
    >>> b = 'cheesezh'
    >>> a == b
    True
    

    is也被叫做同一性运算符,这个运算符比较判断的是对象间的唯一身份标识,也就是id是否相同。通过对下面几个list间的比较,你就会明白is同一性运算符的工作原理:

    >>> x = y = [4,5,6]
    >>> z = [4,5,6]
    >>> x == y
    True
    >>> x == z
    True
    >>> x is y
    True
    >>> x is z
    False
    >>>
    >>> print id(x)
    >>> print id(y)
    >>> print id(z)
    

    5.列举常见的内置函数?

    6.re的match和search区别?
    match()函数只检测RE是不是在string的开始位置匹配,search()会扫描整个string查找匹配;也就是说match()只有在0位置匹配成功的话才有返回,
    如果不是开始位置匹配成功的话,match()就返回none。
    例如:
    print(re.match(‘super’, ‘superstition’).span()) 会返回(0, 5)
    而print(re.match(‘super’, ‘insuperable’)) 则返回None
    search()会扫描整个字符串并返回第一个成功的匹配
    例如:print(re.search(‘super’, ‘superstition’).span())返回(0, 5)

    7.def func(a,b=[]) 这种写法有什么坑?

    def func(a,b=[]):
       b.append(a)
       print(b)
    func(1)
    func(1)
    func(1)
    func(1)
    =================================
    [1]
    [1, 1]
    [1, 1, 1]
    [1, 1, 1, 1]
    

    函数的第二个默认参数是一个list,当第一次执行的时候实例化了一个list,第二次执行还是用第一次执行的时候实例化的地址存储,所以三次执行的结果就是 [1, 1, 1] ,想每次执行只输出[1] ,默认参数应该设置为None。

    Python底层

    1. Python垃圾回收机制?

    import sys sys.getrefcount()查看引用计数
    字符串中间有空格!?等会重新创建新的字符串
    总结
    1. 小整数[-5,257)共用对象,常驻内存,不会被释放。
    2. 单个字符共用对象,常驻内存。
    3. 单个单词,不可修改,默认开启intern机制,共用对象,引用计数为0,则销毁 。
    4. 大整数不共用内存,引用计数为0,销毁 .
    5. 数值类型和字符串类型在 Python 中都是不可变的,这意味着你无法修改这个对象的值,每次对变量的修改,实际上是创建一个新的对象 .
    Garbage collection(GC垃圾回收)
    python采用的是引用计数机制为主,标记-清除和分代收集(隔代回收、分代回收)两种机制为辅的策略

    引用计数机制的优点:

    1、简单
    2、实时性:一旦没有引用,内存就直接释放了。不用像其他机制等到特定时机。实时性还带来一个好处:处理回收内存的时间分摊到了平时。
    引用计数机制的缺点:
    维护引用计数消耗资源
    循环引用,解决不了

    gc模块

    3.1. 垃圾回收机制
    导致引用计数+1的情况
    1.对象被创建,例如a = "hello"
    2.对象被引用,例如b=a
    3.对象被作为参数,传入到一个函数中,例如func(a)
    4.对象作为一个元素,存储在容器中,例如list1=[a,a]
    1. 常用函数
    1、gc.set_debug(flags) 设置gc的debug日志,一般设置为gc.DEBUG_LEAK
    2、gc.collect([generation]) 显式进行垃圾回收,可以输入参数,0代表只检查零代的对象,1代表检查零,一代的对象,2代表检查零,一,二代的对象,如果不传参数,执行一个full collection,也就是等于传2。 在python2中返回不可达(unreachable objects)对象的数目
    3、gc.get_threshold() 获取的gc模块中自动执行垃圾回收的频率。
    4、gc.set_threshold(threshold0[, threshold1[, threshold2]) 设置自动执行垃圾回收的频率。
    5、gc.get_count() 获取当前自动执行垃圾回收的计数器,返回一个长度为3的列表
    Python的GC模块主要运用了引用计数来跟踪和回收垃圾。在引用计数的基础上,还可以通过“标记-清除”解决容器对象可能产生的循环引用的问题。通过分代回收以空间换取时间进一步提高垃圾回收的效率。

    标记-清除

    标记-清除的出现打破了循环引用,也就是它只关注那些可能会产生循环引用的对象
    缺点:该机制所带来的额外操作和需要回收的内存块成正比。
    一旦这个差异累计超过某个阈值(700,10,10),则Python的收集机制就启动了,并且触发上边所说到的零代算法释放“浮动的垃圾”,并且将剩下的对象移动到一代列表。随着时间的推移,程序所使用的对象逐渐从零代列表移动到一代列表。通过这种方法,你的代码所长期使用的对象,那些你的代码持续访问的活跃对象,会从零代链表转移到一代再转移到二代。通过不同的阈值设置,Python可以在不同的时间间隔处理这些对象。Python处理零代最为频繁,其次是一代然后才是二代。

    隔代回收

    原理:将系统中的所有内存块根据其存活时间划分为不同的集合,每一个集合就成为一个“代”,垃圾收集的频率随着“代”的存活时间的增大而减小。也就是说,活得越长的对象,就越不可能是垃圾,就应该减少对它的垃圾收集频率。那么如何来衡量这个存活时间:通常是利用几次垃圾收集动作来衡量,如果一个对象经过的垃圾收集次数越多,可以得出:该对象存活时间就越长。
    dir(builtins)查看内建属性
    getattribute内建属性。属性访问拦截器(方法和属性都可以被拦截),可以返回一个值:以后不要在getattribute方法中调用self.xxxx会引起递归时程序死掉
    map函数会根据提供的函数对指定序列做映射返回值是列表
    map(function, sequence[, sequence, ...]) -> list
    • function:是一个函数
    • sequence:是一个或多个序列,取决于function需要几个参数
    • 返回值是一个list
    filter函数python3返回的是生产器filter函数会对指定序列执行过滤操作
    filter(function or None, sequence) -> list, tuple, or string
    • function:接受一个参数,返回布尔值True或False
    • sequence:序列可以是str,tuple,list
    list(filter(lambda x x%2==0,[1,2,3,4,5,6])---->[2,4,6]
    sorted函数-排序
    sorted(iterable, reverse=False) --> new sorted list
    functools模块import functools
    partial函数(偏函数)把一个函数的某些参数设置默认值,返回一个新的函数,调用这个新函数会更简单。
    wraps函数 使用装饰器时,让外界看被装饰的函数时内容一致。
    例如,被装饰后的函数其实已经是另外一个函数了(函数名等函数属性会发生改变)。
    functools.wraps(func)

    2、Python是如何进行内存管理的?
    答:从三个方面来说,一对象的引用计数机制,二垃圾回收机制,三内存池机制
    一、对象的引用计数机制
    Python内部使用引用计数,来保持追踪内存中的对象,所有对象都有引用计数。

    引用计数增加的情况:
    1、一个对象分配一个新名称
    2、将其放入一个容器中(如列表、元组或字典)

    引用计数减少的情况:
    1、使用del语句对对象别名显示的销毁
    2、引用超出作用域或被重新赋值
    sys.getrefcount( )函数可以获得对象的当前引用计数

    多数情况下,引用计数比你猜测得要大得多。对于不可变数据(如数字和字符串),解释器会在程序的不同部分共享内存,以便节约内存。

    二、垃圾回收

    1、当一个对象的引用计数归零时,它将被垃圾收集机制处理掉。
    2、当两个对象a和b相互引用时,del语句可以减少a和b的引用计数,并销毁用于引用底层对象的名称。然而由于每个对象都包含一个对其他对象的应用,因此引用计数不会归零,对象也不会销毁。(从而导致内存泄露)。为解决这一问题,解释器会定期执行一个循环检测器,搜索不可访问对象的循环并删除它们。

    三、内存池机制
    Python提供了对内存的垃圾收集机制,但是它将不用的内存放到内存池而不是返回给操作系统。

    1、Pymalloc机制。为了加速Python的执行效率,Python引入了一个内存池机制,用于管理对小块内存的申请和释放。

    2、Python中所有小于256个字节的对象都使用pymalloc实现的分配器,而大的对象则使用系统的malloc。

    3、对于Python对象,如整数,浮点数和List,都有其独立的私有内存池,对象间不共享他们的内存池。也就是说如果你分配又释放了大量的整数,用于缓存这些整数的内存就不能再分配给浮点数。


    技术讨论 & 疑问建议 & 个人博客

    版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 3.0 许可协议,转载请注明出处!

    参考
    https://www.itcodemonkey.com/article/6719.html

    相关文章

      网友评论

          本文标题:Python面试题合集

          本文链接:https://www.haomeiwen.com/subject/eoceiftx.html