Day28 描述符应用与类的装饰器

一、上下文管理协议

with open('filename') as f:

        '代码块'

这样的with语句,叫做上下文管理协议,为了让一个对象兼容with语句,必须在这个对象的类中声明__enter__和__exit__方法

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

    def __enter__(self):
        print('出现with语句,对象的__enter__被触发,有返回值则赋值给as声明的变量')

    def __exit__(self, exc_type, exc_val, exc_tb):
        print('with中代码块执行完毕时执行我啊')
        print(exc_type)	# 异常类
        print(exc_val)	# 异常值
        print(exc_tb)	# 追踪信息
# return True	# 如果返回True,则异常被吞掉,代码正常执行

with Open('a.txt') as f:
    print('=====>执行代码块')
    raise AttributeError('***着火啦,救火啊***')	# 只要出现异常,且没被吞掉异常,代码块以后的部分都不会被运行
print('0'*100) #------------------------------->不会执行

 1.使用with语句的目的就是把代码块放入with中执行,with结束后,自动完成清理工作,无须手动干预

2.在需要管理一些资源比如文件,网络连接和锁的编程环境中,可以在__exit__中定制自动释放资源的机制,使用者无须再去关心这个问题,这将大有用处

二、描述符

本身是一个新式类

python是弱类型语言,即参数的赋值没有类型限制,可以通过描述符来实现类型的限制功能

class Typed:
    def __init__(self,name,expected_type):	# expected_type 可以判断任意类型
        self.name = name
        self.expected_type = expected_type

    def __get__(self, instance, owner):
        print('get--->', instance, owner)
        if instance is None:
            return self
        return instance.__dict__[self.name]

    def __set__(self, instance, value):
        print('set--->', instance, value)
        if not isinstance(value, self.expected_type):
            raise TypeError('Expected %s' %str(self.expected_type))
        instance.__dict__[self.name] = value

    def __delete__(self, instance):
        print('delete--->', instance)
        instance.__dict__.pop(self.name)


class People:
    name=Typed('name', str)	# 这里代码重复,可以使用装饰器改进
    age=Typed('name', int)
    salary=Typed('name', float)

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

p1=People(123, 18, 3333.3)	            # 报错
p1=People('yuanshen', '18', 3333.3)		#报错
p1=People('yuanshen', 18, 3333)

使用类的装饰器完成类型限制

class Typed:
    def __init__(self, name, expected_type):
        self.name = name
        self.expected_type = expected_type

    def __get__(self, instance, owner):
        print('get--->', instance, owner)
        if instance is None:
            return self
        return instance.__dict__[self.name]

    def __set__(self, instance, value):
        print('set--->', instance, value)
        if not isinstance(value, self.expected_type):
            raise TypeError('Expected %s' %str(self.expected_type))
        instance.__dict__[self.name] = value

    def __delete__(self, instance):
        print('delete--->', instance)
        instance.__dict__.pop(self.name)

def typeassert(**kwargs):
    def decorate(cls):
        print('类的装饰器开始运行啦------>', kwargs)
        for name, expected_type in kwargs.items():
            setattr(cls, name, Typed(name, expected_type))
        return cls
    return decorate

@typeassert(name=str,age=int,salary=float) 
# 有参:1.运行typeassert(...)返回结果是decorate,此时参数都传给kwargs 
# 2.People=decorate(People)
class People:
    def __init__(self,name,age,salary):
        self.name=name
        self.age=age
        self.salary=salary

print(People.__dict__)
p1=People('yuanshen', 18, 3333.3)

 实用描述符实现静态属性

自制property

class Lazyproperty:
    def __init__(self, func):
        self.func = func
    def __get__(self, instance, owner):
        print('这是我们自己定制的静态属性,r1.area实际是要执行r1.area()')
        if instance is None:		# 当尝试进行类调用的时候
            return self
        return self.func(instance) #此时你应该明白,到底是谁在为你做自动传递self的事情

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

    @Lazyproperty # area = Lazyproperty(area) 相当于定义了一个类属性,即描述符
    def area(self):
        return self.width * self.length

r1 = Room('yuanshen', 1, 1)
print(r1.area)

自定义property实现延迟计算

class Lazyproperty:
    def __init__(self,func):
        self.func=func
    def __get__(self, instance, owner):
        print('这是我们自己定制的静态属性,r1.area实际是要执行r1.area()')
        if instance is None:
            return self
        else:
            print('--->')
            value=self.func(instance)
            setattr(instance,self.func.__name__,value) #计算一次就缓存到实例的属性字典中
            return value

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

    @Lazyproperty #area=Lazyproperty(area) 相当于'定义了一个类属性,即描述符'
    def area(self):
        return self.width * self.length

