首页
学习
活动
专区
工具
TVP
发布
精选内容/技术社群/优惠产品,尽在小程序
立即前往

一切皆对象——Python面向对象:属性访问的魔法(中)——property

在上一篇文章中,我们介绍了方法。本篇文章我们介绍一个轻量级的属性管理机制——。

勘误

在上一篇文章中,笔者中间有一段描述不够严谨:注意到里面用到了一个方法,这个方法和点运算符的作用一样,只不过它是函数的形式,因而属性名可以传递一个变量。特此说明:与点运算符并非完全一样,原因在本文后半部分会解释。对于给大家带来的误导,敬请谅解!

所谓属性管理,即对一个属性,我们如何访问它,如何修改它,访问前后是否需要限制,能否删除等等各种需求。例如,假设一个类的某个属性存储的是一个学科的成绩,那么它就有一些限制:老师只能给出一个0~100的数字做为成绩,其他任何值都是无效的。传统的做法,我们需要为这个属性单独设置一套接口:和:

classStudent:

def__init__(self):

self._score=

defset_score(self,val):

ifnot(

raiseValueError()

self._score=val

defget_score(self):

returnself._score

s=Student()

s.set_score(60)

print(s.get_score())

# 60

这个方法有一些缺点:

繁琐,试想,想给一个学生加一分得这样写:,显然,这不是Python一贯的简约风格;

接口不统一,调用者需要仔细看清究竟是还是还是;

难以维护,接口不能改变,否则会影响业务代码。

Python给出了一个轻量级的属性管理方案——property(下篇文章中会看到,更准确来说是一个高级数据描述符)。我们来利用改写上面的例子:

classStudent:

def__init__(self):

self._score=

defset_score(self,val):

ifnot(

raiseValueError()

self._score=val

defget_score(self):

returnself._score

score=property(

fget=get_score,

fset=set_score

)

s=Student()

s.score=80

print(s.score)

# 80

s.score+=1

print(s.score)

# 81

s.score=-100

# ValueError

我们看到,当我们利用给设置了一些方法后,我们就可以直接对进行访问与修改,大大提高了代码的可读性和可维护性。其接口统一,让用户在使用时不再需要关心和(甚至还有)分别都叫什么。即使对类中的实现做了修改,也不影响业务侧代码的使用。除了方式之外,Python还给出了一套装饰器实现,进一步简化了代码:这里需要注意的是,所有装饰器定义方法名称必须一致。允许在呈现给用户最终数据前能够做一些二次运算,下面看一个例子,给用户呈现RGB的值:

classColor:

def__init__(self):

self.r=

self.g=

self.b=

@property

defrgb(self):

return'#{:02x}{:02x}{:02x}'\

.format(self.r,self.g,self.b)

@rgb.setter

defrgb(self,rgb_seq):

assertisinstance(rgb_seq,list)

assertall(

)

self.r,self.g,self.b=rgb_seq

c=Color()

c.rgb= [10,100,255]

print(c.rgb)

# #0a64ff

还允许我们建立一定的权限控制。当我们仅仅实现了而没有时,该属性就变成只读属性了:

classTest:

def__init__(self):

self.__var=10

@property

defvar(self):

returnself.__var

t=Test()

print(t.var)

# 10

t.var=100

# AttributeError: can't set attribute

当然,我们在系列文章的早期就已经解释了,Python中的属性访问控制是一种基于约定而非约束的方式,所以其实不存在天然的纯私有属性。双下划线开头的私有属性被解释器换了一个名字:

print(t._Test__var)

# 10

t._Test__var=20000

print(t.var)

# 20000

我们来看一个有趣的现象:

classA:

def__init__(self):

self.__var=10

setattr(self,'__var2',100)

a=A()

print(a.__var2)

# 100

print(a.__var)

# AttributeError: 'A' object has no attribute '__var'

这是因为会直接修改实例的属性,向里面添加所设置的属性与值;而点运算符则会先进行一个属性名变换:

print(a.__dict__)

# {'__var2': 100, '_A__var': 10}

这样,我们可以尝试着禁止类对于双下划线开头属性的改名操作,只要将点运算符改成就可以了。利用改名后的属性赋值则会生成一个全新的属性,请看:

a._A__var2=10

print(a.__dict__)

# {'__var2': 100, '_A__var2': 10, '_A__var': 10}

但是,这样的方式又引起了新的问题:

classA:

def__init__(self):

setattr(self,'__var2',100)

@property

defvar2(self):

returnself.__var2

a=A()

print(a.__var2)

# 100

print(a.var2)

# AttributeError: 'A' object has no attribute '_A__var2'

从这里我们发现,在类的内部,通过点运算符访问时,双下划线会直接被转为_classname__attributename的形式。因而这种情况下需要改用方法来直接获取属性,它绕过了更名过程,直接从中拿属性,这便是本文开端勘误的原因:

# ...

@property

defvar2(self):

returngetattr(self,'__var2')

# ...

print(a.var2)

# 100

更多文章

友情链接: https://vonalex.github.io/

欢迎关注 它不只是Python

  • 发表于:
  • 原文链接https://kuaibao.qq.com/s/20180715G14E6900?refer=cp_1026
  • 腾讯「腾讯云开发者社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。
  • 如有侵权,请联系 cloudcommunity@tencent.com 删除。

扫码

添加站长 进交流群

领取专属 10元无门槛券

私享最新 技术干货

扫码加入开发者社群
领券