基础
Q:这两个参数是什么意思:*args,**kwargs?我们为什么要使用它们?
A: 如果我们不确定往一个函数中传入多少参数,或者我们希望以元组(tuple)或者列表(list)的形式传参数的时候,我们可以使用*args(单星号)。如果我们不知道往函数中传递多少个关键词参数或者想传入字典的值作为关键词参数的时候我们可以使用**kwargs(双星号),args、kwargs两个标识符是约定俗成的用法。
list = [1,2]
tuple = (1,2)
dic = {'a':1,'b':2}
def fun(*args,**kwargs):
print(args,kwargs)
fun(list) #([1, 2],) {}
fun(tuple) #((1, 2),) {}
fun(dic) #({'a': 1, 'b': 2},) {}
fun(*dic) #('a', 'b') {}
fun(1,2,3) #(1, 2, 3) {}
fun(a=1,b=2) #() {'a': 1, 'b': 2}
fun(*list) #(1, 2) {}
fun(*list,**dic)#(1, 2) {'a': 1, 'b': 2}
fun(0,list,1) #(0, [1, 2], 1) {}
fun(0,*list,c=1,**dic)#(0, 1, 2) {'c': 1, 'a': 1, 'b': 2}
- 自己:*和**都可以传入不确定个数参数,但是我们希望以元组或者list形式传参时使用一个参数,并且它会把传入参数转为元祖,而希望以dic形式传参时使用**,并且传参时加*和不加*是有区别的,举个例子:fun([1])->([1]) fun(*[1])->(1)
Q:谈一谈Python的装饰器(decorator)
A: 装饰器本质上是一个Python函数,它可以让其它函数在不作任何变动的情况下增加额外功能,装饰器的返回值也是一个函数对象。它经常用于有切面需求的场景。比如:插入日志、性能测试、事务处理、缓存、权限校验等。有了装饰器我们就可以抽离出大量的与函数功能无关的雷同代码进行重用。
- 参考:http://www.pythonsite.com/?p=113
- 自己:我觉得装饰器相当于高级函数+嵌套函数,可以在不改变原函数的情况下添加一些额外的功能。比如说在用scrapy框架的时候,将数据存储到mongodb,API里面就用到了@classmethon,from_crawl这个类方法实现在实例化这个pipelines类之前从setting里面获得一些想要的数据。
- @classmethodz装饰的类方法,@staticmethodz装饰的静态方法,普通的实例方法
- 根据调用方式来记,类方法:类.方法名,实例方法:实例.方法名,静态方法:类/实例.方法名
Q:简要描述python的垃圾回收机制
A:Python中的垃圾回收是以引用计数为主,标记-清除和分代收集为辅。
引用计数:Python在内存中存储每个对象的引用计数,如果计数变成0,该对象就会消失,分配给该对象的内存就会释放出来。
标记-清除:一些容器对象,比如list、dict、tuple,instance等可能会出现引用循环,对于这些循环,垃圾回收器会定时回收这些循环(对象之间通过引用(指针)连在一起,构成一个有向图,对象构成这个有向图的节点,而引用关系构成这个有向图的边)。
分代收集:Python把内存根据对象存活时间划分为三代,对象创建之后,垃圾回收器会分配它们所属的代。每个对象都会被分配一个代,而被分配更年轻的代是被优先处理的,因此越晚创建的对象越容易被回收。
- 参考:https://www.jianshu.com/p/1e375fb40506
- 自己:python中的垃圾回收是以引用计数为主,标记-清除和分代收集为辅。python创建一个对象其实是创建了一个结构体,其中ob_refcnt就是作为引用计数。当有一个对象有新的引用时,它的ob_refcnt就会增加,当引用它的对象被删除,它的ob_refcent就会减少,当引用计数为0时,该对象生命就结束了。
Q:python多线程?
A:Python并不支持真正意义上的多线程,Python提供了多线程包。Python中有一个叫Global Interpreter Lock(GIL)的东西,它能确保你的代码中永远只有一个线程在执行。经过GIL的处理,会增加执行的开销。这就意味着如果你先要提高代码执行效率,使用threading不是一个明智的选择,当然如果你的代码是IO密集型,多线程可以明显提高效率,相反如果你的代码是CPU密集型的这种情况下多线程大部分是鸡肋。
pool = Pool() #创建进程池
pool.map(fun, [i * 10 for i in range(10)]) #fun(i)
pool.close() #不再创建进程
pool.join() #等待所有子进程执行完毕
- 自己:因为GIL锁的存在,python多线程并不能有效的利用多核,可以用多进程来实现多核任务。
Q:说明os,sys模块不同,并列举常用的模块方法?
A:os模板提供了一种方便的使用操作系统函数的方法
sys模板可供访问由解释器使用或维护的变量和与解释器交互的函数
- 自己:os用来进行和系统的交互,sys用来进行和python解释器之间的交互,我一般用os来进行文件操作,path.abspath(’.’)返回命令行当前文件路径,os.path.join(file,’’)进行拼接
Q:lambda,map有什么用?
A:简单来说,lambda表达式通常是当你需要使用一个函数,但是又不想费脑袋去命名一个函数的时候使用,也就是通常所说的匿名函数。
lambda表达式一般的形式是:关键词lambda后面紧接一个或多个参数,紧接一个冒号“:”,紧接一个表达式。lambda表达式是一个表达式不是一个语句。
map() 会根据提供的函数对指定序列做映射。
第一个参数 function 以参数序列中的每一个元素调用 function 函数,返回包含每次 function 函数返回值的新列表。
python2返回列表,python3返回迭代器
f = lambda x,y,z:x+y+z
print(f(1,2,3)) #6
L = {
'f1':(lambda x,y:x**2+y**2),
'f2':(lambda x,y:x**3+y**3)
}
print(L.get('f2')(1,2)) #9
m = map(lambda x:x*x,[y for y in range(10)])
print(m,list(m)) #<map object at 0x00000257BA83FA58> [0, 1, 4, 9, 16]
- 自己:需要一个函数,但是又不想命名,一般用在某些只会使用一次的函数上。
Q:Python是如何进行类型转换的?
A:基本数据结构之间的转换,用自带的方法int()、long()、float()、str()、list()、truple()…
特别提到bytes和string之间的转换
# bytes object
b = b"example"
print(type(b))
# str object
s = "example"
print(type(s))
# str to bytes
print(type(bytes(s, encoding="utf8")))
# bytes to str
print(type(str(b, encoding="utf8")))
# an alternative method
# str to bytes
print(type(str.encode(s)))
# bytes to str
bytes.decode(b)
print(type(bytes.decode(b)))
- 自己:b.decode(b) , s.encode(s)
python浅拷贝和深拷贝
A:Python中对象之间的赋值是按引用传递的,如果要拷贝对象需要使用标准模板中的copy
copy.copy:浅拷贝,只拷贝父对象,不拷贝父对象的子对象。
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] #直接赋值,ab地址相同,a变b变,b变a变
c = [1, 2, 3, 4, ['a', 'b', 'c']] #浅拷贝:最外层为深拷贝(地址发生变化),内层为对象的引用,所以最外层不变,能层跟着变
d = [1, 2, 3, 4, ['a', 'b']] #每一层地址都改变,所以值不会跟着变
- 参考地址:https://zhuanlan.zhihu.com/p/32669561
- 自己:首先复制是对象的引用,比如b=a,只是相当于把b指向a所指向的地址,所以a变b变,b变a也变。深拷贝,也就是copy.deepcopy(),相当于每一层都重新创建了一个新的对象,也就是说原数据不管怎么变,对深拷贝的数据都不影响,打印id可以看到每一层都不一样。浅拷贝,也就是copy.copy(),如果最外层为可变对象(比如list,dic),最外层为深拷贝,第二层开始都为对象的引用,如果最外层为不可变对象(str,truple),那么id(最外层)也不会变。举上面的例子。
Q:Python中单下划线和双下划线分别是什么?
A:name:一种约定,Python内部的名字,用来与用户自定义的名字区分开,防止冲突
_name:一种约定,用来指定变量私有
__name:对解释器有特定含义。Python会改写这些名称,以免与子类中定义的名称产生冲突
class A(object):
def __method(self):
print('A')
class B(A):
def __method(self):
print('B')
print(dir(B())) #['_A__method','_B__method']
b = B()
b._B__method() #B
- 参考:https://blog.csdn.net/wukai_std/article/details/54972522
- 自己:__name__python约定的内部名字,_name私有(private)变量,想要访问可以通过get、set方法(和java类似),__name有特定的含义,python会改写这些名称,避免子类的重写
Q:说一说python自省
A:自省就是面向对象的语言所写的程序在运行时,所能知道对象的类型。简单一句话就是运行时能够获得对象的类型。比如:type()、dir()、getattr()、hasattr()、isinstance()
- 参考:https://kb.cnblogs.com/page/87128/
- 自己:自省就是运行时获得对象的类型,我一般用type()
Q:python2.X和python3.X的区别
A:
- 基本语法不同(print、整数除法)
- python2默认使用ASCLL,python3默认使用Unicode
- 某些库不兼容
Python 3.6.3 (v3.6.3:2c5fed8, Oct 3 2017, 18:11:49) [MSC v.1900 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> 5/2
2.5
>>> 5//2
2
>>> exit()
C:\Users\acer>python2
Python 2.7.14 (v2.7.14:84471935ed, Sep 16 2017, 20:19:30) [MSC v.1500 32 bit (Intel)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> 5/2
2
>>> 5//2
2
>>>
- 自己:我没怎么用过Python2,我觉得2和3的区别主要体现在基本语法,比如print;编码方式,python2默认ASCLL,3默认Unicode;以及相关库的使用,比如urllib库
Q:python基本库的使用(urllib、urllib2、pandams、numpy)
- 参考:https://blog.csdn.net/drdairen/article/details/51149498
- 自己:urllib2可以接受url或者request对象 所以可以设置请求头
urllib不行,但是urllib有个比较好用的urllib.parse.urlencode(),所以我一般用urllib和requests库。
Q:super用法
A:super指的是MRO中的下一个类。
#!/usr/bin/python
# -*- coding: UTF-8 -*-
'''
super指MRO中的下一个类
class Root(object) class B(Root) class C(Root) class D(B,C)
MRO 顺序 D B C Root object
##https://www.zhihu.com/question/20040039
尽量使用
class Child(Parent):
def __init__(self):
Parent.__init__(self)
'''
class FooParent(object):
def __init__(self):
self.parent = 'I\'m the parent.'
print ('Parent')
def bar(self,message):
print ("%s from Parent" % message)
class FooChild(FooParent):
def __init__(self):
# super(FooChild,self) 首先找到 FooChild 的父类(就是类 FooParent),然后把类B的对象 FooChild 转换为类 FooParent 的对象
super(FooChild,self).__init__()
self.parent = 'I\'m the child.'
print ('Child')
def bar(self,message):
super(FooChild, self).bar(message)
print ('Child bar fuction')
print (self.parent) ##子类属性覆盖父类属性,优先调用子类属性
if __name__ == '__main__':
fooChild = FooChild()
fooChild.bar('HelloWorld')
- 参考:https://www.zhihu.com/question/20040039/answer/57883315
- 自己:super并不是指父类,而是指MRO中的下一个类,为了避免出现这种不可预料的bug,我一般选择类名.方法名来调用父类方法。
Q:python异步
- 参考:https://www.liaoxuefeng.com/wiki/0014316089557264a6b348958f449949df42a6d3a2e542c000/00143208573480558080fa77514407cb23834c78c6c7309000
- 自己:常用的是异步IO,当d艾玛需要执行一个耗时的IO操作时,它只发出IO指令,并不等待IO结果,然后去执行其他代码,一段时间后,IO返回结果,再通知CPU去执行。目前暂时没有这方面的需求,所以还没怎么用到过。