首页 > 技术文章 > Python基础 ( 十 ) —— 面向对象补充2

Matrixssy 2019-10-18 15:38 原文

补充:

1、__name__可以获取类名,在python中函数也是一个类,故可以用函数.__name__的方式获取到函数的名字

一、类的装饰器

还记得以前的函数的装饰器吗?是不是基本结构是这样的:

def deco(fun):
    print('*'*50)
return fun @deco
#相当于做了:Foo=deco(Foo) def Foo(): pass

那么类的装饰器又是怎么样的呢?其实和函数的装饰器原理一样。

因为函数也是一个对象不信你试试:

def test():
    pass
test.x = 1
print(test.__dict__)
def deco(obj):
    print('*'*50)
    obj.x = 1
    obj.y = 2
    return obj
@deco               #相当于做了:Foo=deco(Foo)
class Foo:
    pass
print(Foo.x)        #这样就简单的对Foo类进行了装饰

 #可上面这不就把添加的值写死了吗?下面这样才是正确的装饰器,用途是给你的类默认添加指定的数据属性:

def Typed(**kwargs):
    def deco(obj):
        for key, val in kwargs.items():         #给obj设置数据属性
            setattr(obj, key, val)
        return obj
    return deco
@Typed(x=1,y=2,z=3)                             #先执行Typed(x=1,y=2,z=3) 得到返回值deco 相当于 --》 @deco
class Foo:
    pass
print(Foo.__dict__)

#应用场景:上一章的最后谈到的描述符应用,在类中一个个定义类属性太繁琐,用装饰器就能很好的减少代码重复的问题

class Score:
    """
    这是关于限制分数格式的描述符
    """
    def __init__(self, name, type):
        self._name = name
        self._type = type

    def __set__(self, instance, value):
        if not isinstance(value, self._type):
            raise TypeError('%s must be %s' %(self._name, self._type))
        if isinstance(value, int):
            if not 0 <= value <= 100:
                raise ValueError('Valid value must be in [0, 100]')
        instance.__dict__[self._name] = value

    def __get__(self, instance, owner):
        return instance.__dict__[self._name]

    def __delete__(self, instance):
        instance.__dict__.pop(self._name)

def Typed(**kwargs):
    def deco(obj):
        for key, val in kwargs.items():
            setattr(obj, key, Score(key, val))
        return obj
    return deco

@Typed(name=str, math=int, chinese=int, english=int)
class Student:
    
    def __init__(self, name, math, chinese, english):
        self.name = name
        self.math = math
        self.chinese = chinese
        self.english = english

    def __repr__(self):
        return "<Student: {}, math: {}, chinese: {}, english: {}>".format(
            self.name, self.math, self.chinese, self.english
        )

stu1 = Student('alex', 76, 90, 95)
print(stu1)
#如果输入不满足分数描述符里的限制
# stu2 = Student(666, 100, 100, 100)
# stu3 = Student('alex', 110, 90, 70)
# stu4 = Student('alex', '90', 90, 70)
View Code

 二、自定制property

描述符是可以实现大部分python类特性中的底层魔法,包括@classmethod@staticmethod@property,甚至__slots__

先补充一下之前的装饰器知识:

1、装饰器也可以是个类,详见下面例子

2、@XXX这种写法只是对@语句下面的第一个函数或类使用XXX装饰器进行处理

class A:
    def __init__(self,x):   #注意如果装饰器为类,必须能接收参数
        self.x = x
@A                          # --> B=A(B)
class B:
    pass
@A                          # --> C=A(C)
class C:
    pass

下面我们就自己做一个property

class Property:
    def __init__(self, func):
        self.func = func

def __get__(self, instance, owner): return self.func(instance) #注意这里需要回传对象instance class Residence : def __init__(self,name,length,width): self.name=name self.length=length self.width=width @Property # --> cal_area = Property(cal_area) def cal_area(self): return self.width*self.length p1 = Residence('my home',100,100) print(p1.cal_area)

到这里还有点小瑕疵,如果你用类去调用这个property呢?

