编程学习网 > 编程语言 > Python > python 后台开发教程(Python后端开发面试常见问题)
2023
04-11

python 后台开发教程(Python后端开发面试常见问题)


主要是Python后端开发的一些常见问题,包括Python的一些基础知识,以及面试中常问的计网,数据库,数据结构等一些算法题,总体覆盖面试的大多数问题。

Python 基础
0、说明占位符
本文中不特别标注Python版本的,全部默认为Python3
1、Python类中的方法类型
在Python类中有四种方法类型,分别是实例方法、静态方法、类方法和普通方法。
实例方法(即对象方法):需要实例化对象之后才能调用,接受的第一个参数self就是对象本身,必须使用实例化对象才可以访问,不能通过类直接访问.
静态方法:可以通过类名直接调用,不需要传递self和cls;也可以在实例化对象后调用
类方法:可以通过类名调用,也可以在实例化对象后调用。类方法需要一个cls参数,在调用时自动传递
普通方法:和正常的函数一样,可以直接调用,但是在类中不建议写这种方法
测试示例:
class A(object):    # 实例方法(对象方法),需要接收一个形参self,即实例对象自己    def instance_method_fun(self):        print("instance_method_fun,self is {}".format(self))        # 类方法,需要接收一个形参cls,在调用时自动传递    @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.instance_method_fun() # 报错:TypeError: instance_method_fun() missing 1 required positional argument: 'self'A.classmethod_fun() # 输出:classmethod_fun, cls is <class '__main__.A'>A.staticmethod_fun() # 输出:staticmethod_funA.common_fun() # 输出:common_fun  (不建议在类中写普通方法)
a = A()a.instance_method_fun() # 输出:instance_method_fun,self is <__main__.A object at 0x0000018674E1E588>a.classmethod_fun()# 输出:classmethod_fun, cls is <class '__main__.A'>a.staticmethod_fun()# 输出:staticmethod_fun

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 = 1print("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)))
# 输出为:# a:1,id(a):1860272240# x:2,id(x):1860272272# a:1,id(a):1860272240# b:[],id(b):2262818473288# b:[],id(b):2262818473288# b:[2],id(b):2262818473288# b:[2],id(b):2262818473288
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) # 输出:self._b# print(a.__c) # 报错:AttributeError: 'A' object has no attribute '__c'print(a.__dict__) # 输出:{'_b': 'self._b', '_A__c': 'self.__c'}# 我们发现__c变量被自动重命名为_A__c了print(a._A__c) # 输出:self.__c在Python中,当一个文件夹下有一个__init__.py文件,则Python会识别这个文件夹为一个Python包
5、python字符串传参 %s和format
%s和format的区别在于,format可以传递列表、元组等类型,而%s不可以传递元组类型(%s可以传递列表类型),所以在日常使用时,使用format更加方便
6、python 迭代器和生成器
迭代器是python十分强大的一个功能,迭代器是一个可以记住遍历位置的对象。例如:
a = [1,2,3,4]it = iter(a) # 获取一个迭代器对象print(next(it)) # 遍历下一个位置,输出:1print(next(it)) # 遍历下一个位置,输出:2

我们也可以使用迭代器来生成一个我们需要的列表,例如:

