【问题标题】:Is there a reason not to send super().__init__() a dictionary instead of **kwds?是否有理由不发送 super().__init__() 字典而不是 **kwds?
【发布时间】:2013-10-22 15:42:01
【问题描述】:

我昨天刚开始构建一个基于文本的游戏,作为学习 Python 的练习(我使用的是 3.3)。我说的是“基于文本的游戏”,但我的意思更多的是 MUD,而不是选择你自己的冒险。无论如何,当我昨天弄清楚如何使用super() 处理继承和多重继承时,我真的很兴奋,但是我发现参数传递确实使代码变得混乱,并且需要处理大量松散的小变量。此外,创建保存文件似乎是一场噩梦。

所以,我想,“如果某些类层次结构只接受一个参数,一个字典,然后将字典传回怎么办?”举个例子,这里有两个类精简为它们的 init 方法:

class Actor:
    def __init__(self, in_dict,**kwds):
        super().__init__(**kwds)
        self._everything = in_dict
        self._name = in_dict["name"]
        self._size = in_dict["size"]
        self._location = in_dict["location"]
        self._triggers = in_dict["triggers"]
        self._effects = in_dict["effects"]
        self._goals = in_dict["goals"]
        self._action_list = in_dict["action list"] 
        self._last_action = ''
        self._current_action = '' # both ._last_action and ._current_action get updated by .update_action()

class Item(Actor):
    def __init__(self,in_dict,**kwds)
        super().__init__(in_dict,**kwds)
        self._can_contain = in_dict("can contain") #boolean entry
        self._inventory = in_dict("can contain") #either a list or dict entry

class Player(Actor):
    def __init__(self, in_dict,**kwds):
        super().__init__(in_dict,**kwds)
        self._inventory = in_dict["inventory"] #entry should be a Container object
        self._stats = in_dict["stats"]

将被传递的示例字典:

playerdict = {'name' : '', 'size' : '0', 'location' : '', 'triggers' : None, 'effects' : None, 'goals' : None, 'action list' = None, 'inventory' : Container(), 'stats' : None,}

(一旦字典通过,None 将被 {} 替换。)

因此,in_dict 被传递给前一个类,而不是 **kwds 的巨大负载。 我喜欢这个,因为:

  1. 它使我的代码更整洁、更易于管理。
  2. 只要字典至少有一些被调用键的条目,它就不会破坏代码。此外,给定的参数是否从未被使用也没关系。
  3. 文件 IO 似乎变得容易多了(存储为 dicts 的玩家数据字典、存储为 dicts 的项目数据字典等)

我明白了**kwds 的意思(编辑:显然我没有),并且在传递更少的参数时似乎并不麻烦。这只是似乎是一种在创建每个实例时处理大量属性需求的舒适方式。

也就是说,我仍然是一个主要的 python 菜鸟。所以,我的问题是这样的:是否有一个潜在的原因为什么通过 super() 将相同的 dict 反复传递给基类会比仅仅用讨厌的(又大又杂乱的)**kwds 传递更糟糕的主意?(例如,我这个级别的人可能不知道的口译员问题。)

编辑:

以前,创建一个新的 Player 可能看起来像这样,为每个属性传递一个参数。

bob = Player('bob', Location = 'here', ... etc.)   

需要的参数数量激增,我只包括了真正需要存在的属性,以免中断来自 Engine 对象的方法调用。

这是迄今为止我从答案和 cmets 中得到的印象:

发送同一个字典并没有什么“错误”,只要没有机会修改它的内容(Kirk Strauser)并且字典总是有它应该有的东西(goncalopp)。真正的答案是问题错了,使用in_dict 而不是**kwds 是多余的。

这是正确的吗? (另外,感谢您提供的丰富多样的反馈!)

