Python面试过程中常见基础问题总结

Posted by Jack on 2020-03-14
Words 2.8k and Reading Time 11 Minutes
Viewed Times

1. Python相对其他语言(C/C++、Java)有什么区别(或是优点)?

(1)Python是一种解释型语言,这就与编译型语言(C/C++)不同,Python代码在运行之前不需要编译
(2)Python是动态类型语言,在声明变量时不需要说明变量的类型
(3)Python非常适合面向对象的编程(OOP),它支持通过组合(composition)与继承(inheritance)的方式定义类(class),但是没有访问说明符(如public、private)。
(4)Python语言的编写简明简单,代码易于理解和修改,但是运行速度比编译语言通常要慢(C/C++编译成本地机器码, 可以由操作系统直接运行,运行效率较高;Java 编译成字节码,需由Java虚拟机读取运行, 所以效率偏低,但可通过JIT提高运行效率)。
(5)Python使用动态内存分配过程,垃圾收集器会自动将内存回收,Java亦有自己的垃圾回收机制,而C/C++需要程序员自己回收内存,更容易发生内存泄漏。
(6)Python语言中,一切皆对象,函数是第一类对象,即可以被指定给变量,同时函数既能返回函数类型,也可以接受函数作为输入参数。
(7)Python开源,具有强大的社区支持,具有良好的可扩展性和可移植性

补充:
Python: 适合快速开发应用程序
Java: 适合健壮的大型软件
C++: 适合需求效率的软件
C: 适合操作系统及驱动

2. Python程序是怎么运行的?(Python解释器的原理)

Python程序通过Python解释器来运行,首先将源代码转变成中间语言代码(字节码.pyc文件,但不是机器的二进制代码,这也是比C/C++运行慢的原因),然后再被转换成能够直接执行的机器语言代码

补充:
Java程序的运行也是类似,首先Java解释器将源码编译为字节码.class文件(这里是强制的操作,
而Python编译是自动的过程),然后由Java虚拟机用解释的方式执行字节码。

3. Python中的装饰器是什么?

装饰器(Decorator)本质上是一个Python函数,它可以让其他函数在不做修改的前提下增加额外功能(简单说就是为已经存在的对象添加额外的功能),装饰器的返回值也是一个函数对象。它经常用于有切面需求的场景(如插入日志、性能测试、事务处理、缓存、权限校验等),使用装饰器可以抽离出大量与函数功能本身无关的雷同代码并继续重用(便于代码复用)。@符号是装饰器的语法,使用时在被装饰的函数上方添加@decorator_name,下面看示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
def add_log(func):

def wrapper(*args, **kwargs):
logging.info('%s is running' % func.__name__)
return func(*args)
return wrapper

@add_log
def apple():
print('This is an apple.')

@add_log
def pear():
print('This is a pear.')

# apple()
pear()

上面的代码则是在函数原来的功能上,新增了日志功能,可以看出装饰器能够提高程序的可重复利用性,并增加程序的可读性

补充:带参数的装饰器
def add_log(level):
    def decorator(func):
        def wrapper(*args, **kwargs):
            if level == 'info':
                logging.info('%s is running' % func.__name__)
            return func(*args)
        return wrapper
    return decorator

@add_log(level='info')
def apple():
    print('This is an apple.')

@add_log(level='info')
def pear():
    print('This is a pear.')

# apple()
pear()

4. Python中的生成器是什么?

在Python中,这种一边循环一边计算的机制,称为生成器(generator)。生成器本质上还是一个特殊的迭代器,并且只能遍历一次。虽然可通过调用next()方法获得下一个返回值,但是当没有更多元素时,会抛出StopIteration的错误,因此正确的方法是使用for循环进行遍历。生成器通常有以下两类:
(1)生成器表达式:类似于列表生成式,只需把[]替换为()便能创建生成器。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
>>> L = [x * x for x in range(5)]
>>> L
[0, 1, 4, 9, 16, 25]
>>> g = (x * x for x in range(5))
>>> g
<generator object <genexpr> at 0x1022ef630>
>>> for i in g:
··· print(i)
···

0
1
4
9
16
25

(2)生成器函数:使用了yield返回结果的函数。调用一个生成器函数,返回的是一个迭代器对象。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
>>> def func(n):
··· yield n*n
···
>>> func
<function func at 0x000001E28E4B6598>
>>> gen = func(5)
>>> gen
<generator object func at 0x000001E28E4ACDB0>
>>> next(gen)
25
>>> next(gen)
Traceback (most recent call last):
File "<pyshell#8>", line 1, in <module>
next(gen)
StopIteration

5. Python中的迭代器是什么?

迭代器(Iterator)就是用于迭代操作的对象,任何实现了iter()方法和next()方法的对象都可以称为迭代器对象。此处需要区别开的是可迭代对象(Iterable)(只有next()方法),比如list、tuple、dict、set、str等,可以通过内建函数iter()把它们变为迭代器对象。for循环的本质就是先通过iter()函数获取可迭代对象Iterable的迭代器,然后不断调用next()方法来获取下一个值,直到遇到StopIteration的异常后循环结束。下面看基于斐波那契数列的迭代器实现示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Fib:
def __init__(self, n):
self.a = 0
self.b = 1
self.n = n # 迭代次数

# 迭代器自身正是一个迭代器,所以迭代器的__iter__方法返回自身self即可
def __iter__(self):
return self