系统的property会给你返回property对象,但是上面的代码则没实现这个并且会报错

下面完善它:

class Property:
    def __init__(self, func):
        self.func = func
    def __set__(self, instance, value):
        instance.__dict__[self.func] = value
    def __get__(self, instance, owner):
        if instance is None:           #如果这里不判断的话,下面self.func(instance)会报错
                                       #因为此时instance = None                      
            return self
        return self.func(instance)            

class Residence :
    def __init__(self,name,length,width):
        self.name=name
        self.length=length
        self.width=width

    @Property                                 # --> cal_area = Property(cal_area)
    def cal_area(self):
        return self.width*self.length
print(Residence.cal_area)

接着上面的例子,如果现在有一个场景:我需要反复调用cal_area方法,每次调用都重新运行一次cal_area方法

那我能不能利用装饰器,把cal_area的结果记录下来,然后下次再调用cal_area就直接把记录下的结果传过去呢?

这样是不是大大降低了程序的运算量

在此之上,我们在添加一个延迟计算的功能:

class Property:                              #不能设置__set__方法,需要的是非数据描述符才优先调用实例的字典
    def __init__(self, func):
        self.func = func

    def __get__(self, instance, owner):
        if instance is None:
            return self
        res = self.func(instance)
        setattr(instance, self.func.__name__, res)    #把运行结果添加到实例的字典中
        return  res

class Residence :
    def __init__(self,name,length,width):
        self.name=name
        self.length=length
        self.width=width

    @Property
    def cal_area(self):
        print(' cal_area运行')
        return self.width*self.length
r = Residence('room', 100, 100)

print(r.cal_area)                             #实际只运行了一次,其他的都是调用第一次运行的结果
print(r.__dict__)                             #结果已经存入字典中
print(r.cal_area)
print(r.cal_area)

 #下面是property的一个小补充

如果使用@property,虽然是隐藏了你运行的痕迹 r.cal_area() 变成 r.cal_area;但是你怎么往里面传非self里的参数呢?

以下提供两种方式解决这个问题:

class Residence :
    def __init__(self,name,length,width):
        self.name=name
        self.length=length
        self.width=width

    @property
    def cal_area(self):
        return self.width*self.length

    @cal_area.setter
    def cal_area(self, *args):
        print(*args)
        return self.width*self.length

    @cal_area.deleter
    def cal_area(self):
        print('del执行')

r = Residence('room', 100, 100)
r.cal_area = [1,2,3]                        #触发@cal_area.setter下的cal_area
del r.cal_area                              #触发@cal_area.deleter下的cal_area

OR

class Residence:
    def __init__(self, name, length, width):
        self.name = name
        self.length = length
        self.width = width

    def get_cal_area(self):
        return self.width * self.length

    def set_cal_area(self, *args):
        print('set触发,传入值为%s' %args)
        return self.width * self.length

    def del_cal_area(self):
        print('del执行')

    cal_area = property(get_cal_area, set_cal_area, del_cal_area)  # 注意顺序不能乱


r = Residence('room', 100, 100)
r.cal_area = [1, 2, 3]                          # 触发set_cal_area
del r.cal_area

以上应用的两个小例子:

class Sell:
    def __init__(self, origin_price, discount):
        self.origin_price = origin_price
        self.discount = discount

    @property
    def price(self):
        return self.origin_price*self.discount

    @price.setter
    def price(self, val):               #这里的val为传进来的值 200
        self.origin_price = val

    @price.deleter
    def price(self):
        del self.origin_price
s = Sell(100, 0.8)                      #触发@property下的 price
print(s.price)
s.price = 200                           #触发@price.setter下的 price
print(s.price)
del s.price                             #触发@price.deleter下的 price
print(s.__dict__)
卖衣服
class Name:
    def __init__(self, name):
        self.name = name

    @property                             #相当于name=property(name)
    def name(self):                       #相当于是描述符中的 __get__
        return self._name

    @name.setter                          #相当于是描述符中的 __set__
    def name(self, val):                  #获取到值
        if not isinstance(val, str):
            raise AttributeError
        self._name = val

    @name.deleter                         #相当于是描述符中的 __deleter__
    def name(self):
        del self._name

