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()  #私有函数也可调用

输出结果:
<module 'a' (namespace)>  # 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()

输出结果:
<module 'a.test' from 'C:\\Users\\libai\\PycharmProjects\\begin\\a\\test.py'> #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 '__main__.Grandson'>, <class '__main__.Son1'>, <class '__main__.Son2'>, <class '__main__.Parent'>, <class 'object'>)
面试题:以下的代码的输出将是什么? 说出你的答案并解释。
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>
<class '__main__.earth'>
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
必须传入 <class 'str'> 
---------------------------------
set 方法
<__main__.earth object at 0x000002254086D080>
bbs
必须传入 <class 'int'> 
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': <class 'str'>, 'age': <class 'int'>}
========================
set 方法
<__main__.earth object at 0x0000025C38B4E080>
libai
set 方法
<__main__.earth object at 0x0000025C38B4E080>
23
必须传入 <class 'int'> 
*************************************************
{'__module__': '__main__', '__init__': <function earth.__init__ at 0x0000025C38B34950>, '__dict__': <attribute '__dict__' of 'earth' objects>, '__weakref__': <attribute '__weakref__' of 'earth' objects>, '__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 =  <function earth.test at 0x0000021C699B47B8>
instance =  <__main__.earth object at 0x0000021C699CAEB8>
owner =  <class '__main__.earth'>
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__': <function __init__ at 0x0000019D88944598>, 'x': 28, '__module__': '__main__', '__dict__': <attribute '__dict__' of 'earth' objects>, '__weakref__': <attribute '__weakref__' of 'earth' objects>, '__doc__': None}
---------------------------------------------------------
<class '__main__.Foo'>
{'__module__': '__main__', '__init__': <function Foo.__init__ at 0x0000019D88944620>, '__dict__': <attribute '__dict__' of 'Foo' objects>, '__weakref__': <attribute '__weakref__' of 'Foo' objects>, '__doc__': None}
<class 'type'>
<class 'type'> # 类的类的类的类

自定义元类

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,142评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,298评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,068评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,081评论 1 291
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,099评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,071评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,990评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,832评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,274评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,488评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,649评论 1 347
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,378评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,979评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,625评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,796评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,643评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,545评论 2 352

推荐阅读更多精彩内容

  • Lua 5.1 参考手册 by Roberto Ierusalimschy, Luiz Henrique de F...
    苏黎九歌阅读 13,783评论 0 38
  • rljs by sennchi Timeline of History Part One The Cognitiv...
    sennchi阅读 7,322评论 0 10
  • 茉莉说,她迷恋站台,近乎痴迷的那种。我瞪大眼睛,说你没事吧!她说她很好,她喜欢站台是因为在那个地方,始终有人在离开...
    原木C阅读 411评论 0 1
  • 今天学习了《成为作家》第86到89页,重新看待世界需要从大街上的陌生人开始,观察每一个人每一个细节每一个动作。其实...
    喜水阅读 390评论 2 3
  • 妈妈在电话里哀怨的说:“你毕业了是不是就回来这边找工作了?我都好久没见过你了。” 我说:“妈妈呀,你怎么那么煽情,...
    林子一阅读 267评论 1 1