r1=Room('yuashen',1,1)
print(r1.area) #先从自己的属性字典找,没有再去类的中找,然后出发了area的__get__方法
print(r1.area) #先从自己的属性字典找,找到了,是上次计算的结果,这样就不用每执行一次都去计算
class Lazyproperty:
    def __init__(self,func):
        self.func = func
    def __get__(self, instance, owner):
        print('这是我们自己定制的静态属性,r1.area实际是要执行r1.area()')
        if instance is None:
            return self
        else:
            value = self.func(instance)
            # 把值写入实例对象的__dict__起作用,仅当描述符为非数据描述符才行
            instance.__dict__[self.func.__name__] = value
            # setattr(instance, self.func.__name__, value)
            return value
        # return self.func(instance) #此时你应该明白,到底是谁在为你做自动传递self的事情
    def __set__(self, instance, value):
        print('hahahahahah')

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

    @Lazyproperty # area = Lazyproperty(area) 相当于定义了一个类属性,即描述符
    def area(self):
        return self.width * self.length

print(Room.__dict__)
r1 = Room('yuanshen', 1, 1)
print(r1.area)
print(r1.area) 
print(r1.area) 
print(r1.area) 
# 缓存功能失效,每次都去找描述符了,为何,因为描述符实现了set方法,它由非数据描述符变成了数据描述符,数据描述符比实例属性有更高的优先级,因而所有的属性操作都去找描述符了

 

一个静态属性property本质就是实现了get、set、delete三种方法

class Foo:
    @property
    def AAA(self):
        print('get的时候运行我啊')

    @AAA.setter
    def AAA(self, value):
        print('set的时候运行我啊')

    @AAA.deleter
    def AAA(self):
        print('delete的时候运行我啊')

#只有在属性AAA定义property后才能定义AAA.setter,AAA.deleter
f1 = Foo()
f1.AAA
f1.AAA = 'aaa'
del f1.AAA

# 上下两种定义方法都可以

class Foo:
    def get_AAA(self):
        print('get的时候运行我啊')

    def set_AAA(self,value):
        print('set的时候运行我啊')

    def delete_AAA(self):
        print('delete的时候运行我啊')
    AAA = property(get_AAA, set_AAA, delete_AAA) # 内置property三个参数与get,set,delete一一对应

f1 = Foo()
f1.AAA
f1.AAA = 'aaa'
del f1.AAA

只定义getter方法,不定义setter方法就是一个只读属性,birth是读写属性,age就是只读属性。

class Student(object):
    @property
    def birth(self):
        return self._birth
    @birth.setter
    def birth(self, value):
        self._birth = value
        
    @property
    def age(self):
        return 2015 - self._birth
    
    # 方法名称和实例变量均为gender:
    @property
    def gender(self):
        return self.gender	#应该使用self._gender,不重名

属性的方法名不要和实例变量重名,通过实例.方法调用的时候,会首先转换为方法调用,但在执行return self.gender时,又视为访问self的属性,于是又转换为方法调用,造成无限递归,最终导致栈溢出报错RecursionError

三、枚举类

枚举(Enum)是一种数据类型,是绑定到唯一值的符号表示

# 不使用枚举类
from dataclasses import dataclass
 
@dataclass
class Car:
    model: str
    price: float
    registraion_state: str
 
    def total_cost(self) -> float:
        match self.registraion_state:
            case "OR":
                return  self.price + (self.price * 0.05)
            case "WA":
                return  self.price + (self.price * 0.10)
            case "CA":
                return  self.price + (self.price * 0.08)
            case _:
                raise TypeError("Invalid registraion_state value")
 
 
car1 = Car(model="RAV4", price=30000, registraion_state="OR")
car2 = Car(model="RAV4", price=30000, registraion_state="WA")
car3 = Car(model="RAV4", price=30000, registraion_state="CA")
print(car1.total_cost())
print(car2.total_cost())
print(car3.total_cost())
# 使用枚举类
from dataclasses import dataclass
 
from enum import Enum
 
class StateTax(Enum):
    OR = 0.05
    WA = 0.10
    CA = 0.08
 
@dataclass
class Car:
    model: str
    price: float
    tax: StateTax
 
    def total_cost(self) -> float:
        return  self.price + (self.price * self.tax.value)       
     
    def get_tax(self):
        return self.tax.value
 
 
car1 = Car(model="RAV4", price=30000, tax=StateTax.OR)
car2 = Car(model="RAV4", price=30000, tax=StateTax.WA)
car3 = Car(model="RAV4", price=30000, tax=StateTax.CA)
print(car1.total_cost())
print(car2.total_cost())
print(car3.total_cost())

上下两个代码运行结果不变,但是代码更优雅,更容易阅读了 

访问枚举的三种方式

from enum import Enum
 
tax = {
    'OR': 0.05,
    'WA': 0.10,
    'CA': 0.08,
    'CN': 0.03
}
 
StateTax = Enum('tax', tax)

print(StateTax['CN'])
print(StateTax.CN)
print(StateTax(0.03)) #通过 value 来访问

四、元类

【高级货,但基本不会使用到】

动态语言和静态语言最大的不同,就是函数和类的定义,不是编译时定义的,而是运行时动态创建的

一切皆对象,那么用class定义而来的类本身也是一个对象,负责产生该对象的类称之为元类,python内置的元类为type

