python后端开发面试常见问题
大家好,我叫亓官劼(qí guān jié ),在GitHub和CSDN中记录学习的点滴历程,时光荏苒,未来可期,一起加油~~
本篇文章将在GitHub和CSDN上持续更新,主要是Python后端开发的一些常见问题,包括Python的一些基础知识,以及面试中常问的计网,数据库,数据结构等一些算法题,总体覆盖面试的大多数问题。
本文的GitHub地址为:
python-development-interview-FAQ
CSDN暂未发布,发布时再加上链接。
如果有帮助的话,可以在GitHub上点个star,支持下。相对来说GitHub更新要比CSDN上更新更快一些。
创建了个交流群,如果需要可以加群一起交流,Q群545611263(为了避免广告小号,设置了0.1的付费群),也可以加我V:qiguanjie2015
1 Python类中的方法类型
在Python类中有四种方法类型,分别是实例方法、静态方法、类方法和普通方法。
实例方法(即对象方法):需要实例化对象之后才能调用,接受的第一个参数
self
就是对象本身,必须使用实例化对象才可以访问,不能通过类直接访问.
静态方法:可以通过类名直接调用,不需要传递
self
和
cls
;也可以在实例化对象后调用
类方法:可以通过类名调用,也可以在实例化对象后调用。类方法需要一个
cls
参数,在调用时自动传递
普通方法:和正常的函数一样,可以直接调用,但是在类中不建议写这种方法
测试示例:
class A ( object ) :
def instance_method_fun ( self) :
print ( "instance_method_fun,self is {}" . format ( self) )
@classmethod
def classmethod_fun ( cls) :
print ( "classmethod_fun, cls is {}" . format ( cls) )
@staticmethod
def staticmethod_fun ( ) :
print ( "staticmethod_fun" )
def common_fun ( ) :
print ( "common_fun" )
A. classmethod_fun( )
A. staticmethod_fun( )
A. common_fun( )
a = A( )
a. instance_method_fun( )
a. classmethod_fun( )
a. staticmethod_fun( )
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
2 Python的参数传递类型
Python中的参数传递是引用传递,即我们可以对传递对象的属性,但是不能改变传递对象的指针。在Python中有一些对象的值是不可以更改的,例如int,float类型。如果在函数体内修改了不可修改对象的值,Python会在一个新的内存地址上创建一个变量,而不是使用原来的变量;如果在函数体内修改一个可修改对象的值,则在原内存地址操作。
例如:
def fun1 ( x) :
x = x + 1
print ( "x:{},id(x):{}" . format ( x, id ( x) ) )
def fun2 ( x) :
print ( "b:{},id(b):{}" . format ( b, id ( b) ) )
x. append( 2 )
print ( "b:{},id(b):{}" . format ( b, id ( b) ) )
a = 1
print ( "a:{},id(a):{}" . format ( a, id ( a) ) )
fun1( a)
print ( "a:{},id(a):{}" . format ( a, id ( a) ) )
b = [ ]
print ( "b:{},id(b):{}" . format ( b, id ( b) ) )
fun2( b)
print ( "b:{},id(b):{}" . format ( b, id ( b) ) )
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
3 协程
这个是在前不久的面试中才知道有协程这个概念,其实也可以理解为用户线程,相比较内核线程而言,用户线程更加的灵活,并且减少了进出内核态的消耗,缺点是无法利用多核CPU的并行优势。
4 Python命名中的单下划线(_)和双下划线(__)
在Python中,双下划线开头和结尾的命名默认为Python的内部变量/方法,用以区分用户变量。例如场景的
__init__()
,
__dict__
,
__dir__
等。
单下划线开头的命名默认为私有变量,不会在
from a import *
中被导入
双下划线开头,但是没有下划线结尾的命名,Python在解释的时候会默认对其进行重命名为
_类名__变量
。
class A ( ) :
def __init__ ( self) - > None :
self. _b = "self._b"
self. __c = "self.__c"
a = A( )
print ( a. _b)
print ( a. __dict__)
print ( a. _A__c)
在Python中,当一个文件夹下有一个
__init__.py
文件,则Python会识别这个文件夹为一个Python包
5 python字符串传参 %s和format
%s和format的区别在于,format可以传递列表、元组等类型,而%s不可以,所以在日常使用时,使用format更加方便
6 python 迭代器和生成器
迭代器是python十分强大的一个功能,迭代器是一个可以记住遍历位置的对象。例如:
a = [ 1 , 2 , 3 , 4 ]
it = iter ( a)
print ( next ( it) )
print ( next ( it) )
我们也可以使用迭代器来生成一个我们需要的列表,例如:
a = [ i* i for i in range ( 1 , 9 ) ]
print ( a)
在这里如果我们将外面的
[]
改为
()
,a获取的将会是一个生成器,而不是迭代器。在python中,使用yield的函数被称为生成器,生成器返回的是一个迭代器的函数,只能用于迭代操作,在调用生成器运行的过程中,每次遇到 yield 时函数会暂停并保存当前所有的运行信息,返回 yield 的值, 并在下一次执行 next() 方法时从当前位置继续运行。
生成器不保留所有的数据信息,而是指记录当前运行的信息。因此生成器就不能直接获取某个下表的值,而是要通过
next()
或者
sent()
,亦或是迭代器来依次获取值。
例如:
a = ( i* i for i in range ( 1 , 9 ) )
print ( a)
it = iter ( a)
print ( next ( a) )
print ( next ( a) )
print ( next ( a) )
print ( next ( a) )
print ( "=================" )
for item in a:
print ( item)
生成器常用场景:例如我们需要生成一个有规律向前推进的列表,或者一个从1到1亿的列表等等,当我们列表中元素数量十分大时,内存会爆栈。但是如何我们元素间是有规律的,则我们可以利用生成器来解决这个问题。
有需要的话可以参考这篇文章:
学点简单的Python之Python生成器
7 python 装饰器
装饰器是一个十分常用的东西,经常被用在有切面需求的场景,大家耳熟能详的AOP就是这个,装饰器的主要功能就是为已经存在的函数提供一些可复用的定制化功能。
装饰器包含很多内容,需要系统的去看,这里不展开。如有需要,可以参考这篇文章:
学点简单的Python之Python装饰器与闭包
8 python 变量中的作用域
python中变量总是默认本地变量,如果没有,则会创建一个本地变量。在函数中如果要使用全局变量,需要使用
gloabl
进行声明。例如:
a = 5
def fun ( ) :
global a
a = a + 1
fun( )
print ( a)
9 python 闭包
python闭包与其他语言的闭包的意思是一样的,即我们在函数定义中引用了函数外定义的变量,并且该函数可以在其定义环境外被执行。简单来说,就是在函数内定义函数,且函数内部定义的函数中使用了外部函数中的变量,且内部函数可以单独的运行。
这里用语言来描述还是比较的绕,我们可以通过下面这个小例子来更直观的理解:
def extern_func ( ) :
list = [ ]
def inner_func ( name) :
list . append( name)
print ( list )
return inner_func
ret1 = extern_func( )
ret1( 'zhangsan' )
ret1( 'lisi' )
ret2 = extern_func( )
ret2( 'wangwu' )
ret1( 'qiguanjie' )
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
输出为:
[ 'zhangsan' ]
[ 'zhangsan' , 'lisi' ]
[ 'wangwu' ]
[ 'zhangsan' , 'lisi' , 'qiguanjie' ]
我们发现
ret1
和
ret2
中虽然都是在
list
中添加一个
name
并返回,但是
ret1
和
ret2
是两次调用
extern_func( )
返回的函数体,他们作用的list是不同的,他们作用的
list
可以脱离原函数
extern_func()
而单独使用。这就是函数闭包的简单使用。简而言之就是我们在函数中定义函数,并且使用了外部函数中的部分变量,我们内部的函数在脱离外部函数之后继续执行,且单独作用于外部函数的部分变量。
闭包一个常见错误:
我们看下面这个例子
def func ( ) :
list = [ ]
for i in range ( 3 ) :
def inner_func ( ) :
return i* i
list . append( inner_func)
return list
re1, re2, re3= func( )
print ( re1( ) )
print ( re2( ) )
print ( re3( ) )
大家是不是以为三个输出的结果应该是
0,1,4
?但实际上输出的结果都是
4
,这是为什么呢?这里我们的i对于
inner_func
来说是一个外部函数,我们在
list
中添加是是
inner_func
的函数体,里面返回的是
i*i
,但是当我们i变化到2之后,我们才返回list,所以我们输出的三个值才都是
4
,那如何避免这种情况呢?
第一种方法,区分变量
我们使用
_i
来区分
i
def func ( ) :
list = [ ]
for i in range ( 3 ) :
def inner_func ( _i = i) :
return _i* _i
list . append( inner_func)
return list
re1, re2, re3= func( )
print ( re1( ) )
print ( re2( ) )
print ( re3( ) )
这里我们在
inner_func
的参数中定义
_i
变量,值为当前的
i
,这时我们将函数体加入
list
中,就可以保证值不受
i
值变化的影响,输出为0,1,4。另一种方法就是我们直接在
list
中存放计算好的值,而不是函数体(这种方法有一定的局限性,和之前的方法就是两种完全不同的方法了):
def func ( ) :
list = [ ]
for i in range ( 3 ) :
def inner_func ( ) :
return i* i
list . append( inner_func( ) )
return list
re1, re2, re3= func( )
print ( re1( ) )
print ( re2( ) )
print ( re3( ) )
123456789101112
这里我们这里添加到list中的是
inner_func()
,一旦有
()
则表明我们这个函数已经运行了,返回的是一个值。
第二种方法 nonlocal关键字
那如果我们要在内部函数中使用外部函数中的变量,并进行修改我们应该如何做呢?
def extern_func ( name) :
print ( 'exter_func name is : %s' % name)
def inner_func ( ) :
name = 'inner_func ' + name
print ( 'inner_func name is : %s' % name)
return inner_func
ret = extern_func( 'qiguanjie' ) ( )
如何我们直接修改的话,我们会发现这里编译会报错:
UnboundLocalError: local variable 'name' referenced before assignment
这里报错的意思即我们这里的
name
变量在使用前未被分配,这就和我们在函数内使用全局变量时要使用
globle
关键字一样,这里在内部函数中要使用并更改外部函数中的变量,我们需要使用关键字
nonlocal
,对程序进行修改:
def extern_func ( name) :
print ( 'exter_func name is : %s' % name)
def inner_func ( ) :
name = 'inner_func ' + name
print ( 'inner_func name is : %s' % name)
return inner_func
ret = extern_func( 'qiguanjie' ) ( )
此时程序顺利编译,输出结果为:
exter_func name is : qiguanjie
inner_func name is : inner_func qiguanjie
10 python lambda函数
lambda函数即匿名函数,在很多场合都可以使用。lambda 函数比较轻便,即用即仍,很适合需要完成一项功能,但是此功能只在此一处使用,连名字都很随意的情况下。
例如在sort函数中指定排序的
key
:
a = [ { "a" : 13 , "b" : 25 , "c" : 62 } , { "a" : 63 , "b" : 215 , "c" : 612 } , { "a" : 3 , "b" : 634 , "c" : 216 } ]
a. sort( key= lambda x: x[ 'a' ] )
print ( a)
a. sort( key= lambda x: x[ 'b' ] )
print ( a)
a. sort( key= lambda x: x[ 'c' ] )
print ( a)
11 python中的深拷贝与浅拷贝
在浅拷贝时,拷贝出来的新对象的地址和原对象是不一样的,但是新对象里面的可变元素(如列表)的地址和原对象里的可变元素的地址是相同的,也就是说浅拷贝它拷贝的是浅层次的数据结构(不可变元素),对象里的可变元素作为深层次的数据结构并没有被拷贝到新地址里面去,而是和原对象里的可变元素指向同一个地址,所以在新对象或原对象里对这个可变元素做修改时,两个对象是同时改变的,但是深拷贝不会这样,这个是浅拷贝相对于深拷贝最根本的区别。
在深拷贝时,会只拷贝所有元素的值,包括可变对象,也仅拷贝对象中的值,而不是地址。
import copy
a = [ 1 , 2 , 3 , 4 , [ 'a' , 'b' , 'c' ] ]
b = a
c = copy. copy( a)
d = copy. deepcopy( a)
a. append( 5 )
a[ 4 ] . append( 'd' )
print ( "a:{}, id(a):{}, id(a[4]):{}" . format ( a, id ( a) , id ( a[ 4 ] ) ) )
print ( "b:{}, id(b):{}, id(b[4]):{}" . format ( b, id ( b) , id ( b[ 4 ] ) ) )
print ( "c:{}, id(c):{}, id(c[4]):{}" . format ( c, id ( c) , id ( c[ 4 ] ) ) )
print ( "d:{}, id(d):{}, id(d[4]):{}" . format ( d, id ( d) , id ( d[ 4 ] ) ) )
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
12 Python中*args和**kwargs
*args
表示传一个元组给函数,可以同时传递多个参数,这里的
args
可以替换为其他名称,前面加一个
*
即可,例如:
*para
都可以。
**kwargs
表示传一个字典给函数,可以传多个键值对,这里的
kwargs
同样也可以替换为其他名称,前面有
**
即可。
两者的不同如下所示:
def fun ( * args, ** kwargs) :
print ( "args: " , args)
print ( "kwargs: " , kwargs)
fun( "hello" , "world" , "!" , "this" , "is" , "args" , a= 1 , b= 2 , c= 3 )
13 Python中
__new__
和
__init__
的区别
__new__
是静态方法,会返回一个创建的实例
__init__
是实例方法,无返回值
14 Python中的单例模式
单例模式是一种特别重要的设计模式,通过单例模式可以保证系统中一个类只有一个实例并且该实例易于被外界访问,方便控制实例个数并节约系统资源。实现单例模式的常用方法如下:
1 通过
import
导入
在Python中使用
import
来导入一个对象,则是天然的单例模式。因为模块在第一次导入时,会生成
.pyc
文件,当第二次导入时,就会直接加载
.pyc
文件,而不会再次执行模块代码。因此,我们只需把相关的函数和数据定义在一个模块中,就可以获得一个单例对象了。例如:
class A ( object ) :
def fun ( self) :
pass
test_a = A
from a import test_a
2 使用装饰器
def Singleton ( cls) :
_state = { }
def _singleton ( * args, ** kargs) :
if cls not in _state:
_state[ cls] = cls( * args, ** kargs)
return _state[ cls]
return _singleton
@Singleton
class A ( object ) :
a = 1
def __init__ ( self, x= 0 ) :
self. x = x
a1 = A( )
a2 = A( )
print ( "id(a1): " , id ( a1) )
print ( "id(a2): " , id ( a2) )
print ( "a1.a: " , a1. a)
print ( "a2.a: " , a2. a)
a1. a = 2
print ( "a1.a: " , a1. a)
a2. a = 3
print ( "a2.a: " , a2. a)
print ( "a1.a: " , a1. a)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
3 使用类实现
使用类实现的时候需要加锁,否则在多线程中无法保证单实例
import time
import threading
class Singleton ( object ) :
_instance_lock = threading. Lock( )
def __init__ ( self) :
time. sleep( 1 )
@classmethod
def instance ( cls, * args, ** kwargs) :
with Singleton. _instance_lock:
if not hasattr ( Singleton, "_instance" ) :
Singleton. _instance = Singleton( * args, ** kwargs)
return Singleton. _instance
def task ( arg) :
obj = Singleton. instance( )
print ( obj)
for i in range ( 10 ) :
t = threading. Thread( target= task, args= [ i, ] )
t. start( )
time. sleep( 20 )
obj = Singleton. instance( )
print ( obj)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
4 基于
__new__
方法实现
当我们实例化一个对象时,是
先执行了类的__new__方法
(我们没写时,默认调用object.
new
),
实例化对象
;然后
再执行类的__init__方法
,对这个对象进行初始化,所有我们可以基于这个,实现单例模式
import threading
class Singleton ( object ) :
_instance_lock = threading. Lock( )
def __init__ ( self) :
pass
def __new__ ( cls, * args, ** kwargs) :
if not hasattr ( Singleton, "_instance" ) :
with Singleton. _instance_lock:
if not hasattr ( Singleton, "_instance" ) :
Singleton. _instance = object . __new__( cls)
return Singleton. _instance
obj1 = Singleton( )
obj2 = Singleton( )
print ( obj1)
print ( obj2)
def task ( arg) :
obj = Singleton( )
print ( obj)
for i in range ( 10 ) :
t = threading. Thread( target= task, args= [ i, ] )
t. start( )
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
15 Python中
is
和
==
的区别
is
是对指针的比较,
==
是对值的比较。
a = [ 1 , 2 , 3 , 4 ]
b = [ 1 , 2 , 3 , 4 ]
print ( a is b)
print ( a == b)