python 描述器 详解_python高级之描述器

描述器用到的方法用到3个魔术方法: __get__()、__set__()、__delete__()

方法使用格式:

obj.__get__(self, instance, owner)

obj.__set__(self, instance, value)

obj.__delete__(self, instance)

self: 指当前类的实例本身

instance: 指owner的实例

owner: 指当前实例作为属性的所属类

代码一

以下代码执行过程:

定义B类时,执行A()赋值操作,进行A类的初始化,再打印B类调用类属性x的a1属性

紧接着执行B类的初始化,通过b实例调用类属性的x的a1属性class A:

def __init__(self):

self.a1 = 'a1'

print('A.init')

class B:

x = A()

def __init__(self):

print('B.init')

print('-' * 20)

print(B.x.a1)

print('*' * 20)

b = B()

print(b.x.a1)

描述器定义Python中,一个类中实现了__get__、__set__、__delete__三个方法中的任何一个方法,

那么这个类就是描述器.

如果仅实现了__get__,就是非数据描述符 non-data descriptor

同时实现了除__get__以外的__set__或__delete__方法,就是数据描述符 data descriptor

如果一个类的类属性设置为描述器,那么它被称为此描述器的owner属主

描述器方法何时被触发:

当属主类中对是描述器的类属性进行访问时(即类似b.x),__get__方法被触发

当属主类中对是描述器的实例属性通过'.'号赋值时,__set__方法被触发

代码二class A:

def __init__(self):

self.a1 = 'a1'

print('A.init')

def __get__(self,instance,owner):

print('A.__get__ {} {} {}'.format(self,instance,owner))

class B:

x = A()

def __init__(self):

print('B.init')

print('-' * 20)

print(B.x)

# print(B.x.a1) # AttributeError B.x为None,None没有a1属性

print('*' * 20)

b = B()

# print(b.x.a1) # AttributeError B.x为None,None没有a1属性

调用B类的类属性,被A类__get__方法拦截,并返回值None

代码三class A:

def __init__(self):

self.a1 = 'a1'

print('A.init')

def __get__(self,instance,owner):

print('A.__get__ {} {} {}'.format(self,instance,owner))

return self

class B:

x = A()

def __init__(self):

print('B.init')

print('-' * 20)

print(B.x)

print(B.x.a1)

print('*' * 20)

b = B()

print(b.x)

print(b.x.a1)

解决上例中的返回值为None,将A类的实例返回,可成功调用A实例的a1属性

代码四class A:

def __init__(self):

self.a1 = 'a1'

print('A.init')

def __get__(self,instance,owner):

print('A.__get__ {} {} {}'.format(self,instance,owner))

return self

class B:

x = A()

print(x)

def __init__(self):

print('B.init')

# self.x = 100 # 实例调用x属性时,直接查实例自己的__dict__

self.x = A() # 实例调用x属性时,不进入A类的__get__方法

print(self.x)

print('-' * 20)

print(B.x) # __get__

print(B.x.a1) # __get__

print('*' * 20)

b = B()

print(b.x)

print(b.x.a1)

总结: 不论是实例还是类,只要是访问了是描述器的类属性,

都会被描述器的__get__方法拦截

属性的访问顺序(本质)

代码五class A:

def __init__(self):

self.a1 = 'a1'

print('A.init')

def __get__(self,instance,owner):

print('A.__get__ {} {} {}'.format(self,instance,owner))

return self

def __set__(self,instance,value):

print('A.__set__ {} {} {}'.format(self,instance,value))

class B:

x = A()

print(x)

def __init__(self):

print('B.init')

self.x = 100

# self.x = A() # 同上面100结果类似

print(self.x)

# print('-' * 20)

# print(B.x)

# print(B.x.a1)

# print('*' * 20)

b = B()

# print(b.x)

# print(b.x.a1)

print(b.__dict__)

print(B.__dict__)

屏蔽A类的__set__方法,实例的__dict__为{'x': 100}

不屏蔽A类的__set__方法,实例的__dict__为{}

__set__方法本质将实例的__dict__的属性名清空,从而达到数据描述器优先于查实例字典的假象

Python中的描述器描述器在Python中应用非常广泛Python的方法(包括staticmethod()和classmethod())都实现为非数据描述器

.因此,实例可以通过'.'号进行生成属性.property()函数实现为一个数据描述器.

则实例不能使用'.'号进行赋值属性.

示例class A:

@classmethod

def foo(cls):

pass

@staticmethod

def bar():

pass

@property

def z(self):

return 5

def __init__(self): # 非数据描述器

self.foo = 100

self.bar = 200

# self.z = 300 # z属性不能使用实例覆盖

a = A()

print(a.__dict__)

print(A.__dict__)

练习

实现StaticMethod装饰器,完成staticmethod装饰器的功能class StaticMethod:

def __init__(self,fn):

self._fn = fn

def __get__(self,instance,owner):

print(self,instance,owner)

return self._fn

class A:

@StaticMethod # stmd = StaticMehtod(stmd)

def stmd():

print('stmd')

