zoukankan      html  css  js  c++  java
  • Python巩固

    PYTHON 补充知识点

    面向对象三大特性:封装,继承和多态。

    继承的意义在于两点:

    第一,子类如若继承自父类,则自动获取父类所有功能,并且可以在此基础上再去添加自己独有的功能。

    第二,当子类和父类存在同样方法时,子类的方法覆写了父类的代码,于是子类对象执行的将是子类的方法,即“多态”。

    多态到底有什么用呢?在继承关系里,子类对象的数据类型也可以被当作父类的数据类型,但是反过来不行。那么当我们编写一个函数,参数需要传入某一个类型的对象时,我们根本不需要关心这个对象具体是哪个子类,统统按照基类来处理,而具体调用谁的重载函数,再具体根据对象确切类型来决定:

    class Animal():
        def get_name(self):
            print("animal")
    
    class Dog(Animal):
        def get_name(self):
            print("dog")
    
    class Cat(Animal):
        def get_name(self):
            print("cat")
    
    def run(animal):  # 待执行的函数。
        animal.get_name()
    
    cat = Cat()
    run(cat)
    dog = Dog()
    run(dog)
    
    >>>cat
    >>>dog
    

    当我们再新增子类rabbit,wolf等时,根本不需要改变run函数。这就是著名的开闭原则:即对扩展开放(增加子类),对修改关闭(不需要改变依赖父类的函数参数)

    多态存在的三个必要条件:继承,重写,父类引用指向子类对象。

    对于静态语言来说,需要传入参数类型,那么实参则必须是该类型或者子类才可以。而对于动态语言(比如python)来说,甚至不需要非得如此,只需要这个类型里面有个get_name方法即可。这就是动态语言的鸭子特性:只要走路像鸭子,就认为它是鸭子!”这样的话,多态的实现甚至可以不要求继承。

    装饰器的使用

    装饰器的本质就是一个Python函数,它可以在其他函数不做任何代码变化的前提下增强额外功能。装饰器也返回一个函数对象。当我们想给很多函数额外添加功能,给每一个添加太麻烦时,就可以使用装饰器。

    比如有几个函数想给它们添加一个“输出函数名称的功能”:

    def test():
        print("this is test")
        print(test.__name__)
    
    test()
    

    但是如果还有其他函数也需要这个功能,那么就要每个都添加一个print,这样代码非常冗杂。这时候就可以使用装饰器。

    def log(func):
        def wrapper(*args,**kwargs):
            print(func.__name__)
            return func(*args,**kwargs)
        return wrapper
    
    @log  # 装饰器
    def test():
        print("this is test")
    
    test()
    

    当把@log加到test的定义处时相当于执行了:

    test = log(test)
    

    于是乎再执行test()其实就wrapper(),可以得到原本函数的功能以及新添加的功能,非常方便。

    还可以定义一个带参数的装饰器,这给装饰器带来了更大的灵活性:

    def log(text):
        def decorator(func):
            def wrapper(*args, **kwargs):
                print(text)
                return func(*args, **kwargs)
            return wrapper
        return decorator
    
    @log("ahahahahaha")  # 带参数的装饰器
    def test():
        print("this is test")
    

    这样就相当于执行了:

    test = log("ahahahahaha")(test)
    

    还有一点,虽然装饰器非常方便,但是这样使用会导致test的原信息丢失(比如上面的代码test.__name__会变成"wrapper"而不是"test")。这样的话需要导入一个functool包,然后在wrapper函数前面加上一句@functools.wraps(func)就可以了:

    def log(text):
        def decorator(func):
            @functools.wraps(func)  # 加上一句
            def wrapper(*args, **kwargs):
                print(text)
                return func(*args, **kwargs)
            return wrapper
        return decorator
    
    @log("ahahahahaha")
    def test():
        print("this is test")
    

    最后,如果一个函数使用多个装饰器进行装饰的话,则需要根据逻辑确定装饰顺序。

    Python中的多继承与MRO

    Python是允许多继承的(即一个子类有多个父类)。多重继承的作用主要在于给子类拓展功能:比如dog和cat都是mamal类的子类,它们拥有mamal的功能,如果想给它们添加“runnable”的功能,只需要再继承一个类runnable即可。这个额外功能的类一般都会加一个“Mixin”后缀作为区分,表示这个类是拓展功能用的:

    class Dog(mamal,RunableMixIn,CarnivorousMixIn)
        pass
    

    使用Mixin的好处在于不用设计复杂的继承关系即可拓展类的功能。

    多继承会产生一个问题:如果同时有多个父类都实现了一个方法,那么子类应该调用谁的方法呢?这就涉及到MRO(方法解析顺序)。

    在Python3之后,MRO使用的都是C3算法(不区分经典类和新式类,全部都是新式类)。C3算法首先使用深度优先算法,先左后右,得到搜索路径后,只保留“好的结点”。好的结点指的是:该节点之后没有任何节点继承自这个节点。
    比如说下图:
    c3
    按照深度优先,从左至右的顺序应该是F,A,Y,X,B,Y,X。其中第一个Y和第一个X后面B也继承自Y,X,于是把它们去掉,最终路径是:F,A,B,Y,X

    在MRO中,使用super方法也要注意:假设有类A,B,C,A继承自B和C。如果B中使用了Super重写了一个方法,这个方法会在C中去寻找(尽管C并不是B的父类):

    class B:
        def log(self):
            print("log B")
    
        def go(self):
            print("go B")
            super(B, self).go()
            self.log()
    
    class C:
    
        def go(self):
            print("go C")
    
    
    class A(B, C):
    
        def log(self):
            print("log A")
            super(A,self).log()
    
    
    if __name__ == "__main__":
        a = A()
        # 1.a对象执行go,先找自己有没有go,自己没有按照MRO去找B
        # 2.B中找到了go并执行,super顺位查找到c的go,执行。
        # 3.然后执行self.log,因为self此时是A对象,于是首先找到A中的log函数执行
        # 4.再次执行super函数,此时顺位查找到B,执行B.log
        a.go()  
    
    "结果"
    go B
    go C
    log A
    log B
    

    进程与线程

    以前的单核CPU也可以进行多任务,方法是任务1一会儿,任务2一会儿,任务3一会儿......因为CPU速度很快,所以看起来像是执行了多任务,其实就是交替执行任务。

    真正的并行需要在多核CPU上进行。但实际开发中的任务数量一定比核数量要多,所以每个核依旧是在交替执行任务。

    进程:对于操作系统来说,一个任务就是一个“进程”,打开一个文件,程序等都是打开一个“进程”。

    当一个程序在硬盘上运行起来,会在内存中形成一个独立的内存体,这个内存体中有自己独立的地址空间,有自己的堆。操作系统会以进程为最小的资源分配单位。

    线程:线程是操作系统调度执行的最小单位。有时一个进程会有多个子任务:比如一个word文件它同时进行打字、编排、拼写检查等任务,这些子任务被称为“线程”。每个进程至少要有一个线程,和进程一样,真正的多线程只能依靠多核CPU来实现。

    进程与线程的区别:进程是拥有资源的独立单位,线程不拥有系统资源,但可以访问自己所隶属的进程的资源。在系统开销方面,进程的创建与撤销都需要系统都要为之分配和回收资源,它的开销要比线程大。

    多进程的优点是稳定性高,因为一个进程挂了,其他进程还可以继续工作。(主进程不能挂,但是由于主进程负责分配任务,所以挂的概率很低)。而线程呢,由于多线程共享一个进程的内存,所以一个线程挂了全体一起挂。并且多线程在操作共享资源时容易出错(死锁等问题)

    一个线程只能属于一个进程,而一个进程可以有多个线程,但至少有一个线程;
    资源分配给进程,同一进程的所有线程共享该进程的所有资源;
    处理机分给线程,即真正在处理机上运行的是线程;
    线程在执行过程中,需要协作同步。不同进程的线程间要利用消息通信的办法实现同步

    无论是多进程还是多线程,一旦任务数量多了效率都会急剧下降,因为切换任务不仅仅是直接去执行就好了,这中间还需要保存现场、创建新环境等一系列操作,如果任务数量一多会有很多时间浪费在这些准备工作上面。


    实现多任务主要有三种办法:

    1. 多个进程,每个进程仅有一个线程
    2. 一个进程,进程中有多个线程
    3. 多进程多线程(过于复杂,不推荐)


    同时执行多个任务的时候,任务之间是要互相协调的,有时任务2必须要等任务1结束之后去执行,有时任务3和4不能同时执行......所以多进程和多线程编写架构要相对更复杂。


    任务类型主要分为IO密集型计算密集型”。计算密集型任务主要是需要大量计算,比如计算圆周率,对视频解码等。计算密集型任务主要消耗的是CPU资源(不适合Python这种运算效率低的脚本语言编写代码,适合C语言);而IO密集型任务主要是IO操作,CPU消耗很少,大部分时间都消耗在等待IO。(适合开发效率高的脚本语言)

    对于计算密集型任务来说,如果多进程多线程任务远远大于核数,那么效率反而会急剧下降;而对于IO密集型任务来说,多进程多线程可能会好一些,但还是不够好。

    利用异步IO可以实现单进程单线程执行多任务。利用异步IO可以显著提升操作系统的调度效率。对应到Python语言,异步IO被称为协程。

    协程:比线程更加轻量级的“微线程”,协程不被操作系统内核管理,而是由用户自己执行。这样的好处就是极大的提升了调度效率,不会像线程那样切换浪费资源。

    子程序在所有语言里都是层级调用(栈实现),即A调用B,B又调用C,C完了B完了最后A才能完。一个线程就是调用一个子程序。而协程不同,用户可以任意在执行A时暂停去执行B,过一会儿再来执行A。

    协程的优势在于:由于是程序自己控制子程序切换,所以相比线程可以节省大量不必要的切换,对于IO密集型任务来说协程更优。而且不需要线程的锁机制。

    协程是在单线程上执行的,那么如何利用多核CPU呢?方法就是多进程+协程。

  • 相关阅读:
    UWP Composition API
    UWP VirtualizedVariableSizedGridView 支持可虚拟化可变大小Item的View(二)
    UWP VirtualizedVariableSizedGridView 支持可虚拟化可变大小Item的View(一)
    UWP 图片剪切旋转工具
    整理UWP中网络和设备信息获取的帮助类,需要的拿走。
    UWP 设备分辨率
    UWP webview 键盘bug,回退页面,键盘会弹一下。
    UWP 解决Webview在Pivot里面无法左右滑动的问题
    ngnix https
    nginx www解析失败问题解决
  • 原文地址:https://www.cnblogs.com/maxdofo/p/12920567.html
Copyright © 2011-2022 走看看