属性 vs. Getter 和 Setter

很多面向对象的编程语言中都使用了 getter(也称为“accessors”)和 setter(又名“mutators”)来确保数据封装的原则。数据封装——正如我们在教程的面向对象编程的介绍中学到的那样——被视为数据与对它们进行操作的方法的捆绑。这些方法当然是用于检索数据的getter和用于更改数据的setter。根据这个原则,将类的属性设为私有以隐藏和保护它们。

不幸的是,人们普遍认为一个合适的 Python 类应该通过使用 getter 和 setter 来封装私有属性。一旦这些程序员中的一个引入了新属性,他或她就会将其设为私有变量并“自动”为该属性创建一个 getter 和一个 setter。这些程序员甚至可以使用编辑器或 IDE,它们会自动为所有私有属性创建 getter 和 setter。如果程序员使用公共属性,这些工具甚至会发出警告!Java 程序员在阅读以下内容时会皱起眉头,拧紧鼻子,甚至惊恐地尖叫: Pythonic 引入属性的方式是将它们公开。

我们稍后会解释这一点。首先,我们在下面的例子中演示,我们如何以 Javaesque 方式设计一个类,使用 getter 和 setter 来封装私有属性self.__x

 P : 
    def  __init__ ( self ,  x ): 
        self __x  =  x 
    def  get_x ( self ):
        返回 self __x 
    def  set_x ( self ,  x ): 
        self __x  =  x

我们可以在以下演示会话中看到如何使用此类和方法:

from  mutators  import  P 
p1  =  P ( 42 ) 
p2  =  P ( 4711 ) 
p1 get_x ()

输出:

42
p1 set_x ( 47 ) 
p1 set_x ( p1 . get_x () + p2 . get_x ()) 
p1 . get_x ()

输出:

4758

你如何看待“p1.set_x(p1.get_x()+p2.get_x())”这个表达式?很丑,不是吗?如果我们有一个公共属性 x,那么编写如下表达式会容易得多:

p1.x = p1.x + p2.x

这样的赋值比 Javaesque 表达式更容易编写,最重要的是更容易阅读。

让我们以 Pythonic 的方式重写类 P。没有 getter,没有 setter,self.__x我们使用公共属性代替私有属性:

 P : 
    def  __init__ ( self , x ): 
        self x  =  x

很漂亮,不是吗?就三行代码,如果我们不计算空行!

 p 导入 P 
p1  =  P ( 42 ) 
p2  =  P ( 4711 ) 
p1 X

输出:

42
p1 x  =  47 
p1 x  =  p1 x  +  p2 × 
p1 X

输出:

4758

“但是,但是,但是,但是,但是……”,我们可以听到他们的嚎叫和尖叫,“但是没有数据封装!” 是的,在这种情况下没有数据封装。在这种情况下我们不需要它。在我们的起始示例中,get_x 和 set_x 所做的唯一一件事就是“获取数据”,而无需额外执行任何操作。

但是如果我们想在未来改变实现会发生什么?这是一个严肃的论点。让我们假设我们想像这样改变实现:属性 x 可以有 0 到 1000 之间的值。如果分配的值大于 1000,x 应该设置为 1000。相应地,x 应该设置为 0,如果值小于 0。

很容易改变我们的第一个 P 类来解决这个问题。我们相应地更改 set_x 方法:

 P : 
    def  __init__ ( self ,  x ): 
        self set_x ( x ) 
    def  get_x ( self ):
        返回 self __x 
    def  set_x ( self ,  x ):
        如果 x  <  0 : 
            self __x  =  0 
        elif  x  >  1000 : 
            self . __x  =  1000
        其他:
            自我. __x  =  x

以下 Python 会话显示它以我们希望的方式工作:

 mutators1 导入 P 
p1  =  P ( 1001 ) 
p1 get_x ()

输出:

1000
p2  =  P ( 15 ) 
p2 get_x ()

输出:

15
p3  =  P ( - 1 ) 
p3 get_x ()

输出:

0

但有一个问题:让我们假设我们用 public 属性设计了我们的类,没有方法:

 P2 : 
    def  __init__ ( self ,  x ): 
        self x  =  x

人们已经使用了很多,他们编写了这样的代码:

p1  =  P2 ( 42 ) 
p1 x  =  1001 
p1 X

输出:

1001

如果我们现在以类 P 的方式更改 P2,我们的新类将破坏接口,因为属性 x 将不再可用。这就是为什么在 Java 中建议人们只使用带有 getter 和 setter 的私有属性,这样他们就可以更改实现而不必更改接口。

但是 Python 为这个问题提供了解决方案。该解决方案称为属性

具有属性的类如下所示:

 P : 
    def  __init__ ( self ,  x ): 
        self x  =  x 
    @property 
    def  x ( self ):
        返回 self __X 
    @x setter 
    def  x ( self ,  x ):
        如果 x  <  0 : 
            self __x  =  0 
        elif  x  >  1000 : 
            self . __x  =  1000
        其他:
            自我__x  =  x

用于获取值的方法用“@property”修饰,即我们将此行直接放在标题前面。必须用作 setter 的方法用“@x.setter”修饰。如果函数被称为“f”,我们将不得不用“@f.setter”来装饰它。有两点值得注意:我们只是在__init__方法中放置了代码行“self.x = x”,并且属性方法x用于检查值的限制。第二个有趣的事情是我们写了“两个”具有相同名称和不同数量参数的方法“def x(self)”和“def x(self,x)”。我们在课程的前一章中了解到这是不可能的。由于装饰,它在这里工作:

 p2 导入 P 
p1  =  P ( 1001 ) 
p1 X

输出:

1000
p1 x  =  - 12 
p1 X

