zoukankan      html  css  js  c++  java
  • python类的深层次理解

    python类的高级使用

    python类

    子类化内置类型

    python里有一个祖先类object的内置类型,他是所有内置类型的共同祖先,也是所有没有指定父类的自定义类的祖先。当需要实现与某个内置类型具有相似行为的类时,最好使用子类化内置类型。
    例如,我们想使用dict,但我们不希望有相同的值存在字典中,这时我们就自定义一个类

    class MyDictError(ValueError):
    """"有相同值时报错"""
    class Mydict(dict):
    def __setitem__(self, key, value):
    if value in self.values():
    if(key in self and self[key]!=value) or key not in self:
    raise MyDictError('不能有相同值')
    super().__setitem__(key,value)


    my=Mydict()
    my['key']='value'
    my['other_key']='value'
    报错:
    raise MyDictError('不能有相同值')
    __main__.MyDictError: 不能有相同值
    正确的写法
    my=Mydict()
    my['key']='value'
    my['other_key']='value1'
    print(my)
    输出
    {'key': 'value', 'other_key': 'value1'}

    访问超类中的方法

    super是一个内置类,可用于访问属于某个对象的超类的属性,就是使用super可以调用父类的方法

    class Mama:
    def says(self):
    print('do your homework')
    class Sister(Mama):
    def says(self):
    Mama.says(self)
    print('and clean your bedroom')
    s=Sister()
    s.says()

    class Sister(Mama):

    def says(self):

    super(Sister,self).says()

    print('and clean your bedroom')

    class Sister(Mama):

    def says(self):

    super().says()

    print('and clean your bedroom')

    如果super不在方法内部使用,那必须给出参数

    s=Sister()
    super(s.__class__,s).says()

    当super只提供了一个参数,那么super返回的是一个未绑定类型,与classmethod一起使用特别合适,@classmethod类方法只能访问类变量。不能访问实例变量。

    class Pizza:
    def __init__(self,toppings):
    self.toppints=toppings
    def __repr__(self):
    return "Pizza with "+" and ".join(self.toppints)
    @classmethod
    def recomend(cls):
    return cls(['spam','ham','eggs'])
    class VikingPizza(Pizza):
    @classmethod<br/>
    def recomend(cls):<br/>
        recomended=super(VikingPizza,cls).recomend()<br/>
        print(type(recomended))<br/>
        recomended.toppints+=['spam']*5<br/>
        return recomended<br/>
    

    print(Pizza.recomend())

    print(VikingPizza.recomend())

    python的方法解析顺序

    python方法解析顺序是基于C3,C3是一个类的线性化(也叫优先级,即祖先的有序列表),这个列表用于属性查找。C3序列化会挑选最接近的祖先的方法:

    class CommonBase:
    def method(self):
    print('CommonBase')

    class Base1(CommonBase):

    pass

    class Base2(CommonBase):

    def method(self):

    print('Base2')

    class MyClass(Base1,Base2):

    pass

    MyClass.mro

    (<class 'main.MyClass'>, <class 'main.Base1'>, <class 'main.Base2'>, <class 'main.CommonBase'>, <class 'object'>)

    >>> MyClass.mro()

    [<class 'main.MyClass'>, <class 'main.Base1'>, <class 'main.Base2'>, <class 'main.CommonBase'>, <class 'object'>]

    类的mro属性保存了线性化的计算结果

    使用super犯得错误

    混合super与显示类的调用

    class A(object):
    def __init__(self):
    print("A"," ")
    super().__init__()

    class B(object):

    def init(self):

    print("B"," ")

    super().init()

    class C(A,B):

    def init(self):

    print("C"," ")

    A.init(self)

    B.init(self)

    C()

    print(C.mro())

    输出结果

    C

    A

    B

    B

    [<class 'main.C'>, <class 'main.A'>, <class 'main.B'>, <class 'object'>]

    当C调用A.init(self)时,super(A,self).init()调用了B.init方法,产生了错误信息

    不同种类的参数

    这个问题就是在使用super初始化过程中传递参数,如果没有相同的签名,就是传递的参数个数不同,会报错

    __author__ = 'Mr.Bool'
    class CommonBase:
    def __init__(self):
    print('CommonBase')
    super().__init__()

    class Base1(CommonBase):

    def init(self):

    print('Base1')

    super().init()

    class Base2(CommonBase):

    def init(self):

    print('Base2')

    super().init()

    class MyClass(Base1,Base2):

    def init(self,arg):

    print('my base')

    super().init(arg)

    MyClass(10)

    使用super().init(arg)时,父类初始化没有传递参数,触发TypeError错误,解决方案就是使用魔法参数包装构造方法

    class CommonBase:

    def init(self,*args,**kwargs):

    print('CommonBase')

    super().init()

    class Base1(CommonBase):

    def init(self,args,**kwargs):

    print('Base1')

    super().init()

    class Base2(CommonBase):

    def init(self,
    args,**kwargs):

    print('Base2')

    super().init()

    class MyClass(Base1,Base2):

    def init(self,arg):

    print('my base')

    super().init(arg)

    MyClass(10)

    这样做是解决了问题,但代码变得太过脆弱,因为使得构造函数可以接受任何参数,显示使用特定类的__init__()调用可以解决这个问题,但会引发混合super与显示类的调用冲突

    编写类的一些好的实现

    1. 应该避免多重继承
    2. super使用必须一致,不能混合使用super和传统调用
    3. 无论python2还是3都应该显示继承object
    4. 调用父类之前使用mro查看类的层次结构

    描述符

    描述符允许自定义在一个引用对象的属性身上,描述符是实现复杂属性访问的基础,描述符本身是你自定义的一个类,定义了另一个类的属性的访问方式
    定义描述符类基于3个特殊方法,3个方法组成描述符协议
    1. set(self,obj,type=None):在设置属性时调用这一方法
    2. get(self,obj,value):在读取属性时调用这一方法
    3. delete(self,obj):对属性调用del时将调用这一方法
    实现了1,2两个方法的描述符类被称为数据描述符,只实现了2的描述符类被称为非数据描述符
    python中类对象的示例都有一个特殊方法getattribute(),每次调用示例对象的属性或方法都会先调用这个方法,这个方法下的属性查找顺序是:
    1. 属性是否为实例的类对象的数据描述符
    2. 查看该属性是否在对象的
    dict

    3. 查看该属性是否为实例的类对象的非数据描述符
    优先级为1,2,3

    class RevealAccess(object):
    """一个数据描述符,正常设定值并返回值,同时打印记录访问的信息"""
    def __init__(self,initval=None,name="var"):
    self.val=initval
    self.name=name
    def __get__(self, instance, owner):
    print('调用',self.name)
    print(instance)
    print(owner)
    return self.val
    def __set__(self, instance, value):
    print('更新',self.name)
    self.val=value
    class MyClass(object):
    x=RevealAccess(10,'var "x"')
    y=5

    m=MyClass()

    print(m.x)

    m.x=20

    m.y=10

    print(m.dict)

    输出结果

    调用 var "x"

    <main.MyClass object at 0x000001D42CB11F60>

    <class 'main.MyClass'>

    10

    更新 var "x"

    {'y': 10}

    这样看就明朗了,dict没有x值,所以被数据描述符或非数据描述符修饰的属性,不会在dict中显示出来,每次查找属性是都会调用描述符类的get()方法并返回它的值,每次对属性赋值都会调用set()

    描述符的使用场景

    描述符可以将类属性的初始化延迟到被实例访问时,如果这些属性的初始化依赖全局应用上下文或者初始化代价很大,可以使用描述符解决

    class InitOnAccess:
    def __init__(self,klass,*args,**kwargs):
    self.klass=klass
    self.args=args
    self.kwargs=kwargs
    self._initialized=None
    def __get__(self,instance,owner):
    if self._initialized is None:
    print("已初始化的")
    self._initialized=self.klass(*self.args,**self.kwargs)
    else:
    print('内存中')
    return self._initialized
    class MyClass:
    lazy_init=InitOnAccess(list,"argument")
    m=MyClass()
    m.lazy_init
    m.lazy_init
    #输出结果
    已初始化的
    内存中

    property

    property是一个描述符类,它可以将一个属性链接到一组方法上,property接收4个可选参数:fget、fset、fdel和doc,函数参数顺序就是这个,最后一个参数可以用来链接到属性的doc文档

    class Rectangle:
    def __init__(self,x1,y1,x2,y2):
    self.x1,self.x2=x1,x2
    self.y1,self.y2=y1,y2
    def _width_get(self):
    return self.x2-self.x1
    def _width_set(self,value):
    self.x2=self.x1+value
    def _height_get(self):
    return self.y2-self.y1
    def _height_set(self,value):
    self.y2=self.y1+value
    width=property(_width_get,_width_set,doc="矩形宽度")
    height=property(_height_get,_height_set,doc='矩形高度')
    def __repr__(self):
    return "{}({},{},{},{})".format(self.__class__.__name__,self.x1,self.x2,self.y1,self.y2)

    r=Rectangle(10,10,25,34)

    print(r.width,r.height)

    help(Rectangle)

    r.width=100

    print(r)

    输出

    15 24

    Help on class Rectangle in module main:

    class Rectangle(builtins.object)

    | Methods defined here:

    |

    | init(self, x1, y1, x2, y2)

    | Initialize self. See help(type(self)) for accurate signature.

    |

    | repr(self)

    | Return repr(self).

    |

    | ----------------------------------------------------------------------

    | Data descriptors defined here:

    |

    | dict

    | dictionary for instance variables (if defined)

    |

    | weakref

    | list of weak references to the object (if defined)

    |

    | height

    | 矩形高度

    |

    | width

    | 矩形宽度

    Rectangle(10,110,10,34)

    property在使用类的继承时,所创建的属性时利用当前类的方法实时创建,不会使用派生类中覆写的方法

    class MyRectangle(Rectangle):
    def _width_get(self):
    return "{} 米".format(self.x2-self.x1)

    print(Rectangle(10,10,25,34).width)
    print(MyRectangle(10,10,25,34).width)
    #输出结果
    15
    15

    解决这个问题,需要在派生类中覆写整个property

    class MyRectangle(Rectangle):
    def _width_get(self):
    return "{} 米".format(self.x2-self.x1)
    width=property(_width_get,Rectangle.width.fset,doc="矩形宽度")
    print(Rectangle(10,10,25,34).width)
    print(MyRectangle(10,10,25,34).width)
    #输出结果
    15
    15 米

    以上存在一个问题就是写派生类很麻烦容易出错,所以使用property的最佳语法是使用property作装饰器

    class Rectangle:
    def __init__(self,x1,y1,x2,y2):
    self.x1,self.x2=x1,x2
    self.y1,self.y2=y1,y2
    @property
    def width(self):
    "矩形宽度"
    return self.x2-self.x1
    @width.setter
    def width(self,value):
    self.x2=self.x1+value
    @property
    def height(self):
    "矩形高度"
    return self.y2-self.y1
    @height.setter
    def height(self,value):
    self.y2=self.y1+value
    def __repr__(self):
    return "{}({},{},{},{})".format(self.__class__.__name__,self.x1,self.x2,self.y1,self.y2)
    class MyRectangle(Rectangle):
    def width(self):
    return "{} 米".format(self.x2-self.x1)

    print(MyRectangle(0,0,10,10).width())

    元编程

    元编程是一种编写计算机程序的技术,这些程序看作数据,你可以在运行时进行修改,生成和内省
    元编程的两种主要方法:
    1. 专注对基本元素内省的能力与实时创造和修改的能力,最简单的工具是修饰器,允许向现有函数、方法或类中添加附加功能。
    2. 类的特殊方法,允许修改类实例的创建过程,最强大的工具为元类

    第一种装饰器

    这里只说说类装饰器,不大为人熟知,请看如下实例

    def short_repr(cls):
    cls.__repr__=lambda self:super(cls,self).__repr__()[:12]
    return cls
    @short_repr
    class ClassWithLongName:
    pass
    xcv
    print(ClassWithLongName())
    #输出<__main__.Cl

    上面实例展示出了类的好几种特性
    1. 在运行时可以修改实例,也可以修改类对象,ClassWithLongName的方法被修改了 2. 函数也是描述符,根据描述符协议,可以在属性查找时执行实际绑定的,添加到类中,repr添加到ClassWithLongName中
    3. super可以在类定义作用域外使用,传入参数要正确
    4. 类装饰器可以用于类的定义
    修改上上面装饰器

    def short_repr(max_width=12):
    """缩短表示的参数化装饰器"""
    def short(cls):
    """内部包装函数,是实际的装饰器"""
    class ShortName(cls):
    """提供装饰器行为的子类"""
    def __repr__(self):
    return super().__repr__()[:max_width]
    return ShortName
    return short

    不过是用上面的装饰器也会出现name,doc元数据发生变化的情况,这个不能使用wrap装饰器修改

    new方法

    因为new方法在init方法调用之前,所以使用new()方法可以覆写实例的创建过程,覆写new()的实现将会使用合适的参数调用器超类的super().new(),并返回之前修改实例

    class InstanceCountClass:
    instance_created=0
    def __new__(cls, *args, **kwargs):
    print('__new__() 调用',cls,args,kwargs)
    instance=super().__new__(cls)
    instance.number=cls.instance_created
    cls.instance_created+=1
    return instance
    def __init__(self,attr):
    print('__init__()调用',self,attr)
    self.attr=attr
    i1=InstanceCountClass('abc')
    i2=InstanceCountClass('xyz')
    print(i1.number,i1.instance_created)
    print(i2.number,i2.instance_created)
    #输出
    __new__() 调用 <class '__main__.InstanceCountClass'> ('abc',) {}
    __init__()调用 <__main__.InstanceCountClass object at 0x000001D9B29C1F98> abc
    __new__() 调用 <class '__main__.InstanceCountClass'> ('xyz',) {}
    __init__()调用 <__main__.InstanceCountClass object at 0x000001D9B29C8400> xyz
    0 2
    1 2

    不调用init()方法

    class NoInit(int):
    def __new__(cls,v):
    return super().__new__(cls,v) if v!=0 else None
    def __init__(self,value):
    print('调用__init__')
    super().__init__()
    print(type(NoInit(1)))
    print(type(NoInit(-1)))
    print(type(NoInit(0)))
    #输出
    调用__init__
    <class '__main__.NoInit'>
    调用__init__
    <class '__main__.NoInit'>
    <class 'NoneType'>
  • 相关阅读:
    flume 安装过程记录
    hive 动态分区插入
    hive 数据导入
    scala 学习笔记
    hive 日志
    Linux下设置和查看环境变量
    数据结构(4):图解递归调用机制
    使用数据结构栈实现表达式计算器
    数据结构(3):java使用数组模拟堆栈
    使用Vue和Element-ui的cdn模式临摹一个后台框架
  • 原文地址:https://www.cnblogs.com/dcotorbool/p/8508660.html
Copyright © 2011-2022 走看看