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'>
原文地址:https://www.cnblogs.com/dcotorbool/p/8508660.html