def __next__(self):
if self.n < 1:
raise StopIteration
fib = self.a
self.a, self.b = self.b, self.a + self.b
self.n -= 1
return fib

f = Fib(10)
for i in f:
print(i)

# 打印的结果:0, 1, 1, 2, 3, 5, 8, 13, 21, 34

6. xrange和range的区别是什么?

xrange和range的用法完全相同,区别在于xrange返回的是一个生成器(xrange对象),而range返回的是一个可迭代(list)对象。因此,xrange做循环的性能比range好,尤其是生成很大的数字序列的时候,因为不需要预先开辟一块很大的内存空间。
注意:在Python3中没有了xrange函数,但是保留了xrange的实现,并将xrange函数重新命名成range函数。

7. Python是如何进行内存管理的?

Python内存空间是以Python私有堆的形式进行管理的,所有的Python对象和数据结构都存放在一个私有堆中。解释器可以访问私有堆,而程序员不可以访问,但可以通过内核API访问一些Python内存管理工具来控制内存分配。

8. 深拷贝和浅拷贝的区别是什么?

(1)深拷贝是将对象本身复制给另一个对象。这意味着如果对对象的副本进行更改时不会影响原对象。在Python中,我们使用copy.deepcopy(obj)函数进行深拷贝。
(2)浅拷贝是将对象的引用复制给另一个对象。因此,如果我们在副本中进行更改,则会影响原对象。在Python中,我们使用copy.copy(obj)函数进行浅拷贝。

9. 能否解释一下 *args 和 **kwargs?

这两个是Python中的可变参数,*args表示任何多个无名参数,它是一个tuple**kwargs表示任何多个关键字参数,它是一个dict。并且同时使用 *args 和 **kwargs 时,必须 *args参数列要在 **kwargs前面。下面就看看实际的例子:

1
2
3
4
5
6
7
8
9
10
11
def func(*args, **kwargs):
print('args=', end='')
print(args)
print('kwargs=', end='')
print(kwargs)


func(1, 2, None, a=1, b='good', c=[2, 2])
# 输出结果:
# args=(1, 2, None)
# kwargs={'a': 1, 'b': 'good', 'c': [2, 2]}

10. Python参数传递中值传递和引用传递的区别?

(1)值传递:方法调用时,实际参数把它的值传递给对应的形式参数,方法执行中形式参数值的改变不影响实际参数的值
(2)引用传递:也称地址传递,在方法调用时,实际上是把参数的引用(传的是地址,而不是参数的值)传递给方法中对应的形式参数,在方法执行中,对形式参数的操作实际上就是对实际参数的操作,方法执行中形式参数值的改变将会影响实际参数的值。
(3)在Python中,数值型、字符串型或者元组等不可变对象类型都属于值传递,而字典dict或者列表list等可变对象类型属于引用传递

11. “猴子补丁”指的是什么?

属性在运行时的动态替换,叫做猴子补丁(Monkey Patch)。猴子补丁允许在运行期间动态修改一个类或模块,比如下面运行时动态替换speak函数的例子:

1
2
3
4
5
6
from SomeOtherProduct.SomeModule import SomeClass

def speak(self):
return "ook ook eee eee eee!"

SomeClass.speak = speak

12. Python中的pass是什么意思?

pass是Python中的空操作语句,或者说,它是一个复杂语句中的一个空间占位符,只是用来表示空白,什么都不用写。

13. Python中的lambda表达式是什么?

lambda表达式是一个匿名函数,通常用作代码行内的嵌入函数。lambda表达式只允许包含一个表达式,不能包含复杂语句,该表达式的运算结果就是函数的返回值。

14. @property的作用和用法

property的作用主要是为了避免对类属性的直接操作,把不想公开的属性隐藏起来。被property装饰的方法,可以【当作】是属性来使用。

1
2
3
4
5
6
7
8
9
10
11
12
class Exam(object):
def __init__(self, score):
self._score = score

@property
def score(self):
return self._score

>>> e = Exam(80)
>>> e.score
80
>>> e.score = 121 # score 是只读属性,不能设置值

上面的例子设置了类属性只能读取,不能赋值修改,如果要修改,可以用property.setterproperty.getter,如下:
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
class Exam(object):
def __init__(self, score):
self._score = score

@property
def score(self):
return self._score

@score.getter # property.getter,这里与上面property装饰的score等效
def score(self):
return self._score


@score.setter # property.setter
def score(self, val):
if val < 0:
self._score = 0
elif val > 100:
self._score = 100
else:
self._score = val

>>> e = Exam(80)
>>> e.score
80
>>> e.score = 121 # 使用property.setter就可以在内部对值进行判断
>>> e.score
100

15. @classmethod和@staticmethod的区别

使用@staticmethod@classmethod,就可以不需要实例化,直接类名.方法名()来调用。二者的区别在于:
· 经过@staticmethod修饰的方法,不需要self参数,其使用方法和直接调用函数一样。
· 经过@classmethod修饰的方法,也不需要self参数,但是需要一个标识类本身的cls参数。
· @classmethod因为持有cls参数,可以来调用类的属性,类的方法,实例化对象等。
下面是关于它们的使用例子:

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
class A(object):

num = 1 # A的属性

def func(self):
print('This is A function')

@staticmethod
def static_func():
print('This is A static function')
print A.bar

@classmethod
def class_func(cls):
print('This is A class function')
print(cls.num)
cls().func()

>>> A.static_func()
This is A static function

>>> A.class_func()
This is A class function
1
This is A function


...

...

00:00
00:00