对象字面量是否符合Python风格?

53

JavaScript有对象字面量,例如:

var p = {
  name: "John Smith",
  age:  23
}

而.NET拥有匿名类型,例如:

var p = new { Name = "John Smith", Age = 23}; // C#

通过(滥用)命名参数,Python 中可以模拟类似的操作:

class literal(object):
    def __init__(self, **kwargs):
        for (k,v) in kwargs.iteritems():
            self.__setattr__(k, v)
    def __repr__(self):
        return 'literal(%s)' % ', '.join('%s = %r' % i for i in sorted(self.__dict__.iteritems()))
    def __str__(self):
        return repr(self)

用法:

p = literal(name = "John Smith", age = 23)
print p       # prints: literal(age = 23, name = 'John Smith')
print p.name  # prints: John Smith

但这种代码是否被认为是Pythonic的呢?


2
"Pythonic",我喜欢这个词! - Andrew Bullock
3
你的__init__中的for循环可以替换为 self.__dict__ = kw。这将达到相同的效果,但更简洁。 - John La Rooy
8个回答

74

为什么不直接使用字典?

p = {'name': 'John Smith', 'age': 23}

print p
print p['name']
print p['age']

9
你还可以使用 dict 函数:p = dict(name='John Smith', age=23)。该函数用于创建字典,可以指定键值对作为参数,这里创建了一个名为 p 的字典,包含键值对 name: 'John Smith'age: 23 - Tomasz Wysocki
嗯,是的,字典在某种程度上是等价的。此外,Tomasz的建议是一种不错的构建方式。然而,访问字典的“字段”并不像我的代码示例那样好,尽管这只是一个小的语法不便。 - ShinNoNoir
2
虽然我同意,但我认为这里存在一种哲学差异。在Python中占主导地位的哲学是,如果你需要一个具有.name.age成员的对象,你应该明确地创建该对象,包括它的所有构造函数等。 - Wayne Werner
3
当然可以 - 但是在测试/原型化/娱乐中使用交互式解释器完成所有这些工作有点过头了。你知道你以后会明确定义它,但现在你想要直接进入主题... - cji
3
真实故事。如果是这样的话,我会将那个函数放在一个“工具”文件中,并在解释器中玩耍时from tools import literals或类似方式导入 - 因为当你只是做这些时,是否非常符合Python语法并不重要(例如,在真正的代码中有许多人使用1个空格进行缩进 - 明显不是Python风格)。此外,拥有一个“工具箱”,其中包含您经常使用的Python调整的想法本来就是个好主意。 - Wayne Werner
字典不提供属性引用,您必须使用查找键。如果我有很多代码使用属性(没有好的理由),并且我需要过渡支持这些引用的代码,我可能想创建一个对象文字(而不是dict)。 - NeilG

44

你考虑过使用命名元组吗?

使用你的字典表示法。

>>> from collections import namedtuple
>>> L = namedtuple('literal', 'name age')(**{'name': 'John Smith', 'age': 23})

或者关键字参数

>>> L = namedtuple('literal', 'name age')(name='John Smith', age=23)
>>> L
literal(name='John Smith', age=23)
>>> L.name
'John Smith'
>>> L.age
23

很容易将这种行为打包成一个函数。

def literal(**kw):
    return namedtuple('literal', kw)(**kw)

lambda的等价表达式是

literal = lambda **kw: namedtuple('literal', kw)(**kw)

但是我个人认为给“匿名”函数起名字有些愚蠢。


PyRec怎么样?(http://www.valuedlessons.com/2009/10/introducing-pyrec-cure-to-bane-of-init.html) - Plumenator
是的,我已经看过命名元组了,但它不违反DRY原则吗?构建一个对象需要你两次指定每个字段的名称。当然,这可以被规避:literal = lambda **kwargs: namedtuple('literal', ' '.join(kwargs.keys()))(**kwargs); p = literal(name='John Smith', age=23)(代码未经测试,我的机器上没有Py2.6) - ShinNoNoir
1
@ShinNoNoir,创建工厂函数比你的建议要简单一些,因为namedtuple在其第二个参数方面非常灵活。 - John La Rooy
@gnibbler,关于lambda表达式,由于格式限制,我在评论中使用它们。如果考虑到lambda演算,给lambda表达式命名并不是那么愚蠢的事情。关于namedtuple,我之前对它的灵活性不太了解。 - ShinNoNoir
你为什么不能只是使用namedtuple('MyTypeName', [*someDict])(**someDict)呢?例如,当someDict等于{'a':10, 'b':20, 'c':30}时。 - jrh

15

来自ActiveState:

class Bunch:
    def __init__(self, **kwds):
        self.__dict__.update(kwds)

# that's it!  Now, you can create a Bunch
# whenever you want to group a few variables:

point = Bunch(datum=y, squared=y*y, coord=x)

# and of course you can read/write the named
# attributes you just created, add others, del
# some of them, etc, etc:
if point.squared > threshold:
    point.isok = 1

1
太好了,这正是我需要的,用于模拟带有属性的对象进行单元测试时创建对象字面量。在我看来,这是最佳答案 :) 将此技术添加到我的 Pythonic 技能库中。谢谢! - pink spikyhairman
@AndrewBullock,我认为这是最符合“Pythonic”风格的解决方案。虽然大多数人会选择使用命名元组来解决这个问题,但我喜欢这种简洁的方式。 - NeilG