【问题讨论】:

  • 也许我错过了问题的重点,但kwds 是一个字典,可以像一个一样使用。使用** 只是提供了另一种调用方式。你可以打电话给Player(**playerdict),怎么这么乱?
  • 请记住,**kwds 函数接受字典作为参数的一种方式。只是(a)调用它的语法与接受单个参数的函数不同,该参数应该是字典; (2) 如果每个函数依次采用**kwds 并用__init__(**kwds) 调用,则与如果采用kwds 并用kwds 调用相比,则有一个额外的副本。
  • @cmd 原始模型涉及传递大量单独的参数(其中许多是现在正在相关字典中传递的字典),这些参数会很混乱。基本上,卡尔所描述的。

标签: python python-3.x dictionary super


【解决方案1】:

我不确定我是否完全理解您的问题,因为在您更改为使用 in_dict 之前,我看不到代码的外观。听起来您已经在对 super 的调用中列出了数十个关键字(可以理解,这不是您想要的),但这不是必需的。如果您的子班有一个包含所有这些信息的字典,那么当您使用**in_dict 拨打电话时,它可以变成kwargs。所以:

class Actor:
    def __init__(self, **kwds):

class Item(Actor):
    def __init__(self, **kwds)
        self._everything = kwds
        super().__init__(**kwds)

我认为没有理由为此添加另一个字典,因为您可以操纵并传递为 kwds 创建的字典

编辑:

至于使用dict扩展的**与显式列出参数的效率问题,我用这段代码做了一个非常不科学的时序测试:

import time

def some_func(**kwargs):
    for k,v in kwargs.items():
        pass

def main():
    name = 'felix'
    location = 'here'
    user_type = 'player'

    kwds = {'name': name,
            'location': location,
            'user_type': user_type}

    start = time.time()
    for i in range(10000000):
        some_func(**kwds)

    end = time.time()
    print 'Time using expansion:\t{0}s'.format(start - end)
    start = time.time()
    for i in range(10000000):
        some_func(name=name, location=location, user_type=user_type)

    end = time.time()
    print 'Time without expansion:\t{0}s'.format(start - end)


if __name__ == '__main__':
    main()

运行这 10,000,000 次会在绕过 dict 并使用 ** 时产生轻微(并且可能在统计上毫无意义)的优势。

Time using expansion:   -7.9877269268s
Time without expansion: -8.06108212471s

如果我们打印 dict 对象的 ID(函数外的 kwds 和函数内的 kwargs),你会看到 python 为函数创建了一个新的 dict 以供在任何一种情况下使用,但实际上该函数永远只得到一个 dict .在函数的初始定义(创建 kwargs 字典的位置)之后,所有后续调用都只是更新属于该函数的该字典的值,无论您如何调用它。 (另见this关于如何在python中处理可变默认参数的启发性SO问题,这有点相关)

因此,从性能的角度来看,您可以选择对您有意义的任何一个。它不应该对 python 在幕后的运作方式产生有意义的影响。

【讨论】:

  • 谢谢,有道理。您能否在更新后的问题中解决我的结论?
  • 是的,我认为你的结论是准确的。这里的另一个好处是kwds 可以包含超类不需要的东西,这没关系。它只需要提取对其重要的值。但是您关于使用in_dict 的主要问题是多余的结论是正确的,因为kwds 将同样减少参数列表。我还在我的答案中添加了一些时间信息,比较了调用具有许多命名参数的函数的两种方式
【解决方案2】:

我自己已经做到了,in_dict 是一个有很多键的字典,或者是一个设置对象,或者是一些其他具有很多有趣属性的东西的“blob”。如果它能让你的代码更简洁,那完全没问题,特别是如果你把它命名为 settings_objectconfig_dict 或类似名称。

