Python:如何使一个私有属性阻止程序进入无限循环?(私有v/s公共属性)

2024-10-05 10:20:00 发布

您现在位置:Python中文网/ 问答频道 /正文

我对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. 为什么要在这里设置magic private属性,这样程序就不会运行到无限循环中

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

我知道这些都是基本问题,但我浏览了太多的网上资料,没有找到更好的答案。你知道吗


Tags: self程序returnifinitdefpropertyelse
1条回答
网友
1楼 · 发布于 2024-10-05 10:20:00

"Why magic private attribute are making here so that the program is not running into an infinite loop"

它实际上不是一个好地方使用双下划线名称弄乱。我喜欢那个教程,除了那个细节。您可以使用一个下划线,或任何有效的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

描述符拦截属性访问和修改,并删除类的实例,该类将描述符作为属性,以允许各种有趣的内容。你知道吗

注意,描述符属于类:

>>> 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})

如果我将一个实例属性设置为与保存该属性的class属性同名,则会出现正常的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__时将委托给的函数。docstrings的信息非常丰富,只需在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装饰什么,你都可以想象get被传递给了property(fset=<whatever>)。所以现在,每当您的实例尝试设置x.some_attribute = value,其中.some_attributeclass X:上的一个属性时,property.__set__就会被调用,您可以想象它会被x.some_attribute = value转换成X.some_attribute.__set__(x, value)

所以,为了找到问题的症结所在,为什么要使用无限递归,因为使用obj.x = val,其中.x是一个属性,将调用fset,但是在fset中使用obj.x = val,并且fset再次被调用,这是隐藏的递归。你知道吗

@decorator语法是为了方便起见,总是首先接受getter,但是您可以使用long-form方式只提供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。Spoiler alert,classmethodstaticmethod都是描述符,Python如何神奇地将实例传递给方法(也就是说,所有函数对象都是描述符,当类上的实例访问时,__get__方法将实例作为第一个参数传递给函数本身!。它还展示了所有这些东西的Python实现,包括如何用纯Python实现property!你知道吗

相关问题 更多 >

    热门问题