3

我认为创建“匿名”类/实例并没有什么问题。通常情况下,用简单的函数调用一行代码就可以创建一个匿名类或实例,这非常方便。我个人使用类似于以下的代码:

def make_class( *args, **attributes ):
    """With fixed inability of using 'name' and 'bases' attributes ;)"""
    if len(args) == 2:
        name, bases = args
    elif len(args) == 1:
        name, bases = args[0], (object, )
    elif not args:
        name, bases = "AnonymousClass", (object, )
    return type( name, bases, attributes )

obj = make_class( something = "some value" )()
print obj.something

对于创建虚拟对象,它运行得很好。命名元组是可以的,但是不可变的,在某些情况下可能会不方便。而字典呢……嗯,它只是一个字典,但有时你必须传递一些定义了__getattr__而不是__getitem__的内容。

我不知道这是否符合Python风格,但有时它可以加快速度,对我来说这已经足够好了(有时候)。


有趣的方法,除了 make_class 实际上是在构造一个对象。你的方法还存在一个问题,就是你不能使用 name 作为属性名。(但我注意到我的 literal 类也有类似的问题...我不能使用 self 作为属性名)。 - ShinNoNoir
2
实际上它是“类对象” - 在这里实例化:make_class(...)()。我认为?至于名称冲突 - 是的。 - cji

3

我认为你实现的解决方案看起来非常符合Python的风格;话虽如此,types.SimpleNamespace (在这里有文档说明)已经封装了此功能:

from types import SimpleNamespace
p = SimpleNamespace(name = "John Smith", age = 23)
print(p)

这是一个不太为人所知的解决方案,但似乎很理想。这可能是正确的“Pythonic”解决方案。文档(如链接所示)说:“一个简单的对象子类,可以提供对其命名空间的属性访问以及有意义的repr。与对象不同,使用SimpleNamespace,您可以添加和删除属性。如果使用关键字参数初始化SimpleNamespace对象,则会直接将它们添加到底层命名空间中。”但是当@pillmuncher的解决方案看起来如此轻便时,我无法抑制自己对导入该解决方案感到犹豫的原因。 - NeilG

1

来自Python IAQ

As of Python 2.3 you can use the syntax

dict(a=1, b=2, c=3, dee=4)

which is good enough as far as I'm concerned. Before Python 2.3 I used the one-line function

def Dict(**dict): return dict

1

对于大多数情况来说,一个简单的字典就足够了。

如果你正在寻找一个类似于你所指示的文字情况的API,你仍然可以使用字典并简单地覆盖特殊的__getattr__函数:

class CustomDict(dict):
    def __getattr__(self, name):
        return self[name]

p = CustomDict(user='James', location='Earth')
print p.user
print p.location

注意:请记住,与命名元组相反,字段不会得到验证,您需要确保您的参数是合理的。例如,p['def'] = 'something' 这样的参数在字典中是被容忍的,但您将无法通过 p.def 访问它们。


1

我认为在JavaScript中使用对象字面量有两个原因:

  1. 在JavaScript中,对象是创建具有字符串索引属性的“事物”的唯一方法。在Python中,如另一个答案中所述,字典类型可以实现这一点。

  2. JavaScript的对象系统是基于原型的。在JavaScript中没有类的概念(尽管它将在未来版本中出现)- 对象具有原型对象而不是类。因此,通过文字直接创建对象是自然的,因为所有对象只需要内置的根对象作为原型。在Python中,每个对象都有一个类 - 你应该使用对象来处理多个实例的情况,而不仅仅是一次性的。

因此,对象字面量并不是Pythonic的,但它们是JavaScripthonic的。


4
不,那是一种常与DOM琴混合的苏打水。 - Paul D. Waite
2
我不同意- 在JavaScript中确实只有一种创建类似字典的对象的方法,但可以通过obj.propobj["prop"]语法访问它,这是一个相当不错的想法,如果需要的话,Python也可以实现(下面是Unode的回答)。此外-在Python中,类也是对象,因此偶尔将它们用作一次性属性容器可能是有用的(处理需要在传递某些内容时实施__getattr__的API时)。 - cji
1
@cji:关于潜在有用性的观点很好。我认为“Pythonic”是指“在实现受其他语言启发的自定义结构之前使用语言内置功能”,所以我不会批评它,但我仍然坚持认为像literal这样的类并不特别Pythonic。顺便问一下,您有没有任何想要传递具有__getattr__实现的内容的API示例,其中您将传递一次性内容?我在Python方面经验不太丰富,所以我还没有遇到过这样的情况。 - Paul D. Waite
3
我熟悉的一个特定用例是测试Django视图。在测试环境中,通常需要为一两个特定视图“伪造”一些对象。我经常看到那里有类似DummyObject之类的类 - 当然没问题,但有时候你很累或者很匆忙,你会感激每一行代码。你知道明天你将不得不重写它,但是今晚你只想让这些该死的测试通过并入睡 :) - cji
1
@jrh:JavaScript确实有类型,尽管在这种情况下它们可能不是非常相关。我对JavaScript或Python的细节了解不够,无法评估您的陈述的准确性。 - Paul D. Waite
显示剩余3条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接