【问题标题】:Class design: Initializing and updating instance variables elegantly类设计:优雅地初始化和更新实例变量
【发布时间】:2020-08-05 14:49:49
【问题描述】:

有时我想编写一个带有实例变量的类,它一方面应该在 __init__ 内部初始化,但另一方面也可以通过不同的函数(function_1 - function_3)从在某些事件发生后的内部或外部。

更新函数都依赖于相同的输入参数,但在初始化和以后的更新中工作相同。它们可能是类的成员(@staticmethod 或不是),也可能是从某个包导入的实用函数。

对于以后的更新,“元”更新函数(update_member_variables)显然应该是一个过程,i。 e.什么都不返回,只修改成员变量作为副作用。 但是,对于初始化,它最好是一个纯函数并返回变量的值,以便可以将它们分配给__init__ 中的变量。

这种冲突总是让我经历以下重复代码循环,__init__None-initializations 之外的声明,但永远不会导致令人满意的解决方案:

from some_utils import function_1, function_2, function_3


# A: duplicate code in update_member_variables
class Class:
    def __init__(self, parameter):
        self._variable_1 = function_1(parameter)
        self._variable_2 = function_2(parameter)
        self._variable_3 = function_3(parameter)

    def update_member_variables(self, parameter):
        self._variable_1 = function_1(parameter)
        self._variable_2 = function_2(parameter)
        self._variable_3 = function_3(parameter)


# B: instance variables declared outside __init__
class Class:
    def __init__(self, parameter):
        self.update_member_variables(parameter)

    def update_member_variables(self, parameter):
        self._variable_1 = function_1(parameter)
        self._variable_2 = function_2(parameter)
        self._variable_3 = function_3(parameter)


# C: boilerplate None-assignments
class Class:
    def __init__(self, parameter):
        self._variable_1 = None
        self._variable_2 = None
        self._variable_3 = None
        self.update_member_variables(parameter)

    def update_member_variables(self, parameter):
        self._variable_1 = function_1(parameter)
        self._variable_2 = function_2(parameter)
        self._variable_3 = function_3(parameter)


# D: back to duplicated code in update_member_variables
class Class:
    def __init__(self, parameter):
        (
            self._variable_1,
            self._variable_2,
            self._variable_3
        ) = self._derive_values(parameter)

    def _derive_values(self, parameter):
        return (
            function_1(parameter),
            function_2(parameter),
            function_3(parameter),
        )

    def update_member_variables(self, parameter):
        (
            self._variable_1,
            self._variable_2,
            self._variable_3
        ) = self._derive_values(parameter)

选择 B 很诱人,但由于所有针对 __init__ 之外的成员变量声明的警告,我通常坚持使用 C 或 D,尽管它们看起来臃肿而笨重。

没有更好的方法来解决这种情况吗?也许在盒子外面?还是已经是 A-D 中最“优雅”或最干净的那个了?

相关问题:

https://stackoverflow.com/a/51607441/3863847 中回答了类似的问题,但update_number 仅适用于初始化。接受的答案代码类似于我的 D,但没有 update_member_variables

https://stackoverflow.com/a/20661498/3863847 中回答了另一个相关问题。一般来说,Simeon Visser 指出,在初始化后确保对象一致是开发人员的责任,但无论如何,严格遵守该规则并不是必须的。我的情况是可以选B的情况吗? B的实例化对象至少是一致的。

【问题讨论】:

  • “所有警告”的原因是什么?我记得与此相关的事情与编写单元测试时的特定模拟有关。但是分配Nonein __init__ 对规范没有帮助。在这种情况下,您可以基于实例而不是类来指定。
  • 如果只是为了更好地阅读所涉及的类成员,C看起来还可以。
  • @progmatico:所有警告的原因似乎都是 PEP 8,尽管我还没有找到确切的段落。由于我也发现了另一个建议,如您所建议的 None-assignments,我将认为 C 是最好的版本。谢谢你。还要感谢您指导我使用 autospec。

标签: python oop initialization instance-variables


【解决方案1】:
class A:
    def __init__(self, parameter):
        self._variable_1 = function_1(parameter)
        self._variable_2 = function_2(parameter)
        self._variable_3 = function_3(parameter)

    @property
    def variable_1(self):
        return self._variable_1

    @variable_1.setter
    def variable_1(self, value):
        self._variable_1 = function_1(value)

    ... so on and so forth for other variables ...

 a = A(parameter1)
 # update based on parameters
 a.variable_1 = parameter2

我觉得使用属性可以更好地更新变量。

【讨论】:

  • 感谢您的方法,但恐怕这不是我要找的。通常,在 setter 中隐藏一些功能是个好主意,但我必须以这种方式分别为每个变量调用 setter,尽管每个 function_n() 中都有完全相同的参数。我可以将另一个Variables 类中的变量与Variables.update() 捆绑在一起,Class.update() 调用它,并将它的一个实例作为Class 的成员,但这只会将问题转移到Variables类。
  • 也适用于@S818,仍然是关于规范 如果存在可以触发代码执行的属性或描述符,则在模拟时可能无法使用 autospec。搜索“这并非没有警告”,然后在 here 上阅读
【解决方案2】:

https://stackoverflow.com/a/19292653/3863847 中,sthenault 给出了可读性作为实例变量不应在__init__ 之外声明的原因。

据我所知,这植根于 PEP 8,这就是 pylint 抱怨违规的原因 - 我从不选择 B。

sthenault 还建议在 __init__ 中进行无分配,就像 progmatico 在我的问题下方的评论中所做的那样,它对应于我的版本 C。

虽然我希望有一个优雅的技巧以某种方式规避这种情况,但我暂时将 C 视为“最 Pythonic”。如果以后有人想出我正在寻找的这种神奇的解决方案,我会切换接受的答案。

【讨论】:

    猜你喜欢
    • 2020-10-19
    • 2014-09-14
    • 1970-01-01
    • 2010-10-18
    • 1970-01-01
    • 2011-06-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多