繁体   English   中英

在类中使用getProperty()而不是self.property?

[英]Using getProperty() in a class instead of self.property?

我不知道正确的术语,因此无法在线找到任何相关信息。

请看以下示例代码:

def Fruit(object):
    def __init__(self, color):
        self._color = color
    def color(self):
        return self._color

现在,说我要检查水果是否为红色:

    def isRed(self):
        if self._color == "red":
            return True
        return False

会很好地工作。 但是,也是如此

    def isRed(self):
        if self.color() == "red":
            return True
        return False

拥有getProperty函数是优良作法的原因吗? (我假设这是因为我正在攻读的是MIT教授的课程,并且在他的课程中做到了这一点,并希望学生在功课上也能做到这一点。)

这两个示例中的任何一个self.property吗?为什么仅通过self.property来引用该属性self.property

编辑:添加了下划线以使self._color为约定。

TL; DR: 并非所有常规编程最佳实践都并非Python最佳实践。 Getter和setter方法是常规(OOP)最佳实践,但不是Python最佳实践。 相反, @property尽可能使用普通的Python属性,并根据需要切换到Python @property

它是许多面向对象的编程语言(例如Java和C ++),被认为是一种很好的做法:

  • 将数据成员(也称为“属性”)设为私有
  • 提供getter和/或setter方法来访问它们

为什么?

  • 通过保持接口稳定,同时保持实现灵活性(去耦),通过“封装”启用更改
  • 允许更精细的访问级别

让我们详细看一下这些:

面向对象的“封装”

面向对象的核心思想之一是将小数据块的定义与与该数据相关的功能捆绑在一起,使命令式/“结构化” /过程式程序更易于管理和发展。

这些捆绑包称为“对象”。 每个“类”是具有相同数据结构(尽管可能不同的数据)和相同相关功能的组对象的模板。

数据定义是类(对象的“属性”)的(非静态)数据成员。 相关功能被编码为功能成员(“方法”)。

这也可以视为构建新的用户定义类型的方法。 (每个类都是一个类型,每个对象有点像一个值。)

通常,与已经提供的数据成员的类型相比,这些方法需要更多有关属性值的保证才能正常工作。 假设您有

class Color() {
    float red;
    float green;
    float blue;

    float hue() {
        return // ... some formula
    }

    float brightness {
        return // ... some formula
    }
}

如果redgreenblue处于[0,1]范围内,则这些方法的实现可能取决于该事实。 同样,如果它们在[0,256)范围内。 无论类内部的约定如何,维护该类并仅将值分配给可接受的数据成员都是该类方法的任务。

但是,通常,不同类的对象必须交互才能生成有意义的面向对象程序。 但是您不希望仅仅因为您正在访问其他类而考虑其他类的内部约定,因为这将需要大量查找才能找出那些约定。 因此,您不应从该类之外的代码中为该类的对象分配数据成员。

为避免因错误或疏忽而发生这种情况,在这些语言中广为接受的最佳实践是将所有数据成员声明为私有。 但这意味着它们也无法从外部读取 如果外部不关心该值,则可以通过提供一种非私有的getter方法来解决此问题,该方法除了提供属性值外什么也不做。

在限制涟漪效应的同时启用变更

说外部(例如另一个类)必须能够设置类的某些属性的值。 并且说,除了该属性的类型已经施加的限制之外,没有任何其他限制。 您应该将该属性公开吗? (仍然假设这不是在Python中!) 不! 而是提供一个setter方法 ,该方法除了将值作为参数并将其分配给属性外什么也不做!

似乎有些沉闷,那为什么呢? 这样我们以后可以改变主意!

新的副作用

假设您希望每次颜色对象的红色分量发生更改时登录到控制台/终端(标准输出)。 (出于任何原因。)

在(setter)方法中,添加一行代码,然后执行该操作,而无需调用方进行任何更改。

但是,如果您需要首先从分配给公共属性切换到调用setter方法,那么所有分配给这些属性的代码段(到那时可能很多)也必须更改! (不要忘记将属性设置为私有,这样就不会遗忘任何属性。)

因此最好从一开始就只具有私有属性,并在类外的代码必须能够设置值时添加setter方法。

内部代表变动

假设您刚刚注意到,对于您的应用程序,颜色实际上应该在内部以色相,值和饱和度表示,而不是红色,绿色和蓝色分量。

如果您有setter和getter方法,则由于必要的转换计算,红色,绿色和蓝色的方法将变得更加复杂。 (但是,亮度和色调方法将变得更加简单。)不过,与必须更改使用该类的类之外的所有代码相比,更改它们的工作量要少得多。 由于界面保持不变,因此呼叫者根本不需要更改,也不会注意到任何差异。

但是,如果您需要首先从分配给公共属性切换到调用setter方法,那么……我们去过那里,不是吗?

去耦

因此,访问器方法(我们称为getter和setter的方法)可帮助您将类的公共接口与其内部实现分离,从而将对象与其用户分离。 这使您可以在不破坏公共接口的情况下更改内部实现,因此在执行此操作时不必更改使用类的代码。

精细访问级别

是否需要只能从外部读取但不能从外部写入的属性? 简便: 提供一个getter方法,而不提供setter方法(并且属性本身是私有的)。

不太常见,但比您想象的要常见:

是否需要只能从外部写入但不能从外部读取的属性? 简便: 提供一个setter方法 ,而不提供一个 getter方法(并且属性本身是私有的)。

