3

I just started building a text based game yesterday as an exercise in learning Python (I'm using 3.3). I say "text based game," but I mean more of a MUD than a choose-your-own adventure. Anyway, I was really excited when I figured out how to handle inheritance and multiple inheritance using super() yesterday, but I found that the argument-passing really cluttered up the code, and required juggling lots of little loose variables. Also, creating save files seemed pretty nightmarish.

So, I thought, "What if certain class hierarchies just took one argument, a dictionary, and just passed the dictionary back?" To give you an example, here are two classes trimmed down to their init methods:

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"]

Example dict that would be passed:

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

(The None's get replaced by {} once the dictionary has been passed.)

So, in_dict gets passed to the previous class instead of a huge payload of **kwds. I like this because:

  1. It makes my code a lot neater and more manageable.
  2. As long as the dicts have at least some entry for the key called, it doesn't break the code. Also, it doesn't matter if a given argument never gets used.
  3. It seems like file IO just got a lot easier (dictionaries of player data stored as dicts, dictionaries of item data stored as dicts, etc.)

I get the point of **kwds (EDIT: apparently I didn't), and it hasn't seemed cumbersome when passing fewer arguments. This just appears to be a comfortable way of dealing with a need for a large number of attributes at the the creation of each instance.

That said, I'm still a major python noob. So, my question is this: Is there an underlying reason why passing the same dict repeatedly through super() to the base class would be a worse idea than just toughing it out with nasty (big and cluttered) **kwds passes? (e.g. issues with the interpreter that someone at my level would be ignorant of.)

EDIT:

Previously, creating a new Player might have looked like this, with an argument passed for each attribute.

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

The number of arguments needed blew up, and I only included the attributes that really needed to be present to not break method calls from the Engine object.

This is the impression I'm getting from the answers and comments thus far:

There's nothing "wrong" with sending the same dictionary along, as long as nothing has the opportunity to modify its contents (Kirk Strauser) and the dictionary always has what it's supposed to have (goncalopp). The real answer is that the question was amiss, and using in_dict instead of **kwds is redundant.

Would this be correct? (Also, thanks for the great and varied feedback!)

4

3 回答 3

1

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

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 字典的位置)之后,所有后续调用都只是更新属于该函数的该字典的值,无论您如何调用它。(另请参阅这个关于如何在 python 中处理可变默认参数的启发性 SO 问题,这有点相关)

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

于 2013-10-22T16:27:37.500 回答
1

我自己已经做到了,那里in_dict是一个有很多键的字典,或者一个设置对象,或者其他一些具有很多有趣属性的东西的“blob”。如果它使您的代码更清晰,那是完全可以的,特别是如果您将其命名为类似settings_objectconfig_dict类似的名称。

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

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

于 2013-10-22T15:53:34.580 回答
0

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

__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)

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

于 2013-10-22T16:02:29.080 回答