print(A.__dict__)

A.stmd() # 类调用stmd属性

实现ClassMethod装饰器,完成classmethod装饰器的功能from functools import partial

class ClassMethod:

def __init__(self,fn):

self._fn = fn

def __get__(self,instance,owner):

print(self,instance,owner)

return partial(self._fn,owner)

# 使用partial函数将类给作为默认参数

class A:

@ClassMethod # clsmd = ClassMethod(clsmd)

def clsmd(cls):

print('cls',cls.__name__)

print(A.__dict__)

A.clsmd()

类初始化的参数检查import inspect

class Typed:

def __init__(self,tp):

self._tp = tp

def __get__(self,instance,owner):

pass

def __set__(self,instance,value):

if not isinstance(value,self._tp):

raise ValueError(value)

setattr(instance.__class__,self._name,value)

def pcheck(cls):

def wrapper(*args):

sig = inspect.signature(cls)

params = sig.parameters

for i,(name,param) in enumerate(params.items()):

if param.empty != param.annotation:

# if not isinstance(args[i],param.annotation):

# raise ValueError(args[i])

setattr(cls,name,Typed(param.annotation))

return cls(*args)

return wrapper

@pcheck

class A:

# a = Typed(str)

# b = Typed(int)

def __init__(self,a:str,b:int):

self.a = a

self.b = b

A('1',2)

描述器结合装饰实现import inspect

class Typed:

def __init__(self,name,tp):

self._name = name

self._tp = tp

def __get__(self,instance,owner):

print('get',self,instance,owner)

return instance.__dict__[self._name]

def __set__(self,instance,value):

print('set',self,instance,value)

if not isinstance(value,self._tp):

raise ValueError(value)

instance.__dict__[self._name] = value

class A:

a = Typed('a',str)

b = Typed('b',int)

def __init__(self,a:str,b:int):

self.a = a

self.b = b

a = A('1',2)

print(a.__dict__)

# print(type(a.a),type(a.b))

print(a.a)

描述器实现import inspect

def pcheck(cls):

def wrapper(*args):

sig = inspect.signature(cls)

params = sig.parameters

for i,(_,param) in enumerate(params.items()):

if param.empty != param.annotation:

if not isinstance(args[i],param.annotation):

raise ValueError(args[i])

return cls(*args)

return wrapper

@pcheck # A = pcheck(A)

class A:

def __init__(self,a:str,b:int):

self.a = a

self.b = b

A('1','2')

装饰器版本

class A:

def __init__(self,a:str,b:int):

if not (isinstance(a,str) and isinstance(b,int)):

raise ValueError(a,b)

else:

self.a = a

self.b = b

A('1',2)

直接参数检查

思路:

实现参数检查的本质是判断传入的参数是否符合形参定义的类型,也就是用isinstance进行判断.

因此参数检查的不同实现的区别在于在哪些地方拦截传入的参数,来进行检查.

上述实现的拦截地方:

在类初始化时,在对实例属性赋值之前拦截

使用装饰器,和inspect模块,在实例化之前进行参数检查

使用描述器,在初始化时对实例属性设置时,触发描述器的__set__方法,在__set__方法中进行参数检查,再对其实例的类添加类属性

(如果添加在实例上,则会递归调用回到__set__方法)

使用装饰器获取参数注解,给类添加有描述器的类属性,再通过描述器的方式进行参数检查

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Pandas 是一个强大的数据分析工具,提供了很多常用的函数来处理数据,下面是一些常用的函数及其用法: 1. read_csv():读取 CSV 文件并返回一个 DataFrame 对象。 2. head():返回 DataFrame 的前几行数据,默认为前 5 行。 3. tail():返回 DataFrame 的后几行数据,默认为后 5 行。 4. info():返回 DataFrame 的基本信息,包括每列的数据类型、非空值数量等。 5. describe():返回 DataFrame 的基本统计信息,包括计数、均值、标准差、最小值、最大值等。 6. shape:返回 DataFrame 的行数和列数。 7. columns:返回 DataFrame 的列名。 8. index:返回 DataFrame 的行索引。 9. loc[]:根据行标签和列标签访问 DataFrame 中的元素。 10. iloc[]:根据行索引和列索引访问 DataFrame 中的元素。 11. dropna():删除 DataFrame 中的缺失值。 12. fillna():用指定的值或方法填充 DataFrame 中的缺失值。 13. groupby():按照指定的列对 DataFrame 进行分组。 14. apply():对 DataFrame 的每一列应用指定的函数。 15. pivot_table():根据指定的列计算 DataFrame 的透视表。 16. merge():将两个 DataFrame 按照指定的列进行合并。 17. sort_values():按照指定的列对 DataFrame 进行排序。 18. drop_duplicates():去除 DataFrame 中的重复行。 19. value_counts():统计 DataFrame 中每个元素出现的次数。 20. isnull():判断 DataFrame 中的元素是否为空值。 这些函数覆盖了 Pandas 中的很多常用操作,掌握它们对于数据分析和处理非常有帮助。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值