【问题标题】:Python : How making an attribute private stop the program to go into an infinite loop ? (Private v/s Public attribute)Python:如何使属性私有停止程序进入无限循环? (私有与公共属性)
【发布时间】:2019-06-09 02:22:33
【问题描述】:

我对 python 中的面向对象概念很天真。在阅读 here 的 OOP 概念时,我遇到了一个例子。

class P1:

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

    @property
    def x(self):
        return self.__x

    @x.setter
    def x(self, x):
        if x < 0:
            self.__x = 0
        elif x > 1000:
            self.__x = 1000
        else:
            self.__x = x

e = P(x = 2100)
f = e.x*2

print(f)
2000

如果我不将我的变量设为私有(对于 P2 类),那么我猜它会陷入无限循环。

class P2:

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

    @property
    def x(self):
        return self.__x

    @x.setter
    def x(self, x):
        if x < 0:
            self.x = 0
        elif x > 1000:
            self.x = 1000
        else:
            self.x = x

e = P(x = 2100) #Infinite loop 

为了检查 P2 类实际在做什么,我将代码重组为

class P2:
    def __init__(self,x):
        print('init area1')
        self.x = x

    @property
    def x(self):
        print('property area2')
        return self.x

    @x.setter
    def x(self, x):
        print('setter area3')
        if x < 0:
            print('setter area4')
            self.x = 0
        elif x > 1000:
            print('setter area5')
            self.x = 1000
        else:
            print('setter area6')
            self.x = x

当我尝试运行 P2(x = 2100) 时,它给了我一个不可阻挡的输出,例如:

init area1
setter area3
setter area5
setter area3
setter area6
setter area3
setter area6
setter area3
setter area6.......

看来我的程序首先调用了 init 方法,然后它不断地从 setter area 3setter area 6 来回运行。 谁能解释一下

  1. 幕后发生了什么?程序运行情况如何?

  2. 为什么要在这里制作魔术私有属性以使程序不会陷入无限循环

  3. @property 和 @x.setter 在这里如何相互关联?我不能不写@property就写@setter

我知道这些是基本问题,但我浏览了很多在线资料,但没有找到更好的答案。

