python高级1

2.1GIL(全局解释器锁)

GIL(Global Interpreter Lock)

2.1.1GIL执行流程

没有互斥锁的情形:

创建线程1和线程2,当启动线程1和线程2时,线程1先拿到了公共数据count(count的初始值为0),线程1向python解释器申请到了GIL,解释器开辟线程执行线程1,然后,cpu开始执行代码,执行了一段时间后,还未完成count+=1的操作,执行时间到了,释放GIL。此时,线程2向python解释器申请到了GIL,解释器开辟线程执行线程2,然后,cpu开始执行代码,经过一段时间后,完成了count+=1的操作,将count=1赋值给count并且释放GIL。此时线程1再次向python解释器申请到了GIL,解释器开始执行线程1,但此时是在之前执行到的那个位置继续执行,一段时间后,完成了count+=1的操作,并将count=1赋值给了count,但此时的count已经是1了,这就导致了本来预想的是给count加了两次,但实际上是只加了一次。

有互斥锁的情形:

创建线程1和线程2,当启动线程1和线程2时,线程1先拿到了公共数据count(count的初始值为0),并且给公共数据加了互斥锁,然后,线程1向python解释器申请到了GIL,解释器开辟线程执行线程1,接着,cpu开始执行代码,执行了一段时间后,还未完成count+=1的操作,执行时间到了,释放解释器的GIL,但此时并未释放公共数据的那把互斥锁,线程2即使拿到了GIL,也拿不到公共数据,线程2必须继续等待,只能切回线程1继续执行线程1。

2.1.2为什么会设计GIL这把锁

为了解决多线程之间数据完整性

2.1.3为何还要使用多进程

多线程:适用于IO密集型:数据传输,需要等待,不能影响其他线程的运行。

多进程:适用于计算密集型。比如计算某一个文件夹的大小。

计算密集型的任务完全是依靠于cpu的核数,所以需要尽量的完全利用cpu的。Python中多进程是可以利用多核的。

知识补充:使用htop命令可以查看cpu的执行情况

2.2浅拷贝和深拷贝

2.2.1赋值操作认知统一           

(1)赋值是将一个对象的地址给一个变量,让该变量指向该地址

(2)修改不可变对象(str、tuple、int等)需要开辟新空间

(3)修改可变对象(list等)中的某一个值,该对象不需要开辟新空间;但若将可变对象整体修改,那么需要开辟新空间

2.2.2浅拷贝

(1)浅拷贝仅仅复制了容器中元素的地址

(2)浅拷贝的实现方式:

使用copy模块中的copy函数

使用切片操作

使用工厂函数(list/set/dir)

2.2.3深拷贝(copy.deepcopy())

完全拷贝了一个容器的副本,容器内部元素地址都不一样

2.2.4总结浅拷贝和深拷贝

(1)如果是普通的=号,看等号右边是可变对象还是不可变对象

如果是不可变对象,在重新赋值的时候,直接开辟一块新空间

如果是可变对象,

A.如果改变的是可变对象的整体,地址会改变

B.如果改变的是可变对象的某一个元素,地址不变,改变的元素地址会发生变化