不确定是否应该从班级外部访问(和访问)您的属性? 将其设为私有暂时不提供任何获取器和设置器。 您以后可以随时添加它们。 然后考虑他们应该具有的可见性级别。)

如您所见,没有理由在可变对象中拥有非私有属性。 (假设运行时开销对您的应用程序无关紧要(实际上可能无关紧要),或者由编译器进行了优化(可能至少部分地)。)

不是安全功能!

请注意,属性和方法的“可见性”级别并不旨在提供应用程序安全性或隐私性(不是)。 它们是帮助程序员犯错误的工具(通过避免犯错误,避免他们访问本来不应该发生的事情),但它们不会阻止对抗性程序员访问这些东西。 或者,就此而言,诚实的程序员认为自己知道自己在做什么(无论他们是否知道)并且愿意冒险。

Python与众不同

在Python中,一切都是公开的

尽管Python也是强制性的,“结构化的”,过程性的且非常面向对象的,但它采用了更为宽松的可见性方法。 Python中没有真正的“私有”可见性级别,也没有“受保护”或“包”(Java中的默认值)级别。

本质上, Python类中的所有内容都是public

当将Python用作快速,肮脏的即席解决方案的脚本语言时,这很有意义,您可能会编写一次代码,然后扔掉(或者保持这种状态,而无需进一步开发)。

如果您使用Python开发了更多涉及的应用程序(当然这在Python中是可行的,并且也做了很多事情),您可能希望区分类的公共接口及其内部实现细节。 Python提供了两个级别的“隐藏”内部成员(函数,数据属性):

  • 按照惯例: _前缀
  • 按名称修改: __前缀

按照惯例“隐藏”

_开头的名称向命名空间之外的每个人(无论是类,模块还是程序包)发出信号:

除非您知道自己在做什么,否则不应该访问它 我(该名称空间中东西的实现者)可能随意更改,因此您可能不知道通过访问它做什么。 东西可能会破裂。 如果确实如此,那将是 (正在访问它的人)的错误,而不是我的(正在实现它的人)的错误。 该成员不是此名称空间的public接口的一部分

是的,您可以访问它。 那并不意味着你应该。 我们都是成年人。 要负责任。

而且,即使您还不是成年人,也应该坚持这一点。

隐藏名字改头换面

__开头的名称向命名空间之外的每个人(无论是类,模块还是程序包)发出信号:

您知道,与_一样,甚至更强

此外,并且仅当名称空间一个类 (并且属性名称以不超过一个下划线结尾)时:

为了确保您不会意外地从外部访问这些内容,Python会“混搭”这些属性的名称,以便从类外部进行访问。 结果名称是完全可预测的(它是_ +(简单)类名称+原始属性名称),因此您仍然可以访问这些内容,但是您肯定不会仅仅因为错误而已。

同样,这可以帮助避免基类成员及其子类成员之间的名称冲突。 (但是,如果类共享相同的类名(如使用“简单类名”,则不包括模块和包),将无法按预期工作。)

无论哪种情况,您都可能有充分的理由无论如何都要访问这些值(例如,用于调试),并且Python不想在这样做时妨碍您(或使用名称修饰,最多只能这样做)。

Python具有基于方法的“属性”,可以像访问数据属性一样对其进行访问

因此,由于Python中没有真正的私有工具,因此我们无法应用Java和C ++中的模式/样式。 但是我们可能仍然需要稳定的接口来进行认真的编程。

好在Python中,您可以使用方法替换数据属性,而无需更改其用户。 Pils19的答案提供了一个示例:

class Fruit(object):
    def __init__(self, color):
        self._color = color

    @property
    def color(self):
        return self._color

(此装饰器的文档在这里 。)

如果我们还提供了一个属性设定者方法和一个属性设定者方法...

class Fruit(object):
    def __init__(self, color):
        self._color = color

    @property
    def color(self):
        return self._color

    @color.setter
    def color(self, c):
        self._color = c

    @color.deleter
    def color(self):
        del self._color

然后,这相当于一个简单的data属性:

class Fruit(object):
    def __init__(self, c):
        self.color = c

但是现在我们拥有方法的所有自由。 我们可以忽略其中的任何一个(最常见的是只具有getter,因此您具有只读属性),我们可以赋予它们其他或不同的行为,等等。

这是Python中推荐的方法:

  • 如有疑问,使用(公共)数据成员
  • _前缀的实现细节
  • 如果/当您需要其他/不同的行为或禁用读取,写入或删除,使用属性或用属性替换公共数据成员时

你的教授

我假设[有一种在Python中定义非属性获取器和设置器的良好做法],因为我正在学习的MIT教授在他的课程中这样做,并希望学生在功课上也能做到这一点。

您确定这是您的教授所做的,还是他使用了Python的属性机制?

如果是的话,这是关于Python的一类,还是恰好发生在示例中使用Python(并且您的教授也使用它来演示实际上仅适用于其他语言的内容)吗?

而且请不要忘记:即使是麻省理工学院的教授,也可能被迫在并非该学科各个方面的专家的情况下授课。

通常,@ @Property装饰器对您来说是一个好习惯。 并具有带有单个下划线的内部属性。 以您为例,它看起来像:

class Fruit(object):
    def __init__(self, color):
        self._color = color

    @property
    def color(self):
        return self._color

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM