Python的GIL全局解释锁只存在CPython解释器,使用其他语言编写的解释器是没有这个问题的
GIL面试题如下
描述Python GIL的概念,以及它对python多线程的影响?
编写一个多线程抓取网页的程序,并阐明多线程抓取程序是否可比单线程性能有提升,并解释原因。
参考答案:
1. Python语言和GIL没有半毛钱关系。仅仅是由于历史原因在Cpython虚拟机(解释器),难以移除GIL。
2. GIL:全局解释器锁。每个线程在执行的过程都需要先获取GIL,保证同一时刻只有一个线程可以执行代码
3. 线程释放GIL锁的情况: 在IO操作等可能会引起阻塞的system call之前,可以暂时释放GIL,
但在执行完毕后,必须重新获取GIL Python 3.x使用计时器(执行时间达到阈值后,当前线程释放GIL)
或Python 2.x,tickets计数达到100
Python使用多进程是可以利用多核的CPU资源的。
3. 多线程爬取比单线程性能有提升,因为遇到IO阻塞会自动释放GIL锁
深拷贝、浅拷贝
1. 浅拷贝
浅拷贝是对于一个对象的顶层拷贝
通俗的理解是:拷贝了引用,并没有拷贝内容
2. 深拷贝
深拷贝是对于一个对象所有层次的拷贝(递归)
浅拷贝对不可变类型和可变类型的copy不同
import copy
copy.copy() 和 copy.deepcopy() 的两个copy方法
对于可变类型,会进行浅拷贝
对于不可变类型,不会拷贝,仅仅是指向
对于不可变类型包含可变类型,copy.copy() 浅拷贝,copy.deepcopy() 全部拷贝
如果copy.copy拷贝的是元组,那么它不会进行拷贝,仅仅是指向
原因:因为元组是不可变类型,那么意味着数据一定不能修改,因此copy.copy的时候它会自动判断,
如果是元组就是指向
如果拷贝的是元组包含列表类型的数据,copy.copy() 进行浅拷贝,copy.deepcopy() 进行全部拷贝
测试 深拷贝 和 浅拷贝
1. test
import copy
a = [11, 22]
b = [33, 44]
c = [a, b]
d = copy.copy(c)
print(id(c) == id(d)) # False
print(id(c[0]) == id(d[0])) # True
copy.copy() 拷贝可变类型,只是浅拷贝
2. test
import copy
a = (11, 22)
b = (33, 44)
c = (a, b)
d = copy.copy(c)
print(id(c) == id(d)) # True
print(id(c[0]) == id(d[0])) # True
copy.copy() 拷贝不可变类型,只是指向
3. test
import copy
a = [11, 22]
b = [33, 44]
c = [a, b]
d = copy.deepcopy(c)
print(id(c) == id(d)) # False
print(id(c[0]) == id(d[0])) # False
copy.deepcopy() 拷贝可变类型,全部拷贝
4. test
import copy
a = (11, 22)
b = (33, 44)
c = (a, b)
d = copy.deepcopy(c)
print(id(c) == id(d)) # True
print(id(c[0]) == id(d[0])) # True
copy.deepcopy() 拷贝不可变类型,还是指向
5. test 不可变类型包含可变类型,就全部拷贝
import copy
a = [11, 22]
b = [33, 44]
c = (a, b)
d = copy.deepcopy(c)
print(id(c) == id(d)) # False
print(id(c[0]) == id(d[0])) # False
copy.deepcopy() 不可变类型包含可变类型,就全部拷贝
c = [11, 22]
d = c[:] 与 d = copy.copy(c) 一样,属于浅拷贝
对于字典来说,有一点不同:
import copy
a = dict(name="libai", maybe=[11, 22])
b = copy.copy(a)
print(id(a) == id(b)) # False 这里的字典是不相等的
print(id(a["maybe"]) == id(b["maybe"])) # True 键值相等
copy.copy() 拷贝内部包含可变类型
import copy
a = dict(name="libai", maybe=[11, 22])
b = copy.deepcopy(a)
print(id(a) == id(b)) # False 这里的也是不相等
print(id(a["maybe"]) == id(b["maybe"])) # False 都拷贝了
copy.deepcopy() 对于字典,全部拷贝
一般来说,函数传递的都是值的引用,必要时候需要用到深拷贝
Python 的私有化
1. xx:公有变量
2. _x:单前置下划线,私有化属性或方法,from somemodule import * 禁止导入,类对象和子类可以访问
3. __xx:双前置下划线,避免与子类中的属性命名冲突,无法在外部直接访问(名字重整所以访问不到)
4. __xx__:双前后下划线,用户名字空间的魔法对象或属性。例如:__init__ , 不要自己发明这样的名字
5. xx_:单后置下划线,用于避免与Python关键词的冲突
总结:
1. 父类中属性名为__名字的,子类不继承,子类不能访问
2. 如果在子类中向__名字赋值,那么会在子类中定义的一个与父类相同名字的属性
3. _名的变量、函数、类在使用from xxx import *时都不会被导入
导入模块
1. 直接 import
2. 通过sys模块导入自定义模块的path
先导入sys模块
然后通过sys.path.append(path) 函数来导入自定义模块所在的目录
导入自定义模块。
import sys
sys.path.append(r"C:\Users\Pwcong\Desktop\python")
import pwcong
pwcong.hi()
3. 通过pth文件找到自定义模块
动态导入
#a 下的 test.py 文件
def test_a():
print('this is a')
def _test_b():
print('this is b')
(1)导入模块 from xxx import *
from a.test import *
test_a()
test_b()
输出结果:
#找不到 test_b 函数,就算改成_test_b 也是一样
NameError: name 'test_b' is not defined
Process finished with exit code 1
(2)导入模块 __import__('a.test')
module_t = __import__('a.test') #传入字符串
print(module_t) #定位到 test 文件的顶级目录 a
module_t.test.test_a()
module_t.test._test_b() #私有函数也可调用
输出结果:
# a 目录
this is a
this is b
(3)导入模块 importlib
import importlib
module_t = importlib.import_module('a.test')
print(module_t) #直接定位到 test
module_t.test_a()
module_t._test_b()
输出结果:
#a.test
this is a
this is b
导入模块时的路径搜索
在 ipython 中输入下面的命令
import sys
sys.path
打印结果:
['',
'D:\\anaconda\\Scripts',
'D:\\anaconda\\python36.zip',
'D:\\anaconda\\DLLs',
'D:\\anaconda\\lib',
'D:\\anaconda',
'D:\\anaconda\\lib\\site-packages',
'D:\\anaconda\\lib\\site-packages\\win32',
'D:\\anaconda\\lib\\site-packages\\win32\\lib',
'D:\\anaconda\\lib\\site-packages\\Pythonwin',
'D:\\anaconda\\lib\\site-packages\\IPython\\extensions',
'C:\\Users\\libai\\.ipython']
导入模块时的路径搜索
1. 从上面列出的目录里依次查找要导入的模块文件
2. '' 表示当前路径
3. 列表中的路径的先后顺序代表了python解释器在搜索模块时的先后顺序
程序执行时添加新的模块路径
sys.path.append('/home/itcast/xxx')
sys.path.insert(0, '/home/itcast/xxx') # 插入到第一个位置,可以确保先搜索这个路径
重新导入模块
模块被导入后,import module不能重新导入模块,重新导入需用reload
注意:from xxxx import 变量 时,要注意导入的是变量的值,而不是一个指向
封装、继承、多态 是面向对象的3大特性
多继承
super().__init__相对于类名.__init__,在单继承上用法基本无差
但在多继承上有区别,super方法能保证每个父类的方法只会执行一次,而使用类名的方法会导致方法被执行多次
多继承时,使用super方法,对父类的传参数,应该是由于python中super的算法导致的原因,
必须把参数全部传递,否则会报错
单继承时,使用super方法,则不能全部传递,只能传父类方法所需的参数,否则会报错
多继承时,相对于使用类名.__init__方法,要把每个父类全部写一遍, 而使用super方法,
只需写一句话便执行了全部父类的方法,这也是为何多继承需要全部传参的一个原因
使用 print(Grandson.__mro__) 显示类的继承顺序(前提:Son1、Son2都是Parent的子类)
(, , , , )
面试题:以下的代码的输出将是什么? 说出你的答案并解释。
class Parent(object):
x = 1
class Child1(Parent):
pass
class Child2(Parent):
pass
print(Parent.x, Child1.x, Child2.x)
Child1.x = 2
print(Parent.x, Child1.x, Child2.x)
Parent.x = 3
print(Parent.x, Child1.x, Child2.x)
答案, 以上代码的输出是:
1 1 1
1 2 1
3 2 3
说明:
使你困惑或是惊奇的是关于最后一行的输出是 3 2 3 而不是 3 2 1。为什么改变了 Parent.x 的值还会
改变 Child2.x 的值,但是同时 Child1.x 值却没有改变?
这个答案的关键是,在 Python 中,类变量在内部是作为字典处理的。如果一个变量的名字没有在当前类
的字典中发现,将搜索祖先类(比如父类)直到被引用的变量名被找到(如果这个被引用的变量名既没有
在自己所在的类又没有在祖先类中找到,会引发一个 AttributeError 异常 )。
因此,在父类中设置 x = 1 会使得类变量 x 在引用该类和其任何子类中的值为 1。
这就是因为第一个 print 语句的输出是 1 1 1。
随后,如果任何它的子类重写了该值(例如,我们执行语句 Child1.x = 2),然后,
该值仅仅在子类中被改变。这就是为什么第二个 print 语句的输出是 1 2 1。
最后,如果该值在父类中被改变(例如,我们执行语句 Parent.x = 3),这个改变会影响到任何未重写
该值的子类当中的值(在这个示例中被影响的子类是 Child2)。
这就是为什么第三个 print 输出是 3 2 3。
用新式类作输入类型检测,get,set,del
class Type:
def __init__(self, key):
self.key = key
def __get__(self, instance, owner):
print('get 方法')
print(instance)
print(owner)
return instance.__dict__[self.key]
def __set__(self, instance, value):
print('set 方法')
print(instance)
print(value)
if not isinstance(value,str):
print("必须传入字符串")
return
instance.__dict__[self.key] = value
def __delete__(self, instance):
print('del 方法')
print(instance)
del instance.__dict__[self.key]
class earth:
name = Type('name')
def __init__(self, name, age):
self.name = name
self.age = age
e = earth('libai', 20)
print(e.__dict__)
print(e.name)
e.name = 12
print(e.__dict__)
print('----------------------------------')
del e.name
print(e.__dict__)
输出结果:
set 方法
<__main__.earth object at 0x000002257741AEF0>
libai
{'name': 'libai', 'age': 20}
get 方法
<__main__.earth object at 0x000002257741AEF0>
libai
set 方法
<__main__.earth object at 0x000002257741AEF0>
12
必须传入字符串
{'name': 12, 'age': 20}
----------------------------------
del 方法
<__main__.earth object at 0x000002257741AEF0>
{'age': 20}
改进上面的程序,使得可以检测 多种类型
class Type:
def __init__(self, key, except_type):
self.key = key
self.except_type = except_type
def __get__(self, instance, owner):
print('get 方法')
print(instance)
print(owner)
return instance.__dict__[self.key]
def __set__(self, instance, value):
print('set 方法')
print(instance)
print(value)
if not isinstance(value,self.except_type):
print("必须传入 %s " %self.except_type)
return
instance.__dict__[self.key] = value
def __delete__(self, instance):
print('del 方法')
print(instance)
del instance.__dict__[self.key]
class earth:
name = Type('name', str)
age = Type('age', int)
def __init__(self, name, age):
self.name = name
self.age = age
e = earth('libai', 20)
print('---------------------------------')
e.name = 20
print('---------------------------------')
e.age = 'bbs'
输出结果:
set 方法
<__main__.earth object at 0x000002254086D080>
libai
set 方法
<__main__.earth object at 0x000002254086D080>
20
---------------------------------
set 方法
<__main__.earth object at 0x000002254086D080>
20
必须传入
---------------------------------
set 方法
<__main__.earth object at 0x000002254086D080>
bbs
必须传入
Python 的闭包
def deco(x, y):
def wrapper(z):
return x+y+z
return wrapper
d = deco(100, 200)
print(d(300)) // 600
思考:函数、匿名函数、闭包、对象 当做实参时 有什么区别?
1. 匿名函数能够完成基本的简单功能,,,传递是这个函数的引用 只有功能
2. 普通函数能够完成较为复杂的功能,,,传递是这个函数的引用 只有功能
3. 闭包能够将较为复杂的功能,,,传递是这个闭包中的函数以及数据,因此传递是功能+数据
4. 对象能够完成最为复杂的功能,,,传递是很多数据+很多功能,因此传递是功能+数据
装饰器,改成类型检测,如果检测到不匹配,就print 和不写入
def deco(**kwargs): #接收参数传入**kwargs
def wrapper(obj): #返回的 wrapper 传入earth
print('========================')
for key,value in kwargs.items(): # .items() 格式为元组
setattr(obj, key, Type(key, value))
return obj
print(kwargs)
return wrapper
#类型传入检测
class Type:
def __init__(self, key, except_type):
self.key = key
self.except_type = except_type
def __get__(self, instance, owner):
print('get 方法')
print(instance)
print(owner)
return instance.__dict__[self.key]
def __set__(self, instance, value):
print('set 方法')
print(instance)
print(value)
if not isinstance(value,self.except_type):
print("必须传入 %s " %self.except_type)
return
instance.__dict__[self.key] = value
def __delete__(self, instance):
print('del 方法')
print(instance)
del instance.__dict__[self.key]
@deco(name=str,age=int) #name 传入类型 str,age传入类型 int
class earth:
def __init__(self, name, age):
self.name = name
self.age = age
e = earth('libai', '23') #触发set方法
print('*************************************************')
print(earth.__dict__)
print(e.__dict__)
输出结果:
{'name': , 'age': }
========================
set 方法
<__main__.earth object at 0x0000025C38B4E080>
libai
set 方法
<__main__.earth object at 0x0000025C38B4E080>
23
必须传入
*************************************************
{'__module__': '__main__', '__init__': , '__dict__': , '__weakref__': , '__doc__': None, 'name': <__main__.Type object at 0x0000025C38B4AF98>, 'age': <__main__.Type object at 0x0000025C38B4E048>}
{'name': 'libai'}
@property 就是把装饰的类或者函数当成参数传入 property(类) 中,执行完后把返回值再赋给所装饰的类,下面是自定义的 @property,与原生的@property 有类似的功能
class Lazyproperty:
def __init__(self, func):
self.func = func
print('func = ', self.func)
def __get__(self, instance, owner):
print('instance = ', instance)
print('owner = ', owner)
# 以下这句 self.func(instance) 被执行并赋值给print打印时,
# 会执行一遍 test 函数,这也就是为什么输出结果中会有两个 test被打印
print('------------------>>>', self.func(instance))
print('***************************************************')
return self.func(instance)
class earth:
def __init__(self):
pass
@Lazyproperty
def test(self):
print('test')
return '你好吗?'
e = earth()
print(e.test)
输出结果:
func =
instance = <__main__.earth object at 0x0000021C699CAEB8>
owner =
test
------------------>>> 你好吗? # test 被打印两次
***************************************************
test
你好吗?
优先级寻找十分重要啊,
class Lazyproperty:
def __init__(self, func):
self.func = func
def __get__(self, instance, owner):
print('get')
res = self.func(instance)
setattr(instance, self.func.__name__, res)
return res
# 如果存在 __set__ 方法,class Lazyproperty 为数据描述符,优先级高于实例属性,
# 寻找属性时会优先寻找 数据描述符,就会每次执行都调用一次 get 方法
# 现在不存在 __set__ 方法,class Lazyproperty 为非数据描述符,优先级低于实例属性,
# 寻找属性时会优先寻找 实例属性,
# 整个程序执行过程:
# 首先呢,@Lazyproperty 会先执行一遍,把 test 函数当成参数传入,
# 实例化后再赋给test,下面,实例化 earth 类为 e,直接传参到
# self.x 和 self.y ;
# 调用 e.test 过程中,会先寻找 e 的实例属性,实例属性不存在 test,
# 那就去找非数据描述符, class Lazyproperty 的 get 方法被执行,
# instance 是类的实例,也就是 e ,self.func 就是 test,
# owner 是 earth 类,在 earth 类中的 test 函数必须传入一个参数,
# 也就是 instance ,self.func(instance) 执行结果被存到 e.__dict__
# 实例属性中,之后 get 方法就不会执行了,因为在实例属性中找得到结果
# 可以实现不用每次执行都计算一遍
# def __set__(self, instance, value):
# pass
class earth:
def __init__(self, x, y):
self.x = x
self.y = y
@Lazyproperty
def test(self):
return (self.x*self.y)
e = earth(10,22)
print(e.test)
print(e.test)
print(e.test)
print(e.__dict__)
输出结果:
get
220
220
220
{'x': 10, 'y': 22, 'test': 220}
类创建实例对象,元类创建类,python 中任何 class 定义的类其实都是 type 类实例化的对象,类也可以直接用 type 来创建
class Foo:
def __init__(self):
pass
def __init__(self):
pass
earth = type('earth',(object,),{'__init__':__init__,'x':28})
print(earth.__dict__)
print('---------------------------------------------------------')
print(Foo)
print(Foo.__dict__)
print(type(Foo))
print(type(type(type(Foo)))) # type 又是谁创建的呢? 还是 type!
输出结果:
{'__init__': , 'x': 28, '__module__': '__main__', '__dict__': , '__weakref__': , '__doc__': None}
---------------------------------------------------------
{'__module__': '__main__', '__init__': , '__dict__': , '__weakref__': , '__doc__': None}
# 类的类的类的类
自定义元类
class MyType(type):
def __init__(self, a, b, c):
print('元类的构造函数执行')
def __call__(self, *args, **kwargs):
obj = object .__new__(self)
self.__init__(obj, *args, **kwargs)
return obj
class Foo(metaclass=MyType):
def __init__(self, name):
self.name = name
f1 = Foo('alex')
http://www.cnblogs.com/linhaifeng/articles/6232220.html