Python 的GIL,深拷贝和浅拷贝,私有化,多继承,闭包

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

你可能感兴趣的:(Python 的GIL,深拷贝和浅拷贝,私有化,多继承,闭包)