1、不错的面试题网站
- http://www.codingonway.com/
- https://github.com/revotu/python-interviews
- https://github.com/taizilongxu/interview_python
2、Python是如何进行内存管理的?
Python GC主要使用
引用计数(reference counting)来跟踪和回收垃圾。在引用计数的基础上,通过
“标记-清除”(mark and sweep)解决容器对象可能产生的循环引用问题,通过
“分代回收”(generation collection)以空间换时间的方法提高垃圾回收效率。
3、垃圾回收机制
- 当一个对象的引用计数归零时,它将被垃圾收集机制处理掉。
- 当两个对象a和b相互引用时,del语句可以减少a和b的引用计数,并销毁用于引用底层对象的名称。然而由于每个对象都包含一个对其他对象的应用,因此引用计数不会归零,对象也不会销毁。(从而导致内存泄露)。为解决这一问题,解释器会定期执行一个循环检测器,搜索不可访问对象的循环并删除它们。
4、引用计数机制
PyObject是每个对象必有的内容,其中ob_refcnt就是做为引用计数。当一个对象有新的引用时,它的ob_refcnt就会增加,当引用它的对象被删除、引用超出作用域或被重新赋值,它的ob_refcnt就会减少.引用计数为0时,该对象生命就结束了。
优点:
1. 简单
2. 实时性
缺点:
1. 维护引用计数消耗资源
2. 循环引用
5、标记-清除
基本思路是先按需分配,等到没有空闲内存的时候从寄存器和程序栈上的引用出发,遍历以对象为节点、以引用为边构成的图,把所有可以访问到的对象打上标记,然后清扫一遍内存空间,把所有没标记的对象释放。
6、分代回收
分代回收的整体思想是:将系统中的所有内存块根据其存活时间划分为不同的集合,每个集合就成为一个“代”,垃圾收集频率随着“代”的存活时间的增大而减小,存活时间通常利用经过几次垃圾回收来度量。
Python默认定义了三代对象集合,索引数越大,对象存活时间越长。
举例:
当某些内存块M经过了3次垃圾收集的清洗之后还存活时,我们就将内存块M划到一个集合A中去,而新分配的内存都划分到集合B中去。当垃圾收集开始工作时,大多数情况都只对集合B进行垃圾回收,而对集合A进行垃圾回收要隔相当长一段时间后才进行,这就使得垃圾收集机制需要处理的内存少了,效率自然就提高了。在这个过程中,集合B中的某些内存块由于存活时间长而会被转移到集合A中,当然,集合A中实际上也存在一些垃圾,这些垃圾的回收会因为这种分代的机制而被延迟。
7、内存池机制
Python提供了对内存的垃圾收集机制,但是它将不用的内存放到内存池而不是返回给操作系统。
- Pymalloc机制。为了加速Python的执行效率,Python引入了一个内存池机制,用于管理对小块内存的申请和释放。
- Python中所有小于256个字节的对象都使用pymalloc实现的分配器,而大的对象则使用系统的malloc。
- 对于Python对象,如整数,浮点数和List,都有其独立的私有内存池,对象间不共享他们的内存池。
8、函数参数
- 普通参数
即在调用函数时必须按照准确的顺序来进行参数传递。
def fun(name):
print('Hello', name)
fun('World')#Hello World
- 默认参数
即参数含有默认值,在调用函数时可以进行参数传递,若没有进行参数传递则使用默认值,要注意,默认参数必须在普通参数的右侧(否则解释器无法解析)
def fun(name, age=1):
print('Hello', name, age, '年')
fun('World') # Hello World 1 年
- 元组参数,即 *args
参数格式化存储在一个元组中,长度没有限制,
必须位于普通参数和默认参数之后
def fun(name, age=1, *args):
print('Hello', name, age, '年') # Hello World 1 年
print(args) # ('I', 'love', 'it')
for i in args:
print(i)
fun('World', 1, 'I', 'love', 'it')
输出结果:
I
love
it
- 字典参数,即 **kwargs
参数格式化存储在一个字典中,
必须位于参数列表的最后面
def fun(name, age=1, *args, **kwargs):
print('Hello', name, age, '年') # Hello World 1 年
print(args) # ('I', 'love', 'it')
for i in args:
print(i)
print(kwargs) # {'my': 'jack', 'like': 'girl'}
for m in kwargs:
print(m, ':', kwargs[m])
fun('World', 1, 'I', 'love', 'it', my='jack', like='girl')
输出结果:
Hello World 1 年
('I', 'love', 'it')
I
love
it
{'my': 'jack', 'like': 'girl'}
my : jack
like : girl
9、Python里面如何拷贝一个对象?(赋值,浅拷贝,深拷贝的区别)
- 赋值(=),就是创建了对象的一个新的引用,修改其中任意一个变量都会影响到另一个。
- 浅拷贝:创建一个新的对象,但它包含的是对原始对象中包含项的引用(如果用引用的方式修改其中一个对象,另外一个也会修改改变){1,完全切片方法;2,工厂函数,如list();3,copy模块的copy()函数}
- 深拷贝:创建一个新的对象,并且递归的复制它所包含的对象(修改其中一个,另外一个不会改变){copy模块的deep.deepcopy()函数}
- copy 仅拷贝对象本身,而不拷贝对象中引用的其它对象。
- deepcopy 除拷贝对象本身,而且拷贝对象中引用的其它对象。
代码实例 import copy a = [1, 2, 3, 4, ['a', 'b']] #原始对象 b = a #赋值,传对象的引用 c = copy.copy(a) #对象拷贝,浅拷贝 d = copy.deepcopy(a) #对象拷贝,深拷贝 a.append(5) #修改对象a a[4].append('c') #修改对象a中的['a', 'b']数组对象 print 'a = ', a print 'b = ', b print 'c = ', c print 'd = ', d 输出结果: a = [1, 2, 3, 4, ['a', 'b', 'c'], 5] b = [1, 2, 3, 4, ['a', 'b', 'c'], 5] c = [1, 2, 3, 4, ['a', 'b', 'c']] d = [1, 2, 3, 4, ['a', 'b']]
10、Python中重载
函数重载主要是为了解决两个问题:
1. 可变参数类型
2. 可变参数个数
解释一:
- 那么对于情况 1 ,函数功能相同,但是参数类型不同,python 如何处理?答案是根本不需要处理,因为 python 可以接受任何类型的参数,如果函数的功能相同,那么不同的参数类型在 python 中很可能是相同的代码,没有必要做成两个不同函数。
- 那么对于情况 2 ,函数功能相同,但参数个数不同,python 如何处理?大家知道,答案就是缺省参数。对那些缺少的参数设定为缺省参数即可解决问题。因为你假设函数功能相同,那么那些缺少的参数终归是需要用的。
鉴于情况 1 跟 情况 2 都有了解决方案,python 自然就不需要函数重载了。
解释二:
简单来说,Python中为什么不需要重载,重载要解决的是参数类型和参数个数的问题,对于类型,python不像是c语言整型要写int,字符串要写str,,,这些python都不需要。
那么需要解决的就是传递参数个数问题,此时python可以传递列表呀,字典呀,可以使用*arg和**args呀,所以python根本不需要重载。
11、Python中单下划线和双下划线
- __foo__:一种约定,Python内部的名字,用来区别其他用户自定义的命名,以防冲突.
- _foo:一种约定,用来指定变量私有.程序员用来指定私有变量的一种方式.
- __foo:这个有真正的意义:解析器用_classname__foo来代替这个名字,以区别和其他类相同的命名.
12、 __new__和__init__的区别
1. __new__是一个静态方法,而__init__是一个实例方法.
2. __new__方法会返回一个创建的实例,而__init__什么都不返回.
3. 只有在__new__返回一个cls的实例时,后面的__init__才能被调用.
4. 当创建一个新实例时调用__new__,初始化一个实例时用__init__.
13、单例模式
该模式的主要目的是确保某一个类只有一个实例存在
- 使用模块
其实,Python 的模块就是天然的单例模式,
因为模块在第一次导入时,会生成 .pyc 文件,当第二次导入时,就会直接加载 .pyc 文件,而不会再次执行模块代码。因此,我们只需把相关的函数和数据定义在一个模块中,就可以获得一个单例对象了。
# mysingleton.py
class My_Singleton(object):
def foo(self):
pass
my_singleton = My_Singleton()
将上面的代码保存在文件 mysingleton.py 中,要使用时,直接在其他文件中导入此文件中的对象,这个对象即是单例模式的对象
# to use
from mysingleton import my_singleton
my_singleton.foo()
- 使用类
class Singleton(object): def __init__(self): pass @classmethod def instance(cls, *args, **kwargs): if not hasattr(Singleton, "_instance"): Singleton._instance = Singleton(*args, **kwargs) return Singleton._instance
- 基于__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
14、创建字典的方法
- 直接创建
dict = {'name':'earth', 'port':'80'}
- 工厂方法
items=[('name','earth'),('port','80')]
dict2=dict(items)
- fromkeys()方法
dict1={}.fromkeys(('x','y'),-1)
dict={'x':-1,'y':-1}
dict2={}.fromkeys(('x','y'))
dict2={'x':None, 'y':None}
15、数组和元组之间的区别是什么?
- 相同点
首先,列表与元组都是容器,是一系列的对象;
其次,二者都可以包含任意类型的元素甚至可以是一个序列。
- 不同点
列表和元组的“技术差异”是,列表是可变的,而元组是不可变的。
16、Python都有那些自带的数据结构?
Python自带的数据结构分为可变的和不可变的。
- 可变的有:
集合
字典
- 不可变的有:
字符串
元组
数字
数组
17、推导式
- 列表(list)推导式
功能:是提供一种方便的列表创建方法,所以,列表解析式返回的是一个列表
例子:
>>> li=[i*2 for i in range(10) if i % 2 == 0]
>>> print li
[0, 4, 8, 12, 16]
列表解析式最擅长的方式就是对整个列表分别做相同的操作,并且返回得到一个新的列表
- 字典(dict)推导式
- 集合(set)推导式
功能:集合推导式跟列表推导式差不多,都是对一个列表的元素全部执行相同的操作,但集合是一种无重复无序的序列
区别:跟列表推到式的区别在于:1.不使用中括号,使用大括号;2.结果中无重复;3.结果是一个set()集合,集合里面是一个序列
>>> squared={i*2 for i in [1,1,2]}
>>> print squared
set([2, 4])
18、Python是如何进行类型转换的?
Python提供了将变量或值从一种类型转换成另一种类型的内置函数。比如int函数能够将符合数学格式数字型字符串转换成整数。否则,返回错误信息。
19、Python是如何被解释的?
Python是一种解释性语言,Python解释器会将源代码转换成中间语言,之后再翻译成机器码再执行。
20、.Python中的负索引是什么?
Python中的序列索引可以是正也可以是负。如果是正索引,0是序列中的第一个索引,1是第二个索引。如果是负索引,(-1)是最后一个索引而(-2)是倒数第二个索引。
21、Python的参数传递是值传递还是引用传递
1).Python的参数传递有:
- 位置参数
- 默认参数,
- 可变参数,
- 关键字参数
2).函数的传值到底是值传递还是引用传递,要分情况
a.
不可变参数用值传递:
像整数和字符串这样的不可变对象,是通过拷贝进行传递的,因为你无论如何都不可能在原处改变不可变对象
b.
可变参数是用引用传递的
比如像列表,字典这样的对象是通过引用传递,和C语言里面的用指针传递数组很相似,可变对象能在函数内部改变.
22、Xrange和range的区别是什么?
xrange 函数说明:用法与range完全相同,所不同的是生成的不是一个数组,而是一个生成器。
>>> range(5)
[0, 1, 2, 3, 4]
>>> xrange(5)
xrange(5)
>>> list(xrange(5))
[0, 1, 2, 3, 4]
23、单引号,双引号,三引号的区别
1),单引号和双引号主要用来表示字符串
区别:
若你的字符串里面本身包含单引号,必须用双引号
比如:"can't find the log\n"
2).三引号
三单引号:'''python ''',也可以表示字符串一般用来输入多行文本,或者用于大段的注释
三双引号:"""python""",一般用在类里面,用来注释类
24、类和实例
25、类变量和实例变量
实例变量是对于每个实例都独有的数据,而类变量是该类所有实例共享的属性和方法。
class Dog:
kind = 'canine' # class variable shared by all instances
def __init__(self, name):
self.name = name # instance variable unique to each instance
类Dog中,类属性kind为所有实例所共享;
实例属性name为每个Dog的实例独有。
26、类对象和实例对象
- 类对象
类对象仅支持两个操作:
实例化;使用instance_name = class_name()的方式实例化,实例化操作创建该类的实例。
属性引用;使用class_name.attr_name的方式引用类属性。
- 实例对象
实例对象是类对象实例化的产物,实例对象仅支持一个操作:
属性引用;与类对象属性引用的方式相同,使用instance_name.attr_name的方式。
27、属性绑定
我们说的属性绑定,首先需要一个可变对象,才能执行绑定操作,使用objname.attr = attr_value的方式,为对象objname绑定属性attr。
这分两种情况:
若属性attr已经存在,绑定操作会将属性名指向新的对象;
若不存在,则为该对象添加新的属性,后面就可以引用新增属性。
- 类属性绑定
类属性的绑定发生在两个地方:
类定义时;
运行时任意阶段。
在类定义中,类属性的绑定并没有使用objname.attr = attr_value的方式,这是一个特例,其实是等同于后面使用类名绑定属性的方式。
因为是动态语言,所以可以在运行时增加属性,删除属性。
- 实例属性绑定
与类属性绑定相同,实例属性绑定也发生在两个地方:
类定义时;
运行时任意阶段。
类实例有两个特殊之处:
__init__在实例化时执行
Python实例调用方法时,会将实例对象作为第一个参数传递
因此,__init__方法中的self就是实例对象本身
28、属性引用
- 类属性引用
类属性的引用,肯定是需要类对象的,属性分为两种:
数据属性
函数属性
- 实例属性引用
使用实例对象引用属性稍微复杂一些,因为实例对象可引用类属性以及实例属性。但是实例对象引用属性时遵循以下规则:
总是先到实例对象中查找属性,再到类属性中查找属性;
属性绑定语句总是为实例对象创建新属性,属性存在时,更新属性指向的对象。
29、
Python 中的 is 和 ==
is
is the identity comparison. #比较引用是否相同
==
is the equality comparison. #比较内容是否相同
python中新建变量时,并不需要指定类型,因为每个变量实际上存储的是一个引用,就是指向一个对象实体的指针。
is 判断的就是这个指针的值是否相同,如果相同则表示两个变量指向同一个对象实体。
而==则比较它们的内容是否相同
30、isinstance 和 type 的区别
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
区别就是:
- type()不会认为子类是一种父类类型。
- isinstance()会认为子类是一种父类类型。
31、