p1 = Name('alex')                         #触发property --> 触发@name.setter下的name
print(p1.name)                            #触发property --> 触发@property下的name
#p1.name = 1                              #触发property --> 触发@name.setter下的name
del p1.name                               #触发property --> 触发@name.delete下的name
利用property来完成之前章节描述符的类型检测

 #自制classmethod、staticmethod

还是按照自制property的老思路来实现

class Classmethod:
    def __init__(self, func):
        self.func = func
    def __get__(self, instance, owner):             #利用闭包来完成对cls类的默认添加
        def wrapper(name, time):
            print('这里可以给sleep添加别的功能')
            return self.func(owner, name, time)
        return wrapper
class Residence :
    value = 'human'
    def __init__(self,name,length,width):
        self.name=name
        self.length=length
        self.width=width
    @Classmethod
    def sleep(cls, name, time):
        return '%s %s sleep %s hours' % (cls.value, name, time)
print(Residence.sleep('ssy', 10))
p1 = Residence('alex', 10, 10)          
print(p1.sleep('ssy', 10))                    #实例对象也能调用,但是没啥意义
自制classmethod
class Staticmethod:
    def __init__(self, func):
        self.func = func
    def __get__(self, instance, owner):
        return self.func
class Residence :
    value = 'human'
    def __init__(self,name,length,width):
        self.name=name
        self.length=length
        self.width=width
    @Staticmethod
    def text(x,y):                   #也可以不传参数
        return x,y
print(Residence.__dict__)
p1 = Residence('alex',10,10)
print(p1.text(1,2))
自制staticmethod

# 元类:metaclass

都说python中一切皆对象,那我们定义的类又是由哪个类生成的呢?答案是:元类

元类是类的类,是类的模板;就如普通类是对象的模板一样

type是Python里的一个内建元类,用来直接控制其他类的生成:python中所有用class定义的类都是type实例出来的对象

利用type定义类:

def __init__(self, name):
    self.name = name

def text(self):
    print('函数属性添加成功')

Foo = type('Foo', (object,),{'x':1, '__init__':__init__, 'text':text} )
#第一个位置代表定义的类名
#第二个位置代表继承的类,python3中新式类默认继承了object,元组后面的,结尾代表可以接着继承别的类
#第三个位置代表给这个类的字典里添加属性
print(Foo, Foo.__dict__, Foo.x, sep='\n')
f = Foo('alex')
print(f.name)
f.text()

自定义元类

1、 一个类如果没有声明自己的元类,默认就为type;但是我们也可以通过继承type来自定义元类

2、所有对象都继承了object类,可以调用他的很多方法;如 __new__()方法是用来生成对象的,下面会用到。注意传入的对象必须是type类

class MyType(type):

    def __init__(self, class_name, inherit_class, attribute):
        #class_name, inherit_class分别是'Foo', (),
        #attribute 为Foo自己的字典,不包括继承来的

        self.class_name = class_name
        self.inherit_class = inherit_class
        self.attribute = attribute

    def __call__(self, *args, **kwargs):    #自己定义__call__, 不用tpye的
        # self, args, kwargs                
#这几个参数分别为 Foo、实例化传入的值('alex')、{}
obj = object.__new__(self)
#--> object.__new__(Foo) 默认用来产生对象的,这里相当于产生了一个Foo的对象赋给obj self.__init__(obj, *args, **kwargs)
#--> Foo.__init__(obj, *args, **kwargs),相当于把属性传入对象字典中 return obj
#--> 下面的f = obj,且'alex'也传入f的字典中
class Foo(metaclass=MyType): #实际做了 MyType('Foo', (), {}) --> MyType.__init__('Foo', (), {}) #同时,所有对象都继承了object类 def __init__(self, name): self.name = name f = Foo('alex') #Foo是MyType生成的对象,会触发MyType下的__call__;没定义__call__的话就默认继承object的; # 这里我指定了继承type的 print(f, f.__dict__, sep='\n')

 

推荐阅读