【问题标题】:Is self.__dict__.update(**kwargs) good or poor style?self.__dict__.update(**kwargs) 风格是好还是坏?
【发布时间】:2012-04-01 11:07:27
【问题描述】:

在 Python 中,假设我有一个类 Circle,它继承自 Shape。 Shape 需要 x 和 y 坐标,此外,Circle 需要半径。我希望能够通过执行类似的操作来初始化 Circle,

c = Circle(x=1., y=5., r=3.)

Circle 继承自 shape,所以我需要对 __init__ 使用命名参数,因为不同的类需要不同的构造函数。我可以手动设置 x、y 和 r。

class Shape(object):
    def __init__(self, **kwargs):
        self.x = kwargs['x']
        self.y = kwargs['y']

class Circle(Shape):
    def __init__(self, **kwargs):
        super(Circle, self).__init__(**kwargs)
        self.r = kwargs['r']

或者,我可以使用 self.__dict__.update(kwargs) 自动设置我的 Circle 的属性

class Shape(object):
    def __init__(self, **kwargs):
        self.__dict__.update(**kwargs)

class Circle(Shape):
    def __init__(self, **kwargs):
        super(Circle, self).__init__(**kwargs)

这样做的好处是代码更少,而且我不需要像self.foo = kwargs['foo'] 这样维护样板文件。缺点是 Circle 需要哪些参数并不明显。这被认为是作弊还是这种风格很好(只要 Circle 的界面有据可查)?


感谢大家的深思熟虑的回复。 self.__dict__.update(**kwargs) hack 在我尝试组织代码时很有用,但我会确保用正确地显式传递参数并在生产代码中进行明确的错误检查来替换它。

【问题讨论】:

标签: python oop dictionary


【解决方案1】:
class Shape(object):
    def __init__(self, x=None, y=None):
        self.x = x
        self.y = y

class Circle(Shape):
    def __init__(self, r=None, **kwargs):
        super(Circle, self).__init__(**kwargs)
        self.r = r

就是这样。当你真的不需要它们时,不要使用**kwargs

这被认为是作弊还是这种风格很好(只要 Circle 的接口有据可查)?

当您在编写简单易懂的代码和令人头疼的代码 + 漂亮的文档字符串之间做出选择时,实际上您别无选择,您只需编写简单、自记录的代码即可:)

【讨论】:

  • 有很多非常合理的时间可以使用**无论什么时候,尤其是当你重载其他类时
  • @StevenRumbalski 是的,我有一个填充物错过了一些东西:)
  • 对于此解决方案,请注意此继承链中的所有类都无法访问以这种方式从**kwargs 中剥离的关键字值(在子类中)。例如Shape不能定义r参数并期望得到它,而不修改Circle的__init__方法。
  • 我更喜欢这个解决方案,因为它让我不必在每个__init__() 中明确提及 x 一直到类层次结构,同时确保正确的参数出现在正确的级别。跨度>
【解决方案2】:

如果你想让它更明显,你可以让Circle.__init__ 对参数进行一些健全性检查。大概你会检查以确保所有参数都在那里,并且可能会为无意义的参数引发错误。

您甚至可以在Shape 中创建一个装饰器或辅助函数来为您执行此操作。像这样的:

class Circle(Shape):
    def __init__(self, **kwargs):
        self.check(kwargs, 'x', 'y', 'r')
        super(Circle, self).__init__(**kwargs)

.check 将在Shape 中实现,本质上只是验证所有参数都在kwargs 中,并且可能没有额外的参数(对不起,没有那个的代码 - 你可以在你的自己的)。您甚至可以让子类重载它以检查可选参数,您可能希望以不同于其他参数的方式处理这些参数(即,给它们一个默认值,否则不会在 Shape.__init__ 中分配。

否则,如果您记录您的界面,并且它按照记录的方式工作,那总是没问题的。您为使其按照我们“预期”的方式工作(为不正确的参数抛出异常)所做的任何其他事情都是一种奖励。

【讨论】:

  • 你怎么知道“.check是在Shape中实现的”。你是 OP 的老师吗?
  • @StevenRumbalski - 如果我是的话,我会成为最年轻的计算机科学老师。我的意思是 OP 可以实现 Shape.check 然后在所有子类中使用它。我会澄清的。
【解决方案3】:

我会说第一种方法肯定更可取,因为explicit is better than implicit

考虑一下如果你在初始化 Circle 时打错字会发生什么,比如Circle(x=1., y=5., rr=3.)。您希望立即看到此错误,__dict__.update(kwargs) 不会发生这种情况。

【讨论】:

    【解决方案4】:

    如果你想自动分配,我建议以下方法:

    def __init__(self, **kwargs):
        for key, value in kwargs.iteritems():
            setattr(self, key, value)
    

    就风格而言,介于明确编写和使用self.__dict__ 自行破解之间。

    【讨论】:

    • 我更愿意看到self.__dict__.update(kwargs) 而不是这种(可能效率较低且)更长的方法来实现相同的结果。
    • 这将具有使用描述符的优势。仅出于这个原因,直接使用 __dict__ 就不是一个好主意。
    • 你能帮我理解为什么这对描述符更有效吗?
    • 我的理解是,在阅读 docs.python.org/3/howto/descriptor.html 之后,Chris Morgan (~2) 正在推广 Simeon 的答案,因为它会更好地与基于你的类的类一起工作,它确实会覆盖默认的 get/set方法,因此成为数据描述符。使用 setattr 将与它们的覆盖兼容,而直接使用 dict 将绕过任何此类覆盖。
    猜你喜欢
    • 2010-10-20
    • 2011-10-19
    • 1970-01-01
    • 2014-07-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-05-04
    • 1970-01-01
    相关资源
    最近更新 更多