【问题标题】:Understanding data encapsulation in Python了解 Python 中的数据封装
【发布时间】:2016-12-11 13:45:09
【问题描述】:

我正在阅读我们如何确保 python 中的数据封装。其中一篇博客说 “数据封装意味着,我们应该只能通过 getter 和 setter 访问私有属性”

考虑blog中的以下sn-ps:

class Robot:
    def __init__(self, name=None, build_year=None):
        self.name = name
        self.build_year = build_year

现在,如果我创建类的对象如下:

obj1=Robot()
obj1.name('Robo1")
obj1.build_year("1978")

目前,我可以直接访问属性,因为我已经将它们定义为 public(没有 __notation)

现在为了确保数据封装,我需要将属性定义为私有 使用__ 表示法并通过getter 和setter 访问私有属性。

所以新的类定义如下:

class Robot:

    def __init__(self, name=None, build_year=2000):
        self.__name = name
        self.__build_year = build_year            
    def set_name(self, name):
        self.__name = name

    def get_name(self):
        return self.__name    

    def set_build_year(self, by):
        self.__build_year = by

    def get_build_year(self):
        return self.__build_year    

现在我将类实例化如下:

x = Robot("Marvin", 1979)
x.set_build_year(1993)

这样,我实现了数据封装,因为私有数据成员不再被直接访问,只能通过类方法访问。

Q1:我们为什么要这样做?我们在保护谁的代码?谁是外部世界?任何拥有源代码的人都可以根据自己的要求对其进行调整,那么我们为什么要添加额外的方法(get/set)来修改/调整属性?

Q2:上面的例子算data encapsulation吗?

【问题讨论】:

  • 在 Python 中,您始终可以绕过 settersgetters 获取数据。在大多数其他实词计算机语言实现中也是如此,只是不像在 Python 中那么容易,从而给人一种虚假的安全感。
  • 请注意,在链接的文章中,在“公共-保护-和私有属性”部分的末尾,它说:“至少有两个很好的理由反对这种方法。首先并非每个私有属性都需要从外部访问。其次,我们将通过这种方式创建非 Python 代码,您将很快了解。
  • 通常的 Python 理念是“我们在这里都是成年人”,所以我们通常不会绕过隐藏属性然后添加额外的代码来通过简单的 getter 和 setter 方法访问它们。 OTOH,Python 的描述符机制允许您在确实需要它们时拥有 getter 和 setter(如 chepner 的回答所示),并且这些 getter 和 setter 对您的代码调用者来说就像简单的属性访问一样,因此您可以两全其美。

标签: python class object


【解决方案1】:

数据封装比访问保护更通用。无论您如何定义属性,namebuild_year 都由类 Robot 封装。 Python 认为 getter 和 setter 只是访问或分配给底层属性是不必要的。

即使使用双下划线前缀也只是建议性的,更关心的是防止子类中的名称冲突。如果你真的想直接访问__build_year 属性,你仍然可以使用

# Prefix attribute name with _Robot
x._Robot__build_year = 1993

Python 中更好的设计是使用 property,这会导致 Python 在直接定义属性时调用已定义的 getter 和/或 setter。例如:

class Robot(object):
    def __init__(self, name, by):
        self.name = name
        self.build_year = by

    @property
    def name(self):
        return self._name

    @name.setter
    def name(self, newname):
        self._name = newname

    @property
    def build_year(self):
        return self._build_year

    @build_year.setter
    def build_year(self, newby):
        self._build_year = newby

您实际上不会如此简单地定义这些属性函数,但一个很大的好处是您可以从允许直接访问 name 属性开始,如果您稍后决定应该在获取/设置值并且您想要切换到属性,您可以在不影响现有代码的情况下这样做。类似的代码

x = Robot("bob", 1993)
x.build_year = 1993

无论x.build_year = 1993 是否直接分配给build_year 或者它是否真的触发了对属性设置器的调用,都将起作用。

【讨论】:

  • 我已经编辑了您的代码示例,我认为如果您添加更多逻辑,实例变量的 __init__() 方法中的下划线将不起作用,例如在名称设置器中。我还更正了 setter 变量(名称 -> 新名称)。如果它们具有误导性,请随时撤消我的更改,只是想通知您这些:stackoverflow.com/review/suggested-edits/20192932
  • 下划线是有意的;名称是属性的私有实现细节。
  • 嗯,您是否尝试过构建更多逻辑,例如在名称设置器中:if newname == "foo": raise AttributeError("Nope")。那么你不应该创建一个名为“foo”的新机器人。但是对于您的代码,它仍然是允许的。我想重点是,私有/受保护/公共细节是在由属性修饰的函数中处理的,而实例变量看起来像一个公共变量。我可能在这里错过了一个重要的点?!
  • 您可以将任何您想要的逻辑添加到属性函数中。仍然没有真正的公共与私人的概念。 _ 只是传统的建议用户不应该直接访问变量,而只能使用属性提供的任何接口。
  • 是的,但这不是我的意思。问题:如果您在 init-method 中添加下划线,为什么您的代码无法按预期工作。 if newname == "foo": raise 在名称设置器中。我希望你不能创建一个名为“foo”的对象。但它的工作!如果您设置的名称不带下划线,那么您将无法创建名称为“foo”的对象。
【解决方案2】:

关于源代码:有时您向其他人提供编译的python 文件,但不提供源代码,并且您不希望人们将直接属性分配弄得一团糟。

现在,将数据封装视为安全措施,这是分配或提供值之前的最后一点:

您可能希望使用sets 验证或处理分配,以确保分配对您的需要有效或以正确的格式输入变量(例如,您要检查属性__build_year 是高于 1800,或者名称是 string)。在没有使用特定类型声明变量的动态语言(如 python)中非常重要。

gets 也是如此。您可能希望以小数形式返回年份,但在类中将其用作整数。


是的,您的示例是基本的数据封装。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-07-28
    • 1970-01-01
    • 2013-06-11
    • 2018-10-03
    • 1970-01-01
    • 2014-01-24
    相关资源
    最近更新 更多