【问题标题】:Python class structuring, getting and setting properties and inheritance [closed]Python类结构,获取和设置属性和继承[关闭]
【发布时间】:2021-09-06 21:25:04
【问题描述】:

这是我经常遇到的一个问题,我终于想找到最 Pythonic 的解决方案。其核心情况可描述如下:

  1. 一个类包含一些必须根据__init__()参数计算或测试的属性。
  2. 类实例化后属性可能允许也可能不允许更改。
  3. 每次更改后必须重新计算/测试允许更改的属性。
  4. 类应该是“继承友好的”。

示例项目中的目标:帮助进行 2d 形状计算(例如多边形之间的最小距离或线之间的交点)的几何模块。线和多边形有很多共同的属性,所以创建一个 BaseGeometry 类,LinePolygon 类继承自该类。 BaseGeometry 属性:

  • points 一个 (n,x,y) 点列表,这些点在实例化时设置并且可以改变。每次设置时,必须将其断言为 numpy 数组。
  • domain:描述形状边界的 (xmin, xmax, ymin, ymax) 元组。每次设置 points 时都必须重新计算,但不得进行外部更改。

下面,我写了三个(简化的)解决方案来解决这个问题,都各有优缺点。

方法 1:错误地允许更改 domain 并且不清楚存在哪些类属性。

class BaseGeometry1:
    
    def __init__(self, points):
        self.set_points(points)

    def set_points(self, points):
        assert type(points) is np.ndarray
        self.points = points
        x, y = points.T
        self.domain = ((min(x), max(x), min(y), max(y)))

方法 2:仍然允许更改 domain。清楚存在哪些属性,但感觉笨拙/不合逻辑。任意calculate_domain() 是否具有points 作为参数或仅使用self.points。每次更新 points 时都必须调用 calculate_domain()

class BaseGeometry2:
    
    def __init__(self, points):
        self.points = self.set_points(points)
        self.domain = self.calculate_domain()

    def set_points(self, points):
        assert type(points) is np.ndarray
        return points

    def calculate_domain(self):
        x, y = self.points.T
        return (min(x), max(x), min(y), max(y))

方法 3:我觉得这是在正确的轨道上,但我仍然不确定结构。 points.setter 也设置了 _domain 感觉很奇怪。另外,只对所有类属性使用 @property 装饰器是不好的做法吗?

class BaseGeometry3:

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

    @property
    def points(self):
        return self._points

    @points.setter
    def points(self, points):
        assert type(points) is np.ndarray
        self._points = points
        x, y = points.T
        self._domain = ((min(x), max(x), min(y), max(y)))
      
    @property
    def domain(self):
        return  self._domain

我的问题如下:

  1. 这些方法之一是否被认为是传统方法?为什么/为什么不?
  2. 在解决此问题时,还需要注意哪些其他约定?
  3. 还有哪些其他提示或资源可以帮助我改进课堂结构?
  4. 继承自 GeometryBase3 的类是否必须完全重新定义 points.setter 方法才能引入一些从 points 计算的新属性?

另外,我刚开始在这里提问,因此也欢迎对帖子提出任何反馈。提前感谢您的时间和任何答案!

亲切的问候,

约斯特

【问题讨论】:

  • 我会选择第三种方法。如果计算domain 的成本很高,并且points 的设置频率会比domain 的设置频率高,那么您可以在points 的setter 中将_domain 设置为None,然后计算并赋值仅当 domain 的 getter 请求时。您无需为任何顶级功能重新定义 points 的设置器。
  • 欢迎来到 Stack Overflow。请查看tour 页面以获取有关询问的信息。具体来说,问题应该集中在一个的事情上,否则你可能会得到几个不同的答案,它们都同样只解决了你问题的一部分。同样,Stack Overflow 不适合基于意见/主观问题,其中包括最佳实践建议、约定、提示请求等。
  • 最后,像“提前感谢”或签名这样的元评论是不必要的;您通过支持有用的答案/cmets 并接受最能解决您的问题/回答您的问题的答案来感谢这里的人们。同样,每个问题都使用帖子右下角的用户卡签名,其中包括作者姓名、代表、时间戳等。
  • 感谢您的提示和解释!关于意见/约定规则:我完全理解这里要避免基于意见的问题。但是,我觉得代码结构和样式是“好”编程的主要部分(至少对我而言)是我花在思考代码上的大部分时间的原因。我渴望在这方面做得更好,但事实上有许多有效的方法正是这感觉如此困难的原因。堆栈溢出是否根本不是就该主题进行公开讨论的最佳场所,还是我应该用不同的措辞来表达我的问题?

标签: python inheritance properties attributes structure


【解决方案1】:

这里没有一个正确的答案,但我喜欢方法 2 和方法 3 的部分内容。

这样的事情怎么样?这使用了descriptor 协议,其中property 类只是一个特定的形式。它更符合 DRY(“不要重复自己”)原则,而不是为类中的每个属性使用 @property 装饰器。

class UnsettableGeometricDescriptor:
    def __set_name__(self, owner, name):
        self.name = name
        self.lookup_name = f'_{name}'

    def __get__(self, obj, type=None):
        return getattr(obj, self.lookup_name)

    def __set__(self, obj, value):
        raise AttributeError(f'Attribute {self.name} cannot be set directly, use method "update_shape" instead')


class BaseGeometry4:
    def __init__(self, points):
        self.update_shape(points)

    points = UnsettableGeometricDescriptor()
    domain = UnsettableGeometricDescriptor()

    def update_shape(self, points):
        assert isinstance(points, np.ndarray), "points parameter has to be a numpy array!!!"
        self._points = points 
        x, y = points.T
        self._domain = ((min(x), max(x), min(y), max(y)))

这个方法的好处是它的可扩展性——如果你需要向子类添加更多不可设置的属性,这样做很容易,你可以通过在子类中调用 super() 来扩展 update_shape()。

这是怎么回事?

如果你实例化一个BaseGeometry4的实例,你会发现它有一个points属性和一个domain属性,但是都不能直接设置。

这里发生了什么,当您“访问”points 属性时,这会调用UnsettableGeometricDescriptor 类中的__get__ 方法。在points 属性的情况下,此__get__ 方法实际上只是将您重定向到BaseGeometry4 实例的_points 属性,在domain 属性的情况下将您重定向到_domain。描述符的__set__ 方法只是抛出一个异常,因为您永远不希望用户能够直接更新这些属性。这些描述符类所遵循的值(_points_domain)在 BaseGeometry4update_shape() 方法中设置

关于描述符协议here 有一个很棒的 RealPython 教程,关于描述符的官方 python 文档可以在 here 找到。

【讨论】:

    猜你喜欢
    • 2013-04-06
    • 1970-01-01
    • 2010-09-19
    • 2011-08-28
    • 1970-01-01
    • 2019-03-09
    • 2013-03-01
    • 2020-05-16
    • 1970-01-01
    相关资源
    最近更新 更多