python-3.x 在__str__实现中允许使用哪些变量?

92vpleto  于 2022-12-14  发布在  Python
关注(0)|答案(1)|浏览(160)

我正在学习使用this的python @property注解,我的理解是它是一个内置属性,方便访问和修改类属性,我在一些属性上使用这个注解创建了一个类,然后尝试实现str来显示所有内容。
下面,使用str()方法显示的一切都很好,但是,当我用self._area替换self.area时,得到的是AttributeError: Circle object has no attribute _area
str()实现中,为什么允许我使用self._diameter,而***不允许使用***self._area?

class Circle(object):
    def __init__(self, radius): 
        self._radius=radius
    
    @property
    def radius(self):
        return self._radius
        
    @radius.setter
    def radius(self, radius):
        self._radius=radius
    
    @property 
    def diameter(self):
        return self._diameter
    
    @diameter.setter
    def diameter(self, diameter):
        self._diameter=diameter
    
    @diameter.deleter
    def diameter(self):
        del self._diameter
    
    @property
    def area(self):
        self._area = self._radius**2*3.14
        return self._area

        
    def __str__(self):
        return f'Circle has radius of {self._radius}, diameter of {self._diameter}, and area of {self.area}'    
        
c = Circle(4)
c.diameter=3
print(str(c))
7uhlpewt

7uhlpewt1#

这是不起作用的,因为您(相当无意义地)只在self.area被访问时才懒洋洋地计算self._area,所以如果用户提前访问c.area,您的__str__实现将正常工作,但在它未被访问时不工作。
简单解决方案:使用一个快速计算的属性或者一个延迟计算的属性,不要混淆这两种属性。作为一个规则,你应该在__init__中定义(即使只是用None来定义延迟计算的属性)all 属性;它对维护人员很友好(他们不必搜索整个类来找出属性集),而且现代CPython会为此奖励您(通过使用键共享字典来存储属性,显著降低了每个示例的内存开销,几乎将内存开销减半)。
另外,提供对底层属性的完全访问的属性在Python中是没有意义的;如果您无论如何都要授予用户完全访问权限,只需将其设置为常规属性即可,这样可以节省大量的样板文件,并使代码运行得更快。在您的代码中,radiusdiameter都是这种情况。但在diameter的情况下,让它管理单独的属性没有意义(diameter的定义是半径的两倍,因此它们应该使用公共状态,_diameter不应该存在),并且area足够便宜,可以重新计算,它可能只是一个未缓存的属性。
类的简化惯用版本如下所示:

class Circle:  # (object) only necessary on Py2; hope you're not developing for it
    def __init__(self, radius):
        self.radius = radius  # Use a public attribute without bothering with
                              # pointless property wrapping that protects nothing
                              # and just slows things down

    # diameter is just another "view" of the radius, so make the property
    # perform converting mutations to the radius
    @property
    def diameter(self):
        return self.radius * 2  # Computable from radius

    @diameter.setter
    def diameter(self, diameter):
        self.radius = diameter / 2  # Convert to radius and store

    @property
    def area(self):
        # This is cheap enough to compute that it's probably not worth
        # caching (if the radius changes, you'd have to invalidate/recompute the cached value, it's just a pain)
        return math.pi * self.radius ** 2  # Computable from radius

    def __str__(self):
        # Use the accessors; they're accurate, and duplicating the code to compute
        # them is silly
        return f'Circle has radius of {self.radius}, diameter of {self.diameter}, and area of {self.area}'

c = Circle(4)
c.diameter = 3
print(c)  # print already stringifies; calling str on it is redundant

旁注:您总是希望在一个类上实现__repr__,这样引用它的回溯和类似操作才有用;它非常容易编写(当__init__参数不变时,使用type(self).__name__使它成为子类友好的):

def __repr__(self):
    return f'{type(self).__name__}({self._radius!r})'  # !r meaningless for int/float, but it's good to use it as a habit for when it matters, e.g. str arguments

相关问题