美文网首页
python重要知识点总结一

python重要知识点总结一

作者: lifesmily | 来源:发表于2017-07-26 09:19 被阅读377次

    内容包含:

    • 元类
    • python 对象和类的绑定以及类方法,静态方法
    • python 子类调用父类方法总结
    • python 方法解析顺序MRQ
    • python定制类和魔法方法
    • 关于用法__slots__
    • @property使用
    • 修饰器
    • 闭包

    0、元类

    元类就是类的类,所体现的终极思想就是一切皆对象。

    image.png

    关于深层次,待使用到在总结。

    1、python 对象和类的绑定以及类方法,静态方法

    通常我们要使用一个类中的方法时,都需要实例化该类,再进行调用,那类中 self 和 cls 有什么含义,能不能不初始化一个实例而直接调用类方法,对象方法和类方法,静态方法又有什么关系。是本篇文章思考的问题。

    类的调用有以下两种方法:

    >>>class Test:
    ...    def func(self, message):
    ...        print message
    ...
    >>>object1=Test()
    >>>x=object1.func
    >>>x('abc')
    abc
    >>>t=Test.func
    >>>t(object1,'abc')
    abc
    

    但是对于 t=Test.func 来说,变量名 t 是关联到了类 Test 的func 方法的地址上,t是非绑定的,所以在调用t(object1, ‘abc’) 时,必须显式的将实例名与 self 关联,否则将会报出”TypeError: unbound method func() must be called with Test instance as first argument (got str instance instead)” 的错误。

    参考学习

    明白以下几点:
    1、类默认的方法都是绑定对象的,而self参数也是指向该对象,没有实例化对象时,类中方法调用会出错,也涉及到python自动传递self参数。
    2、若想不实例化而直接通过 类名.方法 来调用,需要指定该方法绑定到类,如下,一要使用@classmethod 装饰器,二方法中第一个参数为cls,而不是self。

    >>> class Foo(object):          
    ...     @classmethod                #定义类方法要点1
    ...     def foo(cls):               #定义类方法要点2
    ...             print 'call foo'
    ... 
    >>> Foo.foo()
    call foo
    >>> Foo().foo()
    call foo
    

    类也是对象,因此和下面的静态方法还是有不一样。类中直接定义的属性如下,在类方法中也是可以直接使用的。

    class pizza(object):
        radius = 42
        @classmethod
        def get_radius(cls):
            return cls.radius
    print pizza.get_radius()
    

    类方法对于创建工厂函数最有用,如下

    class pizza(object):
        def __init__(self,ingre):
            self.ingre = ingre
    
        @classmethod
        def from_other(cls,fridge):
            return cls(fridge.juice()+fridge.cheese())  
        def get_ingre(self):
            return self.ingre
    

    cls代表该类,cls()也是用来创建对象,和pizza(fridge.juice()+fridge.cheese())效果一样。待理解,工厂方法是什么?
    3、若只想当成一个普通函数,定义不包含self和cls,则可以使用静态方法,如下:

    >>> class Foo(object):
    ...     @staticmethod
    ...     def foo():
    ...             print 'call foo'
    ... 
    >>> Foo.foo()
    call foo
    >>> Foo().foo()
    call foo
    
    作者:_Zhao_
    链接:http://www.jianshu.com/p/4b871019ef96
    來源:简书
    

    2、python 子类调用父类方法总结

    参考来源

    talk is weak,从程序开始:

    class Person(object):
        def __init__(self):
            self.name = "Tom"
        def getName(self):
            return self.name
    
    class Student(Person):
        def __init__(self):
            self.age = 12
        def getAge(self):
            return self.age
    
    if __name__ == "__main__":
        stu = Student()
        print stu.getName()
    
    作者:nummy
    链接:http://www.jianshu.com/p/dfa189850651
    
    image.png

    上面只是说明一个常用的子类调用父类场景,即调用父类的初始化函数。
    直接运行以上代码会出错,因为尽管Student类继承了Person类,但是并没有调用父类的init()方法,因为子类中对init函数进行了重写,若没有重写会直接继承父类的init函数自动运行。有以下两种方法:

    参考
    1、super方法

    class Base:
        def __init__(self):
            print('Base.__init__')
    
    class A(Base):
        def __init__(self):
            # super().__init__()
            super(A,self).__init__()
            print('A.__init__')
    
    class B(Base):
        def __init__(self):
            # super().__init__()
            super(B,self).__init__()
            print('B.__init__')
    
    class C(A,B):
        def __init__(self):
            # super().__init__()  # Only one call to super() here  python3
            super(C,self).__init__()
            print('C.__init__')
    

    运行结果

    >>> c = C()
    Base.__init__
    B.__init__
    A.__init__
    C.__init__
    >>>
    

    2、调用未绑定的父类构造方法

    class Person(object):
        def __init__(self):
            self.name = "Tom"
        def getName(self):
            return self.name
    
    class Student(Person):
        def __init__(self):
            Person.__init__(self)
            self.age = 12
        def getAge(self):
            return self.age
    
    if __name__ == "__main__":
        stu = Student()
        print stu.getName()
    
    作者:nummy
    链接:http://www.jianshu.com/p/dfa189850651
    

    非绑定方法不经常用到,上述场景却使用的比较多(即子类覆盖父类的方法)。运行时没有父类person的实例,需要显示地进行传递,但有Student的实例,可以用来进行代替。
    这种方法叫做调用父类的未绑定的构造方法。在调用一个实例的方法时,该方法的self参数会被自动绑定到实例上(称为绑定方法)。但如果直接调用类的方法(比如Person.__init),那么就没有实例会被绑定。这样就可以自由的提供需要的self参数,这种方法称为未绑定unbound方法。
    通过将当前的实例作为self参数提供给未绑定方法,Student类就能使用其父类构造方法的所有实现,从而name变量被设置。

    3、python 方法解析顺序

    参考

    上述博文具有很强的参考意义,转述如下:
    在类的多继承中,方法解析顺序MRQ具有很重要的意义,比如对以下菱形继承,D的实例调用show方法,是调用A的还是C的show。

    image.png

    python解析顺序的规范化也是一个不断发展的过程,主要有以下两个阶段:

    • 2.2之前的经典类。经典类中多继承方法解析采用深度优先从左到右搜索,即D-B-A-C-A,也就是说经典类中只采用A的show方法。
    • 经典类对单层继承没有什么问题,但是对上述来说,我们显然更愿意使用C的show方法,因为他是对A的具体化,但是经典类比并不能实现,于是在2.2中引入新式类(继承自object),它仍然采用从左至右的深度优先遍历,但是如果遍历中出现重复的类,只保留最后一个。并且在定义类时就计算出该类的 MRO 并将其作为类的属性。因此新式类可以直接通过 mro 属性获取类的 MRO。
      举个例子:
    image.png

    按照深度遍历,其顺序为 [D, B, A, object, C, A, object],重复类只保留最后一个,因此变为 [D, B, C, A, object]

    这样看起来好像么有问题,但是会有潜在的问题,比如破坏了单调性原则,因此在2.3中引入了 __ C3 算法__。

    C3 MRQ

    我们把类 C 的线性化(MRO)记为 L[C] = [C1, C2,…,CN]。其中 C1 称为 L[C] 的头,其余元素 [C2,…,CN] 称为尾。如果一个类 C 继承自基类 B1、B2、……、BN,那么我们可以根据以下两步计算出 L[C]:
    1、L[object] = [object]
    2、L[C(B1…BN)] = [C] + merge(L[B1]…L[BN], [B1]…[BN])
    这里的关键在于 merge,其输入是一组列表,按照如下方式输出一个列表:
    检查第一个列表的头元素(如 L[B1] 的头),记作 H。
    若 H 未出现在其它列表的尾部,则将其输出,并将其从所有列表中删除,然后回到步骤1;否则,取出下一个列表的头部记作 H,继续该步骤。
    重复上述步骤,直至列表为空或者不能再找出可以输出的元素。如果是前一种情况,则算法结束;如果是后一种情况,说明无法构建继承关系,Python 会抛出异常。

    举例:


    image.png

    根据C3,计算过程为:

    image.png

    4、python定制类和魔法方法

    参考学习

    形如__xxx__的变量或者函数名要注意,这些在Python中是有特殊用途。常见的就是__inint__()函数了,在对象创建后用来初始化,类似的还有__new()__ 和__del__函数。用的不是很多,不做细节深入。

    __str__ 和 __rerp__
    class yuan(object):
        def __init__(self):
            self.name = 'yuanqijie'
            self.age = 22
            self.ambition = 'yes'
        def __str__(self):
            return 'object name: %s'  % self.name
    
    qijie = yuan()
    print qijie
    
    输出为:
    object name: yuanqijie
    

    若没有重写 __str__ 则输出为 <main.Student object at 0x109afb310>
    但注意到,若直接输出变量而不是用print在提示符下还是上述信息,因为直接显示变量调用的不是str(),而是repr(),两者的区别是str()返回用户看到的字符串,而repr()返回程序开发者看到的字符串,也就是说,repr()是为调试服务的。可以类似上述方法进行重写,作为了解即可。

    5、__slots__

    当定义一个类时,可以动态的给该类绑定一个属性和方法,比如:

    >>> class Student(object):
    ...     pass
    
    >>> s = Student()
    >>> s.name = 'Michael' # 动态给实例绑定一个属性
    >>> print s.name
    Michael
    
    >>> def set_age(self, age): # 定义一个函数作为实例方法
    ...     self.age = age
    ...
    >>> from types import MethodType
    >>> s.set_age = MethodType(set_age, s, Student) # 给实例绑定一个方法
    >>> s.set_age(25) # 调用实例方法
    >>> s.age # 测试结果
    25
    

    注意的是,上面是给一个实例绑定的相应的方法,也就是说当在生成一个实例时,上述增加的属性和方法就不起作用了。可以给class绑定方法:

    >>> def set_score(self, score):
    ...     self.score = score
    ...
    >>> Student.set_score = MethodType(set_score, None, Student)
    

    只需将MethodType第二个参数改为None就行。

    __slots__ 用来限制属性
    >>> class people(object):
    ...     __slots__ = ('age','name') # 用tuple定义允许绑定的属性名称
    ... 
    >>> p = people()
    >>> p.age = 20
    >>> p.na = yuan
    >>> p.na = 'yuan'
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    AttributeError: 'people' object has no attribute 'na'
    >>> p.name = 'yuan'
    >>> 
    

    6、@property使用

    参考
    http://python.jobbole.com/80955/
    由上面一节可以知道,绑定属性时,可以随意修改属性值,比如

    s = Student()
    s.score = 9999
    

    很多时候都需要对属性值进行判断,比如正负,大小范围等,一般来说就需要写一个函数进行逻辑检查,比如:

    class Student(object):
    
        def get_score(self):
            return self._score
    
        def set_score(self, value):
            if not isinstance(value, int):
                raise ValueError('score must be an integer!')
            if value < 0 or value > 100:
                raise ValueError('score must between 0 ~ 100!')
            self._score = value
    

    这样就能保证能对传入的值进行逻辑约束,但是每次设置需要调用相应函数,比如s.set_score( 99 ),又显得不是很简洁,能不能像 s.score = 99一样简洁又能进行逻辑检查呢。就是@property。
    @property装饰器可以将一个method变为属性,可以像属性一样简单调用,如student.get_score ,若没有装饰器,则返回的是函数地址。关于setter用法见下。

    class Student(object):
    
        @property
        def score(self):
            return self._score
    
        @score.setter
        def score(self, value):
            if not isinstance(value, int):
                raise ValueError('score must be an integer!')
            if value < 0 or value > 100:
                raise ValueError('score must between 0 ~ 100!')
            self._score = value
    

    @score.setter装饰器表示可以对该属性赋值,若没有则是一个只读的属性。

    7、修饰器

    参考学习

    示例1:
    class myDecorator(object):
        def __init__(self, fn):
            print "inside myDecorator.__init__()"
            self.fn = fn
    
        def __call__(self):
            self.fn()
            print "inside myDecorator.__call__()"
    
    
    @myDecorator
    def aFunction():
        print "inside aFunction()"
    
    print "Finished decorating aFunction()"
    aFunction()
    

    运行上述输出为:

    inside myDecorator.__init__()
    Finished decorating aFunction()
    inside aFunction()
    inside myDecorator.__call__()
    
    示例2:
    def check_is_admin(f):
        def wrapper(*args, **kwargs):
            if kwargs.get('username') != 'admin':
                raise Exception("error occur")
            return f(*args, **kwargs)
        return wrapper
    
    class store(object):
        @check_is_admin
        def get_food(self,username,food):
            print food
    
    s = store()
    s.get_food(username='admin',food='noodles')
    print s.get_food.__name__
    

    上述程序定义了check_is_admin的装饰器,装饰器的主要作用是调用某个函数之前执行一类通用的操作,比如日志任务,上述是执行了权限检查。
    函数被装饰器修饰时,本质上函数变为
    get_food = check_is_admin(get_food(self,username,food))
    check_is_admin直接返回 wrapper函数地址,因此get_food也是指向wrapper函数,故print s.get_food.__name__结果是 wrapper.
    因此调用s.get_food(username='admin',food='noodles')也就是
    wrapper(username='admin',food='noodles')。该函数最后一定要有return f(*args, **kwargs) ,这确保原来函数被执行并返回结果。
    因为装饰器使原函数指向了另一个函数(如上面的wrapper),而原函数只是该函数的一部分,该方式确实对原函数进行了扩展。但同时引入了另外的问题,原函数的属性和名字没有了,如上面s.get_food.__name__并不是get_food。functools提供了名为wraps的装饰器,会复制这些属性给装饰器函数,用法如下:

    import functools
    def check_is_admin(f):
        @functools.wraps(f)
        def wrapper(*args, **kwargs):
            if kwargs.get('username') != 'admin':
                raise Exception("error occur")
            #return f(*args, **kwargs)
        return wrapper
    

    只需额外添加两行代码。
    值得一提的是,**kwargs指定了字典方式传入数据,因此只支持s.get_food(username='admin',food='noodles')而不支持s.get_food('admin','noodles')。为了代码的通用性,考虑对其进行完善,使用inspect模块,最终为:

    import functools
    import inspect
    def check_is_admin(f):
        @functools.wraps(f)
        def wrapper(*args, **kwargs):
            func_args = inspect.getcallargs(f,*args,**kwargs)
            if func_args.get('username') != 'admin':
                raise Exception("error occur")
            print 'test'
            return f(*args, **kwargs)
        return wrapper
    

    func_args会以字典形式记录对应的key和value。意味着装饰器不用检查参数是否是基于位置的参数还是关键字参数,最终以相同的格式保存在返回字典中。

    补充
    funclist = []
    def decorate(func):
        funclist.append(func)
        return func
    
    @decorate
    def func1(num):
        return num + 1
    
    @decorate
    def func2(num):
        return num * num
    
    @decorate
    def func3(num):
        return num * 2
    
    if __name__ == '__main__':
        print max(func(3) for func in funclist)
    

    上面想要说明的是装饰器函数是会执行的,func1 = decorate(func1),这是一条语句,是会执行的,因此,funclist中已经进行了记录。上面的装饰器是最简单的装饰器。

    闭包

    def decorate(func):
        print 'hello'
        msg = 'nice day'
        def wrapper(*args, **kwargs):
            print msg
            if kwargs.get('username') == 'yuan':
                return func(*args, **kwargs)
            else:
                return
        return wrapper
    
    @decorate
    def login(username):
        print username
    
    if __name__ == '__main__':
        login(username='yuan')
        print login.__name__
        print login.__closure__
    

    输出:

    hello
    nice day
    yuan
    wrapper
    (<cell at 0x7f60023c64b0: function object at 0x7f60023c4ed8>, <cell at 0x7f60023c6558: str object at 0x7f60023c72d0>)
    

    对于函数login来说,执行login = decorate(login),返回一个wrapper函数,因此__name__打印出wrapper,但有一个问题,因为wrapper是一个函数,返回之后比较有趣的是 func参数和msg参数仍能使用。一般来说,函数的局部变量只有在执行期间有效,返回之后就会失效。就是说上面的函数通过闭包使脱离了函数本身的作用范围,但仍能访问到局部变量。
    如何实现的呢?
    函数都有一个__closure__属性,如果这个函数是一个闭包的话,那么它返回的是一个由 cell 对象组成的元组对象。cell 对象的cell_contents 属性就是闭包中的自由变量。

    值得注意的是:
    def decorate():
        total = 0
        def wrapper(value)
            total += value
        return wrapper
    

    上面函数会报错,错误原因是total 变量没有定义,其实,对数字或者任何不可变类型,在嵌套函数中重新赋值都会发生错误,因为会创建局部变量,从而不会保存在闭包中。
    要解决这个问题,可以使用 nonlocal 关键字,在python3中有效。

    总结

    嵌套函数可以访问外层函数的局部变量,闭包则可以使函数脱离了函数本身的作用范围,依然能够访问局部变量。

    相关文章

      网友评论

          本文标题:python重要知识点总结一

          本文链接:https://www.haomeiwen.com/subject/nhrmkxtx.html