描述符
描述符的本质就是一个类,在这个类中,至少实现了 __get__(),__set__(),__delete__()中一个,这个也被称为描述符协议
① __get__():调用一个属性时,触发
② __set__():为一个属性赋值时,触发
③ __delete__():采用del删除属性时触发
#结构
class Foo: #Foo类,它实现了三种方法,这个类就被称作一个描述符
def __get__(self, instance, owner):
pass
def __set__(self, instance, value):
pass
def __delete__(self, instance):
pass
描述符的作用:用来代理另外一个类的属性的(必须把描述符定义成这个类的类属性,不能定义到构造函数中)
#############################################################
class Foo:
def __get__(self,instance,owner):
print('get')
def __set__(self,instance,value):
print('set')
def __delete__(self,instance):
print('delete')
f1 = Foo()
f1.name = 'laowang'
f1.name
del f1.name
#这个类产生的实例进行属性的调用/赋值/删除,并不会触发这三个方法
##############################################################
#描述符Boy
class Boy:
def __get__(self,instance,owner):
print('Boy get')
def __set__(self,instance,value):
print('Boy set')
def __delete__(self,instance):
print('Boy delete')
#描述符Girl
class Girl:
def __get__(self,instance,owner):
print('Girl get')
def __set__(self,instance,value):
print('Girl set')
def __delete__(self,instance):
print('Girl delete')
#定义People类
class People:
gen1 = Boy() #gen1属性被Boy类代理
gen2 = Girl() #gen2属性被Girl类代理
def __init__(self,gen1,gen2):
self.gen1 = gen1
self.gen2 = gen2
#实例化
p1 = People('laowang',18) #赋值触发Boy和Girl的set方法
#描述符Boy的使用
p1.gen1 #Boy get
p1.gen1 = 'laozhao' #Boy set
del p1.gen1 #Boy delete
#描述符Girl的使用
p1.gen2 #Girl get
p1.gen2 = 'laoli' #Girl set
del p1.gen2 #Girl delete
#################################################################
#########练习
#简单演示
class Foo:
def __get__(self,instance,owner):
print('触发get',instance,owner)
def __set__(self,instance,value):
print('触发set',instance,value)
def __delete__(self,instance):
print('触发delete',instance)
class Bar:
x = Foo()
def __init__(self,n):
self.x = n
b1 =Bar(10) #触发set <__main__.Bar object at 0x00000212AB3E77B8> 10
print(b1.__dict__) #{}
print(Bar.__dict__) #'x': <__main__.Foo object at 0x0000028BD3E57320>
b1.x #触发get <__main__.Bar object at 0x0000023B653E4400> <class '__main__.Bar'>
b1.x = 1 #触发set <__main__.Bar object at 0x0000023B653E4400> 1
del b1.x #触发delete <__main__.Bar object at 0x0000023B653E4400>
#
class Foo:
def __get__(self,instance,owner):
print('触发get')
def __set__(self,instance,value):
print('触发set',instance,value)
instance.__dict__['x'] = value #增加属性字典赋值
def __delete__(self,instance):
print('触发delete')
class Bar:
x = Foo()
def __init__(self,n):
self.x = n #b1.x = 10 x被代理,因此执行代理描述符内部对应的set方法
b1 = Bar(10) #触发set <__main__.Bar object at 0x000001F0D35D4400> 10
print(b1.__dict__) #{'x': 10}
b1.x = 1111 #触发set <__main__.Bar object at 0x0000023BE8184438> 1111
print(b1.__dict__) #{'x': 1111}
b1.y = 222222 #没有被代理的属性,所以没有执行描述符内的set方法
print(b1.__dict__) #{'x': 111111, 'y': 222222} -->>没有被代理的属性
描述符的分类:一般分为两种:数据描述符和非数据描述符
#1.数据描述符:至少实现了__get__()和__set__()方法
class Foo:
def __get__(self,instance,owner):
print('get')
def __set__(self,instance,value):
print('set')
#2.非数据描述符:没有实现__set__()
class Foo:
def __get__(self,instance,owner):
print('get')
描述符分优先级:下面由高到低
① 类属性
② 数据描述符
③ 实例属性
④ 非数据描述符
⑤ 找不到的属性则触发__getattr__方法
#######################类属性 数据描述符 实例属性
class Foo:
def __get__(self,instance,owner):
print('触发get')
def __set__(self,instance,value):
print('触发set',instance,value)
instance.__dict__['x'] = value #增加属性字典赋值
def __delete__(self,instance):
print('触发delete')
class Bar:
x = Foo()
def __init__(self,x):
self.x = x
#类属型 > 数据描述符
Bar.x #类属性优先级高于数据描述符,但是找不到就去找描述符伪装的类属性找,所以触发了get
Bar.x = 1 #类属性优先级高,所以先给x属性赋值,不会触发set
print(Bar.__dict__) #'x': 1
print(Bar.x) #因为类属性字典中存在x,所以优先类属型,->#1
#数据描述符 > 实例属性
b1 = Bar(10) #触发set <__main__.Bar object at 0x000001B073B272E8> 10
b1.x #触发get
b1.x = 1 #触发set <__main__.Bar object at 0x0000025665DF4358> 1
del b1.x #触发delete
#######################实例属性 非数据描述符 找不到的属性
class Foo:
def __get__(self,instance,owner):
print('触发get')
#def __delete__(self,instance):
#print('触发delete')
class Bar:
x = Foo()
#实例属性 > 非数据描述符
b1 = Bar()
b1.x = 1 #实例属性优先级高,执行赋值
print(b1.__dict__) #{'x': 1}
class Foo:
def __get__(self,instance,owner):
print('触发get')
#def __delete__(self,instance):
#print('触发delete')
class Bar:
x = Foo()
def __getattr__(self,item):
print('-->>')
#执行getattr
b1 = Bar()
b1.x = 1
b1.y #属性找不到,执行getattr
print(b1.__dict__) #{'x': 1}
描述符的使用
python对参数的赋值没有类型限制,可以通过描述符机制来实现类型限制功能
#参数的赋值无类型限制
def test(x):
print('-->>',x)
test('laowang') #-->> laowang
test(1111) #-->> 1111
#描述符必须定义成数据描述符,非数据描述符的优先级低于实例属性
class Typed:
def __get__(self,instance,owner):
print('get方法')
print('instance参数【%s】' % instance)
print('owner参数【%s】' % owner)
def __set__(self,instance,value):
print('set方法')
print('instance参数【%s】' % instance)
print('value参数【%s】' % value)
def __delete__(self,instance):
print('delete方法')
print('instance参数【%s】' % instance)
class People:
name = Typed()
def __init__(self,name,age,salary):
self.name = name
self.age = age
self.salary = salary
p1 = People('laowang',18,1000) #set方法 #instance参数【<__main__.People object at 0x000001ABD8D94390>】#value参数【laowang】
p1.name #get方法 #instance参数【<__main__.People object at 0x000002721B4C4390>】 #owner参数【<class '__main__.People'>】
p1.name = 'laowang' #set方法 #instance参数【<__main__.People object at 0x000001B9245E4400>】 #value参数【laowang】
print(p1.__dict__) #{'age': 18, 'salary': 1000}:没有name原因在于name属性被描述符给代理了
#基本实现类型限制功能
class Typed:
def __init__(self,key):
self.key = key
def __get__(self,instance,owner):
print('get方法')
print('instance参数【%s】' % instance)
print('owner参数【%s】' % owner)
return instance.__dict__[self.key]
def __set__(self,instance,value):
print('set方法')
print('instance参数【%s】' % instance)
print('value参数【%s】' % value)
#instance.__dict__['name'] = value
if not isinstance(value,str): ###########增加类型限制功能-->>字符串类型限制
#print('你传入的类型不是字符串')
#return
raise TypeError('你传入的类型不是-->>字符串') #######报错
instance.__dict__[self.key] = value
def __delete__(self,instance):
print('delete方法')
print('instance参数【%s】' % instance)
instance.__dict__.pop(self.key)
class People:
name = Typed('name') #t1.__set__() self.__set__()
#age = Typed('age')
def __init__(self,name,age,salary):
self.name = name
self.age = age
self.salary = salary
#描述符内只有print的时
#p1 = People('laowang',18,1000) #set方法 #instance参数【<__main__.People object at 0x000001ABD8D94390>】#value参数【laowang】
#print(p1.name) #get方法 #instance参数 #owner参数 #None
#p1.name = 'laowang' #set方法 #instance参数 #value参数【laowang】
#print(p1.name) #get方法 #instance参数 #owner参数 #None
#print(p1.__dict__) #{'age': 18, 'salary': 1000}
#描述符增加取值 赋值 删除值时
p1 = People('laowang',18,1000)
print(p1.__dict__) #{'name': 'laowang', 'age': 18, 'salary': 1000}
#判断类型,213不是字符串类型
p1 = People(213,18,1000) #你传入的类型不是字符串
print(p1.__dict__) #{'age': 18, 'salary': 1000}
print(p1.__dict__) #{'name': 'laowang', 'age': 18, 'salary': 1000}
print(p1.name) #laowang
print(p1.__dict__) #{'name': 'laowang', 'age': 18, 'salary': 1000}
p1.name = 'laozhao'
print(p1.__dict__) #{'name': 'laozhao', 'age': 18, 'salary': 1000}
print(p1.__dict__) #{'name': 'laozhao', 'age': 18, 'salary': 1000}
del p1.name
print(p1.__dict__) #{'age': 18, 'salary': 1000}
#增加多种自定义类型限制
class Typed:
def __init__(self,key,expected_type):
self.key = key
self.expected_type = expected_type
def __get__(self,instance,owner):
print('get方法')
print('instance参数【%s】' % instance)
print('owner参数【%s】' % owner)
return instance.__dict__[self.key]
def __set__(self,instance,value):
print('set方法')
print('instance参数【%s】' % instance)
print('value参数【%s】' % value)
#instance.__dict__['name'] = value
if not isinstance(value,self.expected_type): ###########增加类型限制功能-->>自定义类型限制
raise TypeError('你传入的类型不是-->>%s' % self.expected_type) #######报错
instance.__dict__[self.key] = value
def __delete__(self,instance):
print('delete方法')
print('instance参数【%s】' % instance)
instance.__dict__.pop(self.key)
class People:
name = Typed('name',str) #t1.__set__() self.__set__()
age = Typed('age',int)
def __init__(self,name,age,salary):
self.name = name
self.age = age
self.salary = salary
p1 = People('laowang',18,1000)
print(p1.__dict__) #{'name': 'laowang', 'age': 18, 'salary': 1000}
#判断类型,213不是字符串类型
p1 = People(213,18,1000) #你传入的类型不是字符串
print(p1.__dict__) #{'age': 18, 'salary': 1000}
p1 = People('laozhao','25') #报错,因为age传入的类型不是int
类的装饰器
##函数 装饰器 (无参数)
def deco(func):
print('==')
return func
@deco #test = deco(test)
def test():
print('test函数运行')
test() #== test函数运行
##函数 装饰器 (有参数)
def Typed(**kwargs):
def deco(func):
print('-->>',kwargs)
return func
print('->',kwargs)
return deco
@Typed(x=1,y=2,z=3) #1.Typed(x=1,y=2,z=3) -->>>deco 2.deco -->> test = deco(test)
def test():
print('test函数运行')
test() #-> {'x': 1, 'y': 2, 'z': 3}
##类 装饰器 无参数
def deco(cls):
print('====')
return cls
@deco # Foo = deco(Foo)
class Foo:
pass
f1 = Foo()
print(f1) #<__main__.Foo object at 0x0000026778CD72E8>
##验证一切皆对象
def deco(cls):
print('====',cls) #==== <class '__main__.Foo'>
cls.x = 1
cls.y = 2
cls.z = 3
return cls
#类
@deco # Foo = deco(Foo)
class Foo:
pass
print(Foo.__dict__) #存在赋值的属性:'x': 1, 'y': 2, 'z': 3
#函数
@deco #test = deco(test)
def test():
print('test函数运行')
print(test.__dict__) #test函数同样有__dict__:{'x': 1, 'y': 2, 'z': 3}
##类 装饰器 无参数
def Typed(**kwargs): #外面嵌套一函数,可以添加额外参数来满足不同的需求
def deco(cls): #单一的属性添加设置满足不了多个类的不同的需求
print('-->>',kwargs)
print('类名-->>',cls)
for key,val in kwargs.items():
#cls.__dict__[key] = val 报错
setattr(cls,key,val)
return cls
print('-->>',kwargs)
return deco
@Typed(x=1,y=2,z=3) #1.Typed(x=1,y=2,z=3) -->>>deco 2.deco -->> Foo = deco(Foo)
class Foo:
pass
#运行结果:-->> {'x': 1, 'y': 2, 'z': 3} -->> {'x': 1, 'y': 2, 'z': 3} 类名-->> <class '__main__.Foo'>
print(Foo.__dict__) #'x': 1, 'y': 2, 'z': 3
@Typed(name='laowang') #@deco -->> Bar = deco(Bar)
class Bar:
pass
print(Bar.__dict__) #'name': 'laowang'
#######类装饰器解决多种类型限制定义繁琐问题
class Typed:
def __init__(self,key,expected_type):
self.key = key
self.expected_type = expected_type
def __get__(self,instance,owner):
print('get方法')
print('instance参数【%s】' % instance)
print('owner参数【%s】' % owner)
return instance.__dict__[self.key]
def __set__(self,instance,value):
print('set方法')
print('instance参数【%s】' % instance)
print('value参数【%s】' % value)
#instance.__dict__['name'] = value
if not isinstance(value,self.expected_type): ###########增加类型限制功能-->>自定义类型限制
raise TypeError('你传入的类型不是-->>%s' % self.expected_type) #######报错
instance.__dict__[self.key] = value
def __delete__(self,instance):
print('delete方法')
print('instance参数【%s】' % instance)
instance.__dict__.pop(self.key)
def deco(**kwargs): #外面嵌套一函数,可以添加额外参数来满足不同的需求
def wrapper(obj): #单一的属性添加设置满足不了多个类的不同的需求
for key,val in kwargs.items():
#obj.__dict__[key] = val 报错
print('====',key,val)
setattr(obj,key,Typed(key,val)) #装饰器里面增加描述符
return obj
return wrapper
@deco(name=str,age=int,salary=float,gender=str) #@wrapper -->>People = wrapper(People)
class People:
#name = Typed('name',str) #t1.__set__() self.__set__()
#age = Typed('age',int)
def __init__(self,name,age,salary,gender):
self.name = name
self.age = age
self.salary = salary
print(People.__dict__) #包含属性和对应的Typed对象
p1 = People('laowang',18,1000.0,'male')
描述符总结
描述符是可以实现大部分python类特性中的底层魔法,包括@classmethod,@staticmethd,@property甚至是__slots__属性
描述符是很多高级库和框架的重要工具之一,描述符通常是使用到装饰器或者元类的大型框架中的一个组件
property
#回顾property的使用
class Room:
def __init__(self,name,width,length):
self.name = name
self.width = width
self.length = length
@property #area = property(area) 类属型
def area(self): #area = property(area)
return self.width * self.length
r1 = Room('laowang',1,1)
print(r1.area)
###############自定制property
class Lazyproperty:
def __init__(self,func):
print('-->>',func) #-->> <function Room.area at 0x0000027AB34656A8>
self.func = func
def __get__(self,instance,owner):
print('get')
print(instance)
print(owner)
if instance is None:
return self
return self.func(instance) #返回area方法的值
class Room:
def __init__(self,name,width,length):
self.name = name
self.width = width
self.length = length
#@property #area = property(area) 类属型
@Lazyproperty #area = Lazyproperty(area) -->>>实际上是实例化Lazyproperty这个类
def area(self):
return self.width * self.length
@property #test = property(test)
def test(self):
return '呵呵哒'
#-->>property
#r1 = Room('house',5,5)
#print(r1.area)
#print(Room.__dict__) #存在'area': <property object at 0x000001C4D60B4728>
#-->>Lazyproperty
r1 = Room('house',5,5)
#print(r1.area) #r1.area->Lazyproperty(area)这个实例对象 <__main__.Lazyproperty object at 0x0000029454C0B0F0>
#然而上面并没有实现property的功能
#print(r1.area.func(r1)) #需要传入r1这个实例对象
#将Lazyproperty类修改为描述符,area被Lazyproperty描述符所代理
#print(r1.__dict__) #{'name': 'house', 'width': 5, 'length': 5} 实例里面找不到area属性,去类里面找
#print(Room.__dict__) #'area': <__main__.Lazyproperty object at 0x000002333B574400> 类里面存在area属性
#print(r1.area) #调用描述符,触发get方法:25
#添加静态属性test
#print(Room.test) #静态属性是给实例对象调用的,传入的参数也是实例本身:<property object at 0x0000023409994818>
#print(r1.area) #instance的值是<__main__.Room object at 0x000002B1831E44E0>
#print(Room.area) #instance的值是None。因为没有实例
#增加判断:if instance is None return self
print(Room.test) #<property object at 0x000001720E374818>
print(Room.area) #<__main__.Lazyproperty object at 0x0000020F7D694400>
#类调用属性,会返回对象,作用不大
###############自定制property实现延迟计算功能
class Lazyproperty:
def __init__(self,func):
#print('-->>',func) #-->> <function Room.area at 0x0000027AB34656A8>
self.func = func
def __get__(self,instance,owner):
#print('get')
#print(instance)
#print(owner)
if instance is None:
return self
res = self.func(instance) #返回area方法的值
setattr(instance,self.func.__name__,res)
return res
def __set__(self,instance,value):
pass
class Room:
def __init__(self,name,width,length):
self.name = name
self.width = width
self.length = length
@Lazyproperty #area = Lazyproperty(area)
def area1(self):
return self.width * self.length
@property #test = property(test)
def area(self):
return self.width * self.length
r1 = Room('hosue',5,6)
#print(r1.area1) #30
#print(r1.__dict__) #{'name': 'hosue', 'width': 5, 'length': 6, 'area1': 30}
#print(r1.area1) #r1实例对象中的属性字典里面有了area1这个属性,索引不需要再重复运行func函数计算
#加上__set__方法后,Lazyproperty变为数据描述符,优先级高于实例属性,因此又会重复运行func
print(r1.area1)
print(r1.area1)
classmethod
#类无参数
class ClassMethod:
def __init__(self,func):
self.func = func
def __get__(self,instance,owner):#instance为None,owner为类本身,实例来调用,instance为实例,owner为类本身
def feedback():
print('++')
return self.func(owner)
return feedback
class People:
name = 'laowang'
@ClassMethod #chi_shi = ClassMethod(chi_shi)
def chi_shi(cls):
print('shi很好吃吗 %s' % cls.name)
People.chi_shi() #shi很好吃吗 laowang
p1 = People()
p1.chi_shi() #shi很好吃吗 laowang
#类有参数
class ClassMethod:
def __init__(self,func):
self.func = func
def __get__(self,instance,owner):#instance为None,owner为类本身,实例来调用,instance为实例,owner为类本身
def feedback(*args,**kwargs):
print('++')
return self.func(owner,*args,**kwargs)
return feedback
class People:
name = 'laowang'
@ClassMethod #chi_shi = ClassMethod(chi_shi)
def chi_shi(cls,msg):
print('shi很好吃吗 %s' % cls.name,msg)
People.chi_shi('呵呵哒') #shi很好吃吗 laowang 呵呵哒
p1 = People()
p1.chi_shi('呵呵哒') #shi很好吃吗 laowang 呵呵哒
staticmethod
class StaticMethod:
def __init__(self,func):
self.func=func
def __get__(self, instance, owner):#instance为None,owner为类本身,实例来调用,instance为实例,owner为类本身
def feedback(*args,**kwargs):
print('++')
return self.func(*args,**kwargs)
return feedback
class People:
@StaticMethod #chi_shi=StaticMethod(chi_shi)
def chi_shi(x,y,z):
print('------>',x,y,z)
People.chi_shi(1,2,3) #------> 1 2 3
p1=People()
p1.chi_shi(4,5,6) #------> 4 5 6
property补充
一个静态属性property本质就是实现了get,set,delete三种方法
#用法一
class Foo:
@property
def AAA(self):
print('get的时候运行我啊')
@AAA.setter #AAA属性赋值操作
def AAA(self,value):
print('set的时候运行我啊')
@AAA.deleter #AAA属性删除操作
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
#实例一
class Goods:
def __init__(self):
# 原价
self.original_price = 100
# 折扣
self.discount = 0.8
@property
def price(self):
# 实际价格 = 原价 * 折扣
new_price = self.original_price * self.discount
return new_price
@price.setter
def price(self, value):
self.original_price = value
@price.deleter
def price(self):
del self.original_price
obj = Goods()
print(obj.__dict__)
obj.price # 获取商品价格
obj.price = 200 # 修改商品原价
print(obj.price)
del obj.price # 删除商品原价
#实例二 -->>实现类型检测功能
#第一步:
class People:
def __init__(self,name):
self.name=name
@property
def name(self):
return self.name
p1=People('alex')
#property自动实现了set和get方法属于数据描述符,比实例属性优先级高,所以你这样写会触发property内置的set,
#抛出异常
#第二步
class People:
def __init__(self,name):
self.name=name #实例化就触发property
@property
def name(self):
# return self.name #无限递归
print('get------>')
return self.hehe
@name.setter
def name(self,value):
print('set------>')
self.hehe = value
@name.deleter
def name(self):
print('delete------>')
del self.hehe
p1=People('laowang') #self.name实际是存放到hehe里
print(p1.name) #laowang
print(p1.__dict__) #{'hehe': 'laowang'}
p1.name='laozhao'
print(p1.__dict__) #{'hehe': 'laozhao'}
del p1.name
print(p1.__dict__) #{}
#第三步:加上类型检查
class People:
def __init__(self,name):
self.name=name #实例化就触发property
@property
def name(self):
# return self.name #无限递归
print('get------>')
return self.hehe
@name.setter
def name(self,value):
print('set------>')
if not isinstance(value,str):
print('--')
return self.hehe
self.hehe = value
@name.deleter
def name(self):
print('delete------>')
del self.hehe
p1=People('laowang') #self.name实际是存放到hehe里
print(p1.__dict__) #{'hehe': 'laowang'}
p1.name = 1 ##TypeError: 必须是字符串类型
其他补充
#################获得当前文件的上上级文件夹名
import os,sys
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
print(BASE_DIR)
sys.path.append(BASE_DIR) #将主文件夹路径添加到环境变量中
元类。。。。