【问题讨论】:

    标签: python python-3.x class oop private


    【解决方案1】:

    "为什么神奇的私有属性都在这里使程序是 不会陷入无限循环”

    实际上这不是一个使用双下划线名称修饰的好地方。我喜欢那个教程,除了那个细节。你可以使用单个下划线,或者任何有效的python标识符除了被属性占用的那个,你会看到同样的效果。

    property 是实现the descriptor protocol 的对象。它是一个方便的描述符,用于常见的描述符用例。但是我们可以创建自己的描述符类型。

    从根本上说,描述符是实现__get____set____delete__ 任意组合的任何python 类型。

    当您执行some_object.some_attributesome_object.some_attribute = valuedel some_object.some_attribute 时,这些将被调用,其中some_attributesome_object.__class__ 上的描述符。

    那么,考虑一个具体的例子:

    >>> class Foo:
    ...     def __get__(self, obj, objtype):
    ...         print('inside Foo.__get__')
    ...         return 42
    ...
    >>> class Bar:
    ...     foo = Foo()
    ...
    >>> bar = Bar()
    >>> bar.foo
    inside Foo.__get__
    42
    

    描述符拦截属性访问和修改,以及删除将描述符作为属性的类的实例以允许各种有趣的东西。 p>

    注意,描述符属于类:

    >>> vars(bar)
    {}
    >>> vars(Bar)
    mappingproxy({'__module__': '__main__', 'foo': <__main__.Foo object at 0x1025272e8>, '__dict__': <attribute '__dict__' of 'Bar' objects>, '__weakref__': <attribute '__weakref__' of 'Bar' objects>, '__doc__': None})
    

    如果我将实例属性设置为与持有该属性的类属性同名,则会发生正常的 python 阴影行为:

    >>> bar.foo = 99
    >>> bar.foo
    99
    >>> vars(bar)
    {'foo': 99}
    

    但是我们可以控制这个,我们可以实现__set__

    >>> class Foo:
    ...    def __get__(self, obj, objtype):
    ...       return 42
    ...    def __set__(self, obj, val):
    ...       print('nah-ah-ah')
    ...
    ...
    >>> class Bar:
    ...     foo = Foo()
    ...
    >>> bar = Bar()
    >>> bar.foo
    42
    >>> bar.foo = 99
    nah-ah-ah
    >>> bar.foo
    42
    

    property 对象仅允许您提供在您使用 property.__get__property.__set__property.__delete__ 时将被委托给的函数。文档字符串信息量很大,只需在 python shell 中使用help(property)

    class property(object)
     |  property(fget=None, fset=None, fdel=None, doc=None)
     |
     |  Property attribute.
     |
     |    fget
     |      function to be used for getting an attribute value
     |    fset
     |      function to be used for setting an attribute value
     |    fdel
     |      function to be used for del'ing an attribute
     |    doc
     |      docstring
     |
     |  Typical use is to define a managed attribute x:
     |
     |  class C(object):
     |      def getx(self): return self._x
     |      def setx(self, value): self._x = value
     |      def delx(self): del self._x
     |      x = property(getx, setx, delx, "I'm the 'x' property.")
     |
     |  Decorators make defining new properties or modifying existing ones easy:
     |
     |  class C(object):
     |      @property
     |      def x(self):
     |          "I am the 'x' property."
     |          return self._x
     |      @x.setter
     |      def x(self, value):
     |          self._x = value
     |      @x.deleter
     |      def x(self):
     |          del self._x
    

    所以无论你用@property.setter 装饰什么,你都可以想象得到传递给property(fset=&lt;whatever&gt;)。所以现在,每当您的实例尝试设置x.some_attribute = value 时,其中.some_attributeclass X: 上的一个属性,就会调用property.__set__,您可以想象它会将x.some_attribute = value 转换为X.some_attribute.__set__(x, value)

    所以,为了解决问题的症结,为什么要无限递归,因为使用obj.x = val 其中.x 是一个属性,将调用@987654353 @,但在你的 fset 中你使用obj.x = val,而fset 再次被调用,这是你隐藏的递归。

    @decorator 语法是为了方便起见,总是首先接受 getter,但您可以使用长格式方式仅提供 setter:

    >>> class Weird:
    ...    def setx(self, value):
    ...       self._x = value
    ...    x = property(fset=setx)
    ...
    >>> w = Weird()
    >>> w.x = 'foo'
    >>> w.x
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    AttributeError: unreadable attribute
    

    我强烈推荐阅读descriptor HOWTO。剧透警告,classmethodstaticmethod 都是描述符,Python 如何神奇地将实例传递给方法也是如此(也就是说,所有函数对象都是描述符,__get__ 方法将实例作为第一个参数传递给当被类上的实例访问时,函数本身!。它还展示了所有这些东西的 Python 实现,包括如何在纯 Python 中实现 property

    【讨论】:

    • 非常感谢您的回答。我在这里无法理解的另一件事是,在 P1 类的情况下,self.__x = 1000 如何能够设置 self.x 的值,尽管为 x 定义了属性。请解释一下。
    • @user110244 它没有,它设置了 self.__x 的值,这是属性 getter 返回的值。
    • 非常感谢您的回复。我还有两个问题:1)在执行 P2 类内部时,它从不打印“property area”。这是不是意味着程序没有进入属性区?我现在对程序流程感到困惑。根据我的理解,流程是 init > property(去 setter 检查属性)> setter(从那里返回值)? 2) 在 P1 类中,如果我将 setter 中的代码更改为 elif > 1000: self.__y = 1000,即使“@property”中没有 self.__y,它仍会返回该值。如果没有找到 self.__y 或返回 "@property" 中没有的内容,它不应该中断吗?
    猜你喜欢
    • 1970-01-01
    • 2018-05-09
    • 1970-01-01
    • 2013-10-06
    • 2017-06-27
    • 2010-11-30
    • 2015-06-09
    • 1970-01-01
    • 2017-12-09
    相关资源
    最近更新 更多