(2)如果是浅拷贝(切片、copy.copy()、工厂函数(list()、set()、dir())

返回的新对象,跟原对象的地址是不一样的,但是新对象中的元素的地址跟原对象中的地址是一样的。

(3)如果是深拷贝:copy.deepcopy()

如果深拷贝的对象中,都是不可变对象,效果跟浅拷贝一样。

如果深拷贝的对象中,是可变对象,重新来了一遍,对象以及对象中元素的地址都不一样。

2.3公有属性、私有属性和保护属性

xx:公有变量

_x:单前置下划线,From somemodule import * 禁止导入

__x:私有变量,外界不允许访问

__xx__:魔法对象

xx__:后置下划线,用于避免与python关键字重名

2.3.1私有属性的访问方法1

class Person(object):
    def __init__(self,name,age,gender):
        self.name = name
        if age < 0 or age >100:
            age = 18
        self.__age = age
        if gender != '男' and gender!='女':
            gender = '男'
        self.__gender = gender

    def say_hello(self):
        print("大家好,我叫%s,我今年%d岁了,我是%s生" %(self.name,self.__age,self.__gender))

person = Person("张三",180,"跟班长一样")
person.say_hello()

2.3.2私有属性的访问方法2

# 自定义set和get方法
class Person(object):
    def __init__(self, name):
        self.name = name
        self.__age = 0
        self.__sex = "男"

    # def get_age(self):
    #     return self.__age
    # 
    # 
    # def set_age(self, age):
    #     if age < 0 or age > 100:
    #         self.__age = 0
    #     else:
    #         self.__age = age
    #Or:
    def get_age(self):
    if self.__age < 0 or self.__age > 100:
        self.__age = 18
        return self.__age

    def set_age(self, age):
    	   self.__age = age


person = Person("张三")
person.set_age(120)
print(person.get_age())

2.3.3私有属性的访问方法3:property

class Person(object):
    def __init__(self, name):
        self.name = name
        self.__age = 0
        self.__sex = "男"

    def set_age(self, age):
        if age < 0 or age > 100:
            self.__age = 0
        else:
            self.__age = age

    def get_age(self):
        return self.__age
    #Or:
    # def get_age(self):
    #     if self.__age < 0 or self.__age > 100:
    #         self.__age = 0
    #         return self.__age
    #     else:
    #         return self.__age
    # 
    # def set_age(self, age):
    #     self.__age = age

    def del_age(self):
        del self.__age

    # property将get_age和set_age的引用进行了封装,赋值给了age
    # 当对age进行赋值的时候,执行的是set_age
    # 当对age进行取值的时候,执行的是get_age
    # 注意顺序,必须先是get,然后是set
    age = property(get_age, set_age, del_age)


person = Person("李四")
person.age = 180
print(person.age)

2.3.4私有属性的访问方法4:装饰器封装

class Person(object):
    def __init__(self, name):
        self.name = name
        self.__age = 0
        self.__sex = "男"

# 1、如果使用@property的方式,先把所有的方法名字,改为一致
# 2、在取值函数的上面加上@property
# 3、在设值和删除值的前面加上@......
    @property
    def age(self):
        return self.__age

    @age.setter
    def age(self, age):
        if age < 0 or age > 100:
            self.__age = 0
        else:
            self.__age = age

    #Or:
    # @property
    # def age(self):
    #     if self.__age < 0 or self.__age > 100:
    #         self.__age = 0
    #         return self.__age
    #     else:
    #         return self.__age
    # 
    # @age.setter
    # def age(self, age):
    #     self.__age = age

person = Person("李四")
person.age = 180
print(person.age)

2.4模块导入相关

2.4.1module和package

(1)module就是一个.py文件

(2)package就是一个包含.py文件的文件夹,文件夹中还包含一个特殊文件__.init__.py

2.4.2查看导入模块搜索路径

  导入sys模块

  输入:sys.path查看导入模块搜索路径(默认从当前路径找起)

2.4.3添加导入模块搜索路径

sys.path.append(“路径”)

sys.path.insert(“路径”)

2.4.4重新导入加载模块

from imp import reload

reload(模块名称)

2.4.5模块循环导入

你中有我,我中有你”

在程序设计中,应该避免模块的循环导入

2.5反射

(1)概念:反射就是通过字符串的形式,导入模块,通过字符串的形式,去模块中寻找指定函数、并执行。

(2)作用:可以使用字符串的形式去模块中对模块中的成员进行增删改查

2.5.1动态导入模块(__import__)

将模块名以字符串的形式进行导入

__import__返回导入的模块对象的引用

fromlist:允许从其他的路径下进行导入

2.5.2反射中四个常用的函数

(1)getattr()

作用:找到指定对象中的指定成员,如果存在则返回成员引用,否则返回None

这个对象可以是模块,也可以普通的对象

参数一:表示要去哪个对象中查找成员

参数二:表示要查找的那个成员

参数三:表示如果找不到,返回default=None

(2)hasattr()

作用:判断指定对象中是否含有指定成员,有则返回True,否则返回False

参数一:要判断的模块对象

参数二:要找的成员

(3)setattr()

作用:给指定对象添加指定成员

例如:

#给指定对象添加属性

setattr(person,"age",18)

#给指定对象添加方法

setattr(person,"hello_python",lambda self:print("hello_python"))

# 由于hello_python是动态生成的,所以调用的时候,需要手动指定self的值

person.hello_python(person)

(4)delattr()

作用:删除指定对象中的指定成员

2.6多继承的问题

2.6.1继承的目的

为了让子类可以使用父类的成员,实现代码的复用。

2.6.2多继承带来的问题

钻石继承带来的问题就是子类会调用多次父类的__init__函数,造成资源浪费。

2.6.3解决方案:使用super()

(1)执行原理

super(cls,instance)

super会先获取instance的__mro__列表(方法关系解析顺序列表)然后找到列表中cls的下一个类,返回

(2)简化写法

super()

(3)使用super()的参数问题

在使用super()的过程中,会遇到参数传递混乱的问题

(4)使用*args和**kwargs可以解决super()的参数问题

建议:在使用super时,建议使用加参数的形式,这样更便于阅读

2.6.4*args和**kwargs的使用

(1)*args

当*args以形参出现时,解释器看到*args,会将传进来的多余的参数都赋给*args;当args以实参出现时,表示打包,会得到一个元组;当*args以实参出现时,表示拆包

(2)**kwargs

当**kwargs以形参出现时,解释器看到**kwargs,会将传进来的多余的参数都赋给**kwargs;当kwargs以实参出现时,表示打包,会得到一个字典;当**kwargs以实参出现时,表示拆包(注意:不支持print(**kwargs))

2.7类、实例对象、类方法、实例方法、类属性、实例属性、静态方法

2.7.1类、实例对象、类属性、实例属性

(1)类:用来创建对象

(2)实例对象:通过类创建出来的对象

(3)类属性:属于类的属性,内存中只有一份

(4)实例属性:属于实例的属性,每个对象中都有一份

2.7.2实例方法、类方法、静态方法

(1)三种方法的存储

       实例方法、类方法、静态方法在内存中都存储在类中,区别就是调用不同。

(2)三种方法的调用

       实例方法:由对象进行调用,必须传递一个self参数,调用时,将调用该方法的对象赋值给self。

       类方法:由类调用,必须传递一个cls参数,调用时,将调用该方法的类传递给

cls

       静态方法:由类调用,无默认参数

2.8两个原则

2.8.1开放封闭原则

对扩展开放

对修改封闭

2.8.2高内聚、低耦合

耦合:模块与模块之间的依赖程度

内聚:模块内部成员的独立性

2.9魔法方法

python中,有一些内置好的方法,这些方法在我们执行某些特定的操作时会被自动调用,也可以手动调用,这些方法我们称之为魔法方法

2.9.1__class__()

获得当前实例的所属的类

2.9.2__getattribute_()

(1)属性访问拦截器,在访问实例属性的时候自动调用。

(2)类中的属性和方法,都可以使用__getattribute__进行获取。

(3)注意:以后不要在__getattribute__方法中调用self.xxxx

例子:

class Itcast(object):
    def __init__(self,subject1):
        self.subject1 = subject1
        self.subject2 = 'cpp'

    #属性访问时拦截器,打log
    def __getattribute__(self,obj):
        if obj == 'subject1':
            print('log subject1')
            return 'redirect python'
        else:   #测试时注释掉这2行,将找不到subject2
            return object.__getattribute__(self,obj)

    def show(self):
        print('this is Itcast')

s = Itcast("python")
print(s.subject1)
print(s.subject2)

2.9.3__bases__()

获取指定类的所有父类构成元素,使用方法为 :类名.__bases__

2.9.4__call__()

具有__call__这个魔法方法的对象可以使用XXX()的形式进行调用。

比普通的函数,可以封装更多的功能

2.10上下文管理器

实现了上下文协议的对象即为上下文管理器

2.10.1上下文协议

__enter____exit__

2.10.2作用

用于资源的获取和释放

2.10.3总结

Python 提供了 with 语法用于简化资源操作的后续清除操作,是 try/finally 的替代方法,实现原理建立在上下文管理器之上。此外,Python 还提供了一个contextmanager 装饰器,更进一步简化上下管理器的实现方式

例子:

from contextlib import contextmanager
 
@contextmanager
def my_open(path, mode):
    f = open(path, mode)
    yield f
    f.close()
调用:
with my_open('out.txt', 'w') as f:
    f.write("hello , the simplest context manager")

2.11元类

Python中的类,本质上也是一个对象。type就是元类,也就是创建类的类

2.12Garbage collection(垃圾回收机制——gc)

2.12.1小整数对象池

Python 对小整数的定义是 [-5, 257) ,这些整数对象是提前建立好的,不会被垃圾回收。在一个 Python 的程序中,所有位于这个范围内的整数使用的都是同一个对象。但是当定义2个相同的字符串时,引用计数为0,触发垃圾回收

2.12.2大整数对象池

每一个大整数,均创建一个新的对象。

2.12.3intern机制——共享机制

python中有这样一个机制——intern机制,让其只占用一个”HelloWorld”所占的内存空间。靠引用计数去维护何时释放。

2.12.4总结

(1)小整数[-5,257)共用对象,常驻内存

(2)单个字符共用对象,常驻内存

(3)单个单词,不可修改,默认开启intern机制,共用对象,引用计数为0,则销毁 

(4)字符串(含有空格等),不可修改,没开启intern机制,不共用对象,引用计数为0,销毁 

(5)大整数不共用内存,引用计数为0,销毁 

(6)数值类型和字符串类型在 Python 中都是不可变的,这就意味着无法修改这个对象的值,每次对变量的修改,实际上是创建一个新的对象 

2.12.5垃圾回收机制—gc

(1)python中gc采用的是引用计数机制为主,标记-清除和隔代回收两种机制为辅的策略

(2)引用计数机制优缺点:

A.优点:

简单

实时性:一旦没有引用,内存就直接释放了。不用像其他机制等到特定时机。实时性还带来一个好处:处理回收内存的时间分摊到了平时。

B.缺点:维护引用计数消耗资源②循环引用

(3)查看一个对象的引用计数

import sys

sys.getrefcount(xxx)

(4)导致引用计数+1的情况

  • 对象被创建,例如a=23
  • 对象被引用,例如b=a
  • 对象被作为参数,传入到一个函数中,例如func(a)
  • 对象作为一个元素,存储在容器中,例如list1=[a,a]

(5)导致引用计数-1的情况

  • 对象的别名被显式销毁,例如del a
  • 对象的别名被赋予新的对象,例如a=24
  • 一个对象离开它的作用域,例如f函数执行完毕时,func函数中的局部变量(全局变量不会)
  • 对象所在的容器被销毁,或从容器中删除对象

(6)隔代回收机制

把对象分为三代,一开始,对象在创建的时候,放在一代中,如果在一次一代的垃圾检查中,该对象存活下来,就会被放到二代中,同理在一次二代的垃圾检查中,该对象存活下来,就会被放到三代中。

(7)有三种情况会触发垃圾回收

调用gc.collect()

当gc模块的计数器达到阀值的时候。

程序退出的时候

(8)gc模块常用功能解析

①gc.get_threshold() 获取的gc模块中自动执行垃圾回收的频率。

②gc.set_threshold(threshold0[, threshold1[, threshold2]) 设置自动执行垃圾回收的频率。

③gc.get_count() 获取当前自动执行垃圾回收的计数器,返回一个长度为3的列表

2.13闭包

(1)在一个函数中定义了一个函数,里面的函数使用到了外面函数的数据,并且外面这个函数的返回值是里面这个函数的引用,我们管这个过程称之为闭包。

(2)注意:如果在内函数中对外函数的数据直接修改会报错,这时需要在内函数中使用关键字nonlocal,目的是告诉解释器该数据在外函数中不在自己内部

(3)LEGB含义解释

L-Local(function):函数内的名字空间

E-Enclosing function locals:外部嵌套函数的名字空间(例如closure)

G-Global(module):函数定义所在模块(文件)的名字空间

B-Builtin(Python):Python内置模块的名字空间

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值