输出:

0

或者,我们可以使用不带装饰器的不同语法来定义属性。如您所见,代码肯定没有那么优雅,我们必须确保__init__再次在方法中使用 getter 函数:

 P : 
    def  __init__ ( self ,  x ): 
        self set_x ( x ) 
    def  get_x ( self ):
        返回 self __x 
    def  set_x ( self ,  x ):
        如果 x  <  0 : 
            self __x  =  0 
        elif  x  >  1000 : 
            self . __x  =  1000
        其他:
            自我. __x  =  x 
    x  = 属性(get_x , set_x )

最新版本还有一个问题。我们现在有两种方法可以访问或更改 x 的值:使用“p1.x = 42”或“p1.set_x(42)”。这样我们就违反了 Python 的基本原则之一:“应该有一种——最好只有一种——明显的方法来做到这一点。” (见Python 之禅

我们可以通过将 getter 和 setter 方法变成私有方法来轻松解决这个问题,我们的类 P 的用户无法再访问这些方法:

 P : 
    def  __init__ ( self ,  x ): 
        self __set_x ( x ) 
    def  __get_x ( self ):
        返回 self __x 
    def  __set_x ( self ,  x ):
        如果 x  <  0 : 
            self __x  =  0 
        elif  x  >  1000 : 
            self . __x  =  1000
        其他:
            自我. __x  =  x 
    x  = 属性(__get_x , __set_x )

有心有情的机器人

尽管我们通过使用私有的 getter 和 setter 解决了这个问题,但带有装饰器“@property”的版本是 Pythonic 的方式!

从我们到目前为止所写的内容以及在其他书籍和教程中也可以看到的内容,我们很容易得出这样的印象,即属性(或 mutator 方法)和属性之间存在一对一的联系,即每个属性都有或应该有自己的属性(或 getter-setter-pair),反之亦然。即使在 Python 之外的其他面向对象语言中,实现这样的类通常也不是一个好主意。主要原因是许多属性仅在内部需要,并且为类的用户创建接口不必要地增加了类的可用性。一个类的可能用户不应该被无数 - 主要是不必要的 - 方法或属性“淹没”!

以下示例显示了一个类,该类具有无法从外部访问的内部属性。这些是私有属性self.__potential _physicalself.__potential_psychic. 此外,我们表明可以从多个属性的值中推导出一个属性。我们示例的属性“条件”以描述性字符串的形式返回机器人的条件。条件取决于机器人的精神值和物理条件的总和。

 机器人def  __init__ ( self ,  name ,  build_year ,  lk  =  0.5 ,  lp  =  0.5  ): 
        self 姓名 = 姓名
        自我build_year  =  build_year 
        self __potential_physical  =  lk 
        self __potential_psychic  =  LP 
    @property 
    DEF 条件(自):
        š  = 自我__potential_physical  +  self __potential_psychic 
        if  s  <=  - 1 :
           返回 “我觉得很痛苦!” 
        elif  s  <=  0 :
           返回 “我感觉不好!” 
        elif  s  <=  0.5 :
           返回 “可能更糟!” 
        elif  s  <=  1 :
           返回 “似乎没问题!” 
        否则返回 “太棒了!”  
如果 __name__  ==  "__main__" :
      , 1979 , 0.2 , 0.4  )
    ÿ  = 机器人(“卡利班” , 1993年, - 0.4 , 0.3 )
    打印(X 条件)
    打印(Ý 条件)

输出:

好像没问题!
我心情不好!

公共属性而不是私有属性

让我们总结一下私有和公共属性、getter 和 setter 以及属性的用法: 假设我们正在设计一个新类,并且我们正在考虑一个实例或类属性“OurAtt”,这是我们设计类所需要的。我们必须注意以下问题:

  • 我们班级的可能用户是否需要“OurAtt”的值?
  • 如果没有,我们可以或应该将其设为私有属性。
  • 如果必须访问它,我们将其作为公共属性访问
  • 当且仅当我们必须对数据进行一些检查或转换时,我们才会将其定义为具有相应属性的私有属性。(举个例子,你可以再看看我们的类P,其中的属性必须在0到1000之间的区间内,这是由属性“x”保证的)
  • 或者,您可以使用 getter 和 setter,但使用属性是处理它的 Pythonic 方式!

假设我们将“OurAtt”定义为公共属性。我们的类已经被其他用户成功使用了一段时间。

class  OurClass : 
    def  __init__ ( self ,  a ): 
        self OurAtt  =  a 
x  =  OurClass ( 10 )
打印( x . OurAtt )

输出:

10

现在是让一些传统的 OOPistas 吓得魂飞魄散的一点:想象一下,“OurAtt”已被用作整数。现在,我们的类必须确保“OurAtt”必须是 0 到 1000 之间的值?没有财产,这真是一个可怕的场景!由于属性,这很容易:我们创建了“OurAtt”的属性版本。

class  OurClass : 
    def  __init__ ( self ,  a ): 
        self OurAtt  = 一个
    @property
    高清 OurAtt (个体经营):
        回归 自我__OurAtt 
    @OurAtt setter 
    def  OurAtt ( self ,  val ):
        如果 val  <  0 : 
            self __OurAtt  =  0 
        elif  val  >  1000 : 
            self __OurAtt  =  1000
        其他:
            自我__OurAtt  =  val 
x  =  OurClass ( 10 )
打印( x . OurAtt )

输出:

10

这很棒,不是吗?您可以从可以想象到的最简单的实现开始,以后可以自由迁移到属性版本,而无需更改界面!所以属性不仅仅是 getter 和 setter 的替代品!

您可能已经注意到的另一件事:对于类的用户,属性在语法上与普通属性相同。

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值