type()函数既可以返回一个对象的类型,又可以创建出新的类型,比如,我们可以通过type()函数创建出Hello类,而无需通过class Hello(object)...的定义

>>> def fn(self, name='world'): # 先定义函数
...     print('Hello, %s.' % name)
...
>>> Hello = type('Hello', (object,), dict(hello=fn)) # 创建Hello class
>>> h = Hello()
>>> h.hello()
Hello, world.
>>> print(type(Hello))
<class 'type'>
>>> print(type(h))
<class '__main__.Hello'>

要创建一个class对象,type()函数依次传入3个参数:

  • class的名称;
  • 继承的父类集合,注意Python支持多重继承,如果只有一个父类,别忘了tuple的单元素写法;
  • class的方法名称与函数绑定,这里我们把函数fn绑定到方法名hello上。

当我们使用class关键字创建类的时候,解释器就是在使用type()在进行创建类

一个类没有声明自己的元类时,默认元类为type,也可以通过继承type来自定义元类,使用metaclass关键字参数为一个类指定元类

class Mymeta(type): #只有继承了type类才能称之为一个元类,否则就是一个普通的自定义类
    pass

class OldboyTeacher(object,metaclass=Mymeta): # OldboyTeacher=Mymeta('OldboyTeacher',(object),{...})
    school='oldboy'

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

    def say(self):
        print('%s says welcome to the oldboy to learn Python' %self.name)

调用Mymeta会先产生一个空对象OldTeacher,然后连同调用Mymeta括号里的参数都传给Mymeta元类下的__init__方法,完成初始化

class Mymeta(type): #只有继承了type类才能称之为一个元类,否则就是一个普通的自定义类
    def __init__(self,class_name,class_bases,class_dic):
        # print(self) #<class '__main__.OldboyTeacher'>
        # print(class_bases) #(<class 'object'>,)
        # print(class_dic) #{'__module__': '__main__', '__qualname__': 'OldboyTeacher', 'school': 'oldboy', '__init__': <function OldboyTeacher.__init__ at 0x102b95ae8>, 'say': <function OldboyTeacher.say at 0x10621c6a8>}
        super(Mymeta, self).__init__(class_name, class_bases, class_dic)  # 重用父类的功能

        if class_name.islower():
            raise TypeError('类名%s请修改为驼峰体' %class_name)

        if '__doc__' not in class_dic or len(class_dic['__doc__'].strip(' \n')) == 0:
            raise TypeError('类中必须有文档注释,并且文档注释不能为空')

class OldboyTeacher(object,metaclass=Mymeta): # OldboyTeacher=Mymeta('OldboyTeacher',(object),{...})
    """
    类OldboyTeacher的文档注释
    """
    school='oldboy'

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

    def say(self):
        print('%s says welcome to the oldboy to learn Python' %self.name)

调用t1=OldboyTeacher('egon',18)会做三件事

1、产生一个空对象obj

2、调用__init__方法初始化对象obj

3、返回初始化好的obj

对应着,OldboyTeacher类中的__call__方法也应该做这三件事

class Mymeta(type): #只有继承了type类才能称之为一个元类,否则就是一个普通的自定义类
    def __call__(self, *args, **kwargs): #self=<class '__main__.OldboyTeacher'>
        #1、调用__new__产生一个空对象obj
        obj=self.__new__(self) # 此处的self是类OldoyTeacher,必须传参,代表创建一个OldboyTeacher的对象obj

        #2、调用__init__初始化空对象obj
        self.__init__(obj,*args,**kwargs)

        #3、返回初始化好的对象obj
        return obj

class OldboyTeacher(object,metaclass=Mymeta):
    school='oldboy'

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

    def say(self):
        print('%s says welcome to the oldboy to learn Python' %self.name)

t1=OldboyTeacher('egon',18)
print(t1.__dict__) #{'name': 'egon', 'age': 18}

 

属性查找顺序

1、先对象层:OldoyTeacher->Foo->Bar->object 2、然后元类层:Mymeta->type

class Mymeta(type): #只有继承了type类才能称之为一个元类,否则就是一个普通的自定义类
    n=444

    def __call__(self, *args, **kwargs): #self=<class '__main__.OldboyTeacher'>
        obj=self.__new__(self)
        self.__init__(obj,*args,**kwargs)
        return obj

class Bar(object):
    n=333

class Foo(Bar):
    n=222

class OldboyTeacher(Foo,metaclass=Mymeta):
    n=111

    school='oldboy'

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

    def say(self):
        print('%s says welcome to the oldboy to learn Python' %self.name)

print(OldboyTeacher.n)

 

元类的作用非常强大,它可以改变类的行为,从而改变类的实例化过程。它可以控制类的创建,可以控制类的属性和方法,还可以控制类的继承。

元类还可以用来实现自定义的类型检查,可以用来实现自定义的类型转换,以及实现自定义的类型转换函数。此外,元类还可以用来实现自定义的类型比较,以及实现自定义的元类。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值