a = [i*i for i in range(1,9)] # 使用迭代器生成一个[1,9)的平方的列表print(a) # 输出:[1, 4, 9, 16, 25, 36, 49, 64]在这里如果我们将外面的[]改为(),a获取的将会是一个生成器,而不是迭代器。在python中,使用yield的函数被称为生成器,生成器返回的是一个迭代器的函数,只能用于迭代操作,在调用生成器运行的过程中,每次遇到 yield 时函数会暂停并保存当前所有的运行信息,返回 yield 的值, 并在下一次执行 next() 方法时从当前位置继续运行。
生成器不保留所有的数据信息,而是指记录当前运行的信息。因此生成器就不能直接获取某个下表的值,而是要通过next()或者sent(),亦或是迭代器来依次获取值。
例如:
a = (i*i for i in range(1,9)) # 使用生成器生成一个[1,9)的平方的列表print(a) # 输出:[1, 4, 9, 16, 25, 36, 49, 64]# print(a[2]) # 报错TypeError: 'generator' object is not subscriptableit = iter(a) # 获取一个迭代器对象print(next(a)) # 输出 1print(next(a)) # 输出 4print(next(a)) # 输出 9print(next(a)) # 输出 16print("=================")for item in a:    print(item)# 输出:# 25# 36# 49# 64生成器常用场景:例如我们需要生成一个有规律向前推进的列表,或者一个从1到1亿的列表等等,当我们列表中元素数量十分大时,内存会爆栈。但是如何我们元素间是有规律的,则我们可以利用生成器来解决这个问题。
7、python 装饰器
装饰器是一个十分常用的东西,经常被用在有切面需求的场景,大家耳熟能详的AOP就是这个,装饰器的主要功能就是为已经存在的函数提供一些可复用的定制化功能。
8、python 变量中的作用域
python中变量总是默认本地变量,如果没有,则会创建一个本地变量。在函数中如果要使用全局变量,需要使用gloabl进行声明。例如:
a = 5def fun():    global a    a = a + 1fun()print(a)# 输出:6
9、python 闭包
python闭包与其他语言的闭包的意思是一样的,即我们在函数定义中引用了函数外定义的变量,并且该函数可以在其定义环境外被执行。简单来说,就是在函数内定义函数,且函数内部定义的函数中使用了外部函数中的变量,且内部函数可以单独的运行。
这里用语言来描述还是比较的绕,我们可以通过下面这个小例子来更直观的理解:
# 定义一个函数extern_funcdef extern_func():    # 在extern_func函数内定义一个空列表list    list = []    # 在extern_func函数内定义一个函数inner_func    def inner_func(name):        # inner_func函数的功能是在list列表中添加一个name,然后输出list        list.append(name)        print(list)    # exten_func函数的返回值是inner_func的函数体    return inner_func
# 调用extern_func函数,返回值赋值给ret1,即ret1为inner_func函数体ret1 = extern_func()ret1('zhangsan')# 调用ret1,在list中添加一个'zhangsan',并输出listret1('lisi')# 调用ret1,在list中添加一个'lisi',并输出list
# 调用extern_func函数,返回值赋值给ret2,即ret2为inner_func函数体ret2 = extern_func()ret2('wangwu')# 调用ret2,在list中添加一个'wangwu',并输出list
ret1('qiguanjie')# 调用ret1,在list中添加一个'lisi',并输出list输出为:
['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 listre1,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 listre1,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 listre1,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():        nonlocal name        name = 'inner_func ' + name        print('inner_func name is : %s' % name)    return inner_func
ret = extern_func('qiguanjie')()此时程序顺利编译,输出结果为:
exter_func name is : qiguanjieinner_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': 3, 'b': 634, 'c': 216}, {'a': 13, 'b': 25, 'c': 62}, {'a': 63, 'b': 215, 'c': 612}]a.sort(key=lambda x: x['b'])print(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['c'])print(a) # 输出: [{'a': 13, 'b': 25, 'c': 62}, {'a': 3, 'b': 634, 'c': 216}, {'a': 63, 'b': 215, 'c': 612}]
11、python中的深拷贝与浅拷贝
在浅拷贝时,拷贝出来的新对象的地址和原对象是不一样的,但是新对象里面的可变元素(如列表)的地址和原对象里的可变元素的地址是相同的,也就是说浅拷贝它拷贝的是浅层次的数据结构(不可变元素),对象里的可变元素作为深层次的数据结构并没有被拷贝到新地址里面去,而是和原对象里的可变元素指向同一个地址,所以在新对象或原对象里对这个可变元素做修改时,两个对象是同时改变的,但是深拷贝不会这样,这个是浅拷贝相对于深拷贝最根本的区别。
在深拷贝时,会只拷贝所有元素的值,包括可变对象,也仅拷贝对象中的值,而不是地址。
import copya = [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])))
# 输出为:# a:[1, 2, 3, 4, ['a', 'b', 'c', 'd'], 5],  id(a):1998224934024, id(a[4]):1998224933896# b:[1, 2, 3, 4, ['a', 'b', 'c', 'd'], 5],  id(b):1998224934024, id(b[4]):1998224933896# c:[1, 2, 3, 4, ['a', 'b', 'c', 'd']],     id(c):1998224936904, id(c[4]):1998224933896# d:[1, 2, 3, 4, ['a', 'b', 'c']],          id(d):1998224935752, id(d[4]):1998224957960
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)

# 输出为:# args:  ('hello', 'world', '!', 'this', 'is', 'args')# kwargs:  {'a': 1, 'b': 2, 'c': 3}

以上就是python 后台开发教程(Python后端开发面试常见问题)的详细内容,想要了解更多Python教程欢迎持续关注编程学习网。

扫码二维码 获取免费视频学习资料

Python编程学习

查 看2022高级编程视频教程免费获取