不过,这不应该是通常的情况。通常最好显式传递一小组单独的变量。它使代码更清晰,更容易推理。客户端可能会意外传递in_dict = None,直到某些方法尝试访问它时您才会知道。假设Actor.__init__ 没有剥离in_dict,而是像self.settings = in_dict 一样存储它。一段时间后,Actor.method 出现并尝试访问它,然后繁荣!死进程。如果您正在调用 Actor.__init__(var1, var2, ...),那么调用者会更早地引发异常,并为您提供有关实际问题的更多上下文。

所以是的,无论如何:在适当的时候随意这样做。请注意,这并不适合经常使用,这样做的愿望可能是一种告诉您重构代码的气味。

【讨论】:

  • 我明白为什么最好尽早获得异常,但我没有关注它可能来自哪里。假设我的代码从文件数据构造字典没有问题,你能解释一下我什么时候可以期待in_dict = None?还是只是“最佳实践”的问题?例如stick_data 将是一个字典,其中包含一根棍子所需的属性,它保存在item_dictionary 中,它是根据读取的文件构造的。我没有完整的代码来展示这个,因为我正在确定我需要如何组织这些材料(因此是这个问题。)
  • 对象可能以一百万种方式结束为空或无,大多数涉及检索用户输入。也许您正在从 Web 请求中接收 JSON。也许您正在从数据库中进行选择,但没有得到任何结果。或者您可能正在阅读命令行输入,而用户不小心过早地点击了“return”。在任何这些情况下(以及更多情况),您都希望在接收输入的地方进行输入验证,而不是在库代码的核心。
  • 哈!自从我之前的评论(找出保存文件)以来,我一直在阅读有关 JSON(特别是检索嵌套字典)的内容。对我来说也是新的。无论如何,我认为您的意思是,“在您可以确定None(或其他一些不适当的参数)不会传递给__init__ 方法的非常特定的情况下使用它很好而且很花哨。”这大致是您的意图吗?
  • “请注意,它经常不合适,这样做的愿望可能是一种告诉您重组代码的气味。”我想我明白你的意思了。目前,我只写了大概十几个类,并开始考虑复杂的嵌套字典的细节,这些字典通过基本的关联类的方法相互影响。真的,我可以通过在 Player 中掌握基础知识,然后用越来越具体的类对其进行改进来减少这种情况。然后,将通过选择类而不是提供大量输入来定义细节。我觉得很傻。
  • @Ben 是的,我就是这个意思。我非常喜欢你的第二种方法,即使用更容易推理(并且更容易测试!)的简单方法,然后通过子类化来改变它们的行为。
【解决方案3】:

这不是特定于 python 的,但我可以看到像这样传递参数的最大问题是它破坏了encapsulation。任何类都可以修改参数,而且很难判断每个类中需要哪些参数 - 使您的代码难以理解,也更难调试。

考虑显式使用每个类中的参数,并在剩余的部分调用超级的__init__。您无需明确说明:

class ClassA( object ):
    def __init__(self, arg1, arg2=""):
        pass

class ClassB( ClassA ):
    def __init__(self, arg3, arg4="", *args, **kwargs):
        ClassA.__init__(self, *args, **kwargs)


ClassB(3,4,1,2)

您也可以不初始化变量并使用方法来设置它们。然后,您可以在不同的类中使用不同的方法,并且所有子类都可以访问超类的方法。

【讨论】:

  • 这是最初的想法(在尝试字典之前),因为它需要从文件中读取大量单独的数据,然后单独传递每个数据。我仍在尝试在 Python 的上下文中了解封装的想法,因为这些属性都是公开的。目前,__init__ 属性很关键;如果他们不在那里,事情就会破裂。订单在这里算吗?也就是说,在方法的第一行调用super().__init__(**kwds) 的(个人)约定是否不会在其他任何东西可以修改条目之前将字典移动到祖先?
猜你喜欢
  • 1970-01-01
  • 2010-12-22
  • 2017-02-14
  • 1970-01-01
  • 2020-07-06
  • 2011-03-18
  • 2010-12-31
  • 2021-12-01
  • 1970-01-01
相关资源
最近更新 更多