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可以调用父类的方法

'''
遇到问题没人解答?小编创建了一个Python学习交流QQ群:857662006 
寻找有志同道合的小伙伴,互帮互助,群里还有不错的视频学习教程和PDF电子书!
'''
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

    def recomend(cls):

        recomended=super(VikingPizza,cls).recomend()

        print(type(recomended))

        recomended.toppints+=['spam']*5

        return recomended


print(Pizza.recomend())

print(VikingPizza.recomend())  

python的方法解析顺序

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

'''
遇到问题没人解答?小编创建了一个Python学习交流QQ群:857662006 
寻找有志同道合的小伙伴,互帮互助,群里还有不错的视频学习教程和PDF电子书!
'''
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初始化过程中传递参数,如果没有相同的签名,就是传递的参数个数不同,会报错

'''
遇到问题没人解答?小编创建了一个Python学习交流QQ群:857662006 
寻找有志同道合的小伙伴,互帮互助,群里还有不错的视频学习教程和PDF电子书!
'''
__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
'''
遇到问题没人解答?小编创建了一个Python学习交流QQ群:857662006 
寻找有志同道合的小伙伴,互帮互助,群里还有不错的视频学习教程和PDF电子书!
'''
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文档

'''
遇到问题没人解答?小编创建了一个Python学习交流QQ群:857662006 
寻找有志同道合的小伙伴,互帮互助,群里还有不错的视频学习教程和PDF电子书!
'''
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

'''
遇到问题没人解答?小编创建了一个Python学习交流QQ群:857662006 
寻找有志同道合的小伙伴,互帮互助,群里还有不错的视频学习教程和PDF电子书!
'''
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(),并返回之前修改实例

'''
遇到问题没人解答?小编创建了一个Python学习交流QQ群:857662006 
寻找有志同道合的小伙伴,互帮互助,群里还有不错的视频学习教程和PDF电子书!
'''
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'>
posted @ 2022-10-07 20:42  I'm_江河湖海  阅读(5)  评论(0)    收藏  举报