参考文档:
github: https://github.com/taizilongxu/interview_python
本地地址:/home/yuyh/下载/面试题/python web开发面试.doc
目标:尽量掌握这里的全部的知识点
1.python函数的参数是怎么传递的?
这里考查的是python的数据类型问题:可变数据类型和不可变数据类型(类型是属于对象,而非变量)
可变对象:list, set, dict
不可变对象:string, number, tuble
函数传递的参数是不可变的,如a=1, 这样会给函数的a一个内存地址,存储a的值
如果函数传递的参数为可变,则不分配内存,而是在原来基础上修改变量的值
原文叙述:所有的变量都可以理解是内存中的一个对象的引用
- 函数引用指向一个不可变对象,当函数返回时,外面的引用和里面没有关系(两个不同的内存)
- 函数引用指向一个可变对象,对它的操作就和定位了指针地址一样,在内存中修改
2.Python中的元类(metaclass)
设计模式中经常用到,元类其实是可以控制对象创建和对象实例化的
元类是一个类的类,这意味着该类是它的元类的实例
使用元类,开发人员有机会从预定义的Python类创建自己类型的类
示例代码:
# 元类MyInt
class MyInt(type):
def __call__(cls, *args, **kwargs):
print("调用了元类MyInt的__call__方法 ", args)
print("这说明元类控制着对象的实例化")
return type.__call__(cls, *args, **kwargs)
# 类int的元类声明:metaclass=MyInt
# int类型数据是type,即存在一个元类,因为int是type类型的类,
# 这里假设这个元类就是MyInt
class int(metaclass=MyInt):
def __init__(self, x, y):
self.x = x
self.y = y
i = int(4, 5)
运行结果:
调用了元类MyInt的__call__方法 (4, 5)
这说明元类控制着对象的实例化
3.@staticmethod和@classmethod
Python有3种方法:静态方法(staticmethod),类方法(classmethod)和实例方法
先理解函数参数的self和cls, 这个self和cls是对类或者实例的绑定
区别:
实例方法必须实例化 a= A()
类方法传递的是类而不是实例
静态方法其实和普通方法一样,不需要对谁进行绑定
4.类变量和实例变量
类变量:是可在类的所有实例之间共享的值
实例变量:实例化之后,每个实例单独拥有的变量
跟题1 有相似之处
class Person:
name=[]
p1=Person()
p2=Person()
p1.name.append(1)
print(p1.name) # [1]
print(p2.name) # [1]
print(Person.name ) # [1]
5.Python自省
自省就是运行时能够获得对象的类型,比如type(),dir(),getattr(),hasattr(),isinstance()
6.字典推导式
d = {key: value for (key, value) in iterable}
列表推导式?
l = [x for x in range(100) if x%3 is 0]
7.Python中单下划线和双下划线
init(): 双下划线,一种Python内部的名字。用来区别其他用户自定义的命名,以防冲突
__foo: 解析器用_classname__foo来代替这个名字,以区别和其他类相同的命名,它无法直接像公有成员一样随意访问,
通过对象名._类名__foo这样的方式才可以访问
_foo: 一种约定,用来指定私有变量,不能用from mudule import *导入,其他方面和公有一样访问
8.字符串格式化:% 和 .format
对于%最烦人的是它无法同时传递一个变量和元组:
"hi there %s " % name
如果name恰好是(1,2,3),它会抛出一个TypeError异常,为了保证正确,必须这样:
"h1 there %s " % (name,)
但是使用 .format就没有这些问题
“h1 there {0}”.format(name)
9.迭代器和生成器
迭代器:对象实现了__iter__()和__next__()两个方法(迭代器协议)
列表:list对象不是一个迭代器对象
可以使用list对象创建一个迭代器对象,然后让迭代器对象调用__next__()方法,直到最后抛出一个Stoplteration异常,
告诉外部调用者迭代完成了,外部调用者尝试去捕获这个异常去做进一步处理
li=list.__iter__()
li.__next__() # 1
li.__next__() # 2
...
>>> li.__next__()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
注意:for循环本质上也是一种迭代器
生成器是一个包含有yield的函数,同时也是一个迭代器
生成器的创建:
- 使用生成器函数的返回值,返回一个生成器对象
- 使用生成器表达式的返回值,返回一个生成器对象
生成器创建问题:将列表生成式中[]改成()之后数据结构是否改变?答案:是,从列表变为生成器
L = [xx for x in range(10)]
L # [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
g = (xx for x in range(10))
g # <generator object at 0x00000028F8B774200>
在Python中,我们可以采用生成器:边循环,边计算的机制 --> generator
参考:https://blog.csdn.net/Yuyh131/article/details/83310486
10.*args 和 **kwargs
用 *args, **kwargs 只是为了方便并没有强制要求使用它们
一般情况下:
- *args: 不知参数个数时使用,它可以传递任意数量的参数,可以传递列表或者元组
- **kwargs: 一般传递的参数为字典(键值对形式)时使用
11.面向切面编程AOP 和装饰器
装饰器是一个很著名的设计模式,经常被用于有切面需求的场景,如插入日志、性能测试、事务处理等
装饰器是解决这类问题的绝佳设计,有了装饰器,可以抽离与函数功能无关的代码并继续重用
简言之,装饰器的作用就是为已经存在的对象添加额外的功能
- 装饰器实现定时器
import time
# 装饰器:计算函数TimeOut运行所需时间
def Deco(function):
def wrapper(*args, **kwargs):
start_time = time.time()
function()
end_time = time.time()
run_time = end_time - start_time
print(run_time * 1000)
return wrapper
@Deco
def TimeOut():
print("测试下面的代码运行时间")
# a = 2
# time.sleep(a)
# print("这里过去了{0}秒".format(a))
pass
#f = TimeOut
TimeOut()
2.有参数的装饰器等(有空再回来加上)
12.鸭子类型
鸭子类型在动态语言中经常使用,非常灵活,使得Python减少设计一堆设计模式
我们并不关心对象是什么类型,到底是不是鸭子,只关心行为
比如在python中,有很多file-like的东西,比如StringIO,GzipFile,socket。它们有很多相同的方法,我们把它们当作文件使用
又比如list.extend()方法中,我们并不关心它的参数是不是list,只要它是可迭代的,所以它的参数可以是list/tuple/dict/字符串/生成器等
13.Python中的重载
解决两个问题:
- 可变参数类型
- 可变参数个数
设计原则:两个函数功能是完全相同的
Python解决这两个问题: - python可以接受任何类型的参数,如果函数的功能相同,那么不同的参数类型在Python中很可能是相同的代码,没有必要做成两个函数; 即python不需要处理这个问题
- 函数功能相同,但函数个数不同,python处理:答案就是缺省参数。对那些缺少的参数设定为缺省参数即可解决问题。因为你假设函数功能相同,那么那些缺少的参数终归是需要用的
鉴于情况1, 2都有了解决方案,python自然不需要函数重载了
14.新式类和旧式类
新式类很早在2.2就出现了,所以旧式类完全是兼容的问题。Python3里的类全部都是新式类
这里有一个MRO问题可以了解下:
新式类是广度优先, 旧式类是深度优先
一个旧式类的深度优先的例子:
# Python2
class A:
def foo1(self):
print "A"
class B(A):
def foo2(self):
pass
class C(A):
def foo1(self):
print "C"
class D(B, C):
pass
d = D()
d.foo1()
运行结果:
A
原文叙述:按照经典类的查找顺序从左到右深度优先的规则,在访问d.foo1()的时候,D这个类是没有的…那么往上查找,先找到B,里面没有,深度优先,访问A,找到了foo1(),所以这时候调用的是A的foo1(),从而导致C重写的foo1()被绕过
15.__init__和__new__的区别
__new__
是一个静态方法,而 __init__
是一个实例方法
__new__
方法会返回一个创建的实例,而 __init__
什么都不返回
只有在 __new__
返回一个 cls 的实例时,后面的 __init__
才能被调用
当创建一个新实例时调用 __new__
,初始化一个实例时用 __init__
原文叙述:ps: __metaclass__
是创建类时起作用.所以我们可以分别使用 __metaclass__
, __new__
和 __init__
来分别在类创建,实例创建和实例初始化的时候做一些小手脚
16.单例模式
单例模式的核心结构中只包含一个被称为单例类的特殊类,通过单例模式可以保证系统中一个类只有一个实例而且该实例易于外界访问,从而方便对实例个数的控制并节约系统资源
如果希望在系统中某个类的对象只能存在一个,单例模式是最好的解决方案
new()在__init__()之前被调用的,用于生成实例对象
利用这个方法和类的属性的特点可以实现单例设计模式
单例模式是指创建唯一对象,单例模式设计的类只能实例
- 使用__new__()方法
class Singleton(object):
def __new__(cls, *args, **kw):
if not hasattr(cls, '_instance'):
orig = super(Singleton, cls)
cls._instance = orig.__new__(cls, *args, **kw)
return cls._instance
class MyClass(Singleton):
a = 1
- 共享属性
创建实例时把所有实例的__dict__指向同一个字典,这样它们具有相同的属性和方法
class Borg(object):
_state = {}
def __new__(cls, *args, **kw):
ob = super(Borg, cls).__new__(cls, *args, **kw)
ob.__dict__ = cls._state
return ob
class MyClass2(Borg):
a = 1
- 装饰器版本
def singleton(cls):
instances = {}
def getinstance(*args, **kw):
if cls not in instances:
instances[cls] = cls(*args, **kw)
return instances[cls]
return getinstance
@singleton
class MyClass3:
pass
- import方法
作为python的模块是天然的单例模式
# mysingleton.py
class My_Singleton(object):
def foo(self):
pass
my_singleton = My_Singleton()
# to use
from mysingleton import my_singleton
my_singleton.foo()
关于单例模式博客:https://blog.csdn.net/yuyh131/article/details/83104970
17.Python中的作用域
Python中,一个变量的作用域总是由在代码中被赋值的地方所决定
变量搜索的顺序:
本地作用域(Local) -> 当前作用域被嵌入的本地作用域(Enclosing locals) ->
全局/模块作用域(Global) -> 内置作用域(Builtin)
18.GIL线程全局锁
线程全局锁(Clobal Interpreter Lock), 即Python为了保证线程安全而采取的独立线程运行的限制
其实就是一个核只能在同一时间运行一个线程
对于IO密集型任务,Python的多线程起到作用,但是对于CPU密集型任务,Python的多线程几乎占不到任何优势,还有可能因为争夺资源而变慢
解决办法就是多进程和下面的协程(协程也是单CPU, 但是能减少切换代价、提升性能)
19.协程
简单点说协程是进程和线程的升级版,进程和线程都面临着内核态和用户态的切换问题而耗费许多切换时间,而协程就是用户自己控制切换的时机,不再需要陷入系统的内核态
python里最常见的yield就是协程的思想,可以查看第9个问题
20.闭包
闭包(closure)是函数式编程的重要的语法结构
闭包也是一种组织代码的结构,它同样提高了代码的可重复使用性
闭包是指当前作用域中的变量通过值或者引用的方式封装到lambda表达式中,
它使lambda表达式从一个普通的函数变成一个带隐藏参数的函数
创建一个闭包必须满足以下几点:
- 必须有一个内嵌函数
- 内嵌函数必须引用外部函数中的变量
- 外部函数的返回值必须是内嵌函数
21.Lambda函数
匿名函数
来看一个用lambda函数作为闭包的例子:
def my_add(n):
return lambda x:x+n
add_3 = my_add(3)
add_3(7) # 10
这里的lambda函数就是一个闭包,在全局作用域范围中
之所以返回10,是因为在my_add局域作用域中,变量n的值在闭包的作用使得它在全局作用域也可以被访问到
22.Python函数式编程
函数式编程支持:filter函数,map函数,reduce函数
filter函数的功能相当于过滤器
a = [1,2,3,4,5,6,7,]
b = filter(lambda x: x>5, a)
print(b) # [6,7]
map函数是对一个序列的每个项依次执行函数
a = map(lambda x:x*2, [1,2,3])
list(a) # [2,4,6]
reduce函数是对一个序列的每个项迭代调用函数
reduce(lambda x,y:x*y,range(1,4))
# >>> 6
23.Python里的拷贝
引用和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’]]
24.Python垃圾回收机制
Python GC主要使用引用计数来跟踪和回收垃圾。在引用计数的基础上,通过“标记-清除”解决容器对象可能产生的循环引用问题,通过“分代回收”以空间换时间的方法提高垃圾回收效率
1.引用计数:
PyObject是每个对象必有的内容,其中ob_refcnt就是做为引用计数。当一个对象有新的引用时,它的ob_refcnt就会增加,当引用它的对象被删除,它的ob_refcnt就会减少。引用计数为0时,该对象生命就结束了
优点:简单;实时性
缺点:维护引用计数消耗资源;循环引用
2.标记-清除机制
基本思路是先按需分配,等到没有空闲内存的时候从寄存器和程序栈上的引用出发,遍历以对象为节点、以引用为边构成的图,把所有可以访问到的对象打上标记,然后清扫一遍内存空间,把所有没标记的对象释放
3.分代技术
分代回收的整体思路:将系统中的所有内存块根据其存活时间划分为不同的集合,每个集合就成为一个“代”,垃圾收集频率随着“代”的存活时间的增大而减少,存活时间通常利用经过几次垃圾回收来度量
Python默认定义三代对象集合,索引数越大,对象存活时间越长
25.Python的List
推荐: http://www.jianshu.com/p/J4U6rR
26.Python的is
is是对比地址,==是对比值
27.read,readline和readlines
read 读取整个文件
readline 读取下一行,使用生成器方法
readlines 读取整个文件到一个迭代器以供我们遍历