27

我注意到我使用的一种常见模式是将SomeClass.__init__()参数分配给self同名的属性。例子:

class SomeClass():
    def __init__(self, a, b, c):
        self.a = a
        self.b = b
        self.c = c

事实上,它必须是其他人的常见任务,并且PyDev有一个快捷方式 - 如果您将光标放在参数列表上并单击Ctrl+1,您将获得为您Assign parameters to attributes创建样板代码的选项。

是否有一种不同的、简短而优雅的方式来执行这项任务?

4

10 回答 10

9

我同情你认为样板代码是一件坏事的感觉。但在这种情况下,我不确定是否有更好的选择。让我们考虑一下可能性。

如果您只讨论几个变量,那么一系列self.x = x行很容易阅读。事实上,我认为从可读性的角度来看,它的明确性使得这种方法更可取。虽然打字可能会有点痛苦,但仅凭这一点还不足以证明一种新的语言结构可能会掩盖真正发生的事情。当然vars(self).update(),在这种情况下,使用恶作剧会比它的价值更令人困惑。

另一方面,如果您将九个、十个或更多参数传递给__init__,则可能无论如何都需要重构。因此,这种担忧实际上只适用于涉及传递 5-8 个参数的情况。现在我可以看到,八行self.x = x代码无论是打字还是阅读都会很烦人;但我不确定 5-8 参数的情况是否足够常见或麻烦到足以证明使用不同方法的合理性。所以我认为,虽然你提出的问题在原则上是好的,但在实践中,还有其他限制性问题使其无关紧要。

为了使这一点更具体,让我们考虑一个函数,它接受一个对象、一个字典和一个名称列表,并将字典中的值分配给列表中的名称。这可以确保您仍然明确哪些变量被分配给 self. (我绝不会建议一个不需要明确枚举要分配的变量的问题的解决方案;那将是一个稀土错误磁铁):

>>> def assign_attributes(obj, localdict, names):
...     for name in names:
...         setattr(obj, name, localdict[name])
...
>>> class SomeClass():
...     def __init__(self, a, b, c):
...         assign_attributes(self, vars(), ['a', 'b', 'c'])

现在,虽然不是非常没有吸引力,但这仍然比一系列简单的self.x = x线条更难弄清楚。而且,根据具体情况,打字也比一行、两行甚至三四行更长、更麻烦。因此,您只能从五参数案例开始获得一定的回报。但这也是您开始接近人类短期记忆容量极限(= 7 +/- 2“块”)的确切时刻。所以在这种情况下,你的代码已经有点难以阅读了,这只会让它更具挑战性。

于 2011-12-30T19:46:40.770 回答
8

你可以这样做,它具有简单的优点:

>>>  class C(object):
    def __init__(self, **kwargs):
        self.__dict__ = dict(kwargs)

这让任何代码创建一个实例C来决定构造后实例的属性是什么,例如:

>>> c = C(a='a', b='b', c='c')
>>> c.a, c.b, c.c
('a', 'b', 'c')

如果您希望所有C对象都具有abc属性,那么这种方法将没有用。

(顺便说一句,这种模式来自 Guido 他自己的坏自我,作为在 Python 中定义枚举问题的通用解决方案。创建一个像上面这样的类Enum,然后你可以编写类似的代码Colors = Enum(Red=0, Green=1, Blue=2),以后使用Colors.RedColors.GreenColors.Blue。)

如果你设置self.__dict__kwargs而不是dict(kwargs).

于 2011-12-30T23:57:38.760 回答
4

@pcperini 回答的 Mod:

>>> class SomeClass():
        def __init__(self, a, b=1, c=2):
            for name,value in vars().items():
                if name != 'self':
                    setattr(self,name,value)

>>> s = SomeClass(7,8)
>>> print s.a,s.b,s.c
7 8 2
于 2011-12-30T19:30:43.263 回答
3

您的特定情况也可以使用namedtuple处理:

>>> from collections import namedtuple
>>> SomeClass = namedtuple("SomeClass", "a b c")
>>> sc = SomeClass(1, "x", 200)
>>> print sc
SomeClass(a=1, b='x', c=200)
>>> print sc.a, sc.b, sc.c
1 x 200
于 2011-12-30T23:05:32.317 回答
2

装饰魔法!!

>>> class SomeClass():
        @ArgsToSelf
        def __init__(a, b=1, c=2, d=4, e=5):
            pass

>>> s=SomeClass(6,b=7,d=8)
>>> print s.a,s.b,s.c,s.d,s.e
6 7 2 8 5

在定义时:

>>> import inspect
>>> def ArgsToSelf(f):
    def act(self, *args, **kwargs):
        arg_names,_,_,defaults = inspect.getargspec(f)
        defaults=list(defaults)
        for arg in args:
            setattr(self, arg_names.pop(0),arg)
        for arg_name,arg in kwargs.iteritems():
            setattr(self, arg_name,arg)
            defaults.pop(arg_names.index(arg_name))
            arg_names.remove(arg_name)
        for arg_name,arg in zip(arg_names,defaults):
            setattr(self, arg_name,arg)
        return f(*args, **kwargs)
    return act

当然,你可以定义这个装饰器一次并在整个项目中使用它。
此外,这个装饰器适用于任何对象函数,不仅仅是__init__.

于 2011-12-31T10:43:43.197 回答
1

您可以通过 setattr() 来完成,例如:

[setattr(self, key, value) for key, value in kwargs.items()]

不是很漂亮,但可以节省一些空间:)

所以,你会得到:

  kwargs = { 'd':1, 'e': 2, 'z': 3, }

  class P():
     def __init__(self, **kwargs):
        [setattr(self, key, value) for key, value in kwargs.items()]

  x = P(**kwargs)
  dir(x)
  ['__doc__', '__init__', '__module__', 'd', 'e', 'z']
于 2011-12-30T19:03:59.003 回答
1

对于那个简单的用例,我必须说我喜欢明确地放置东西(使用 PyDev 中的 Ctrl+1),但有时我最终也会使用一堆实现,但是使用一个类,其中接受的属性是从预先声明的属性创建的在课堂上,以便我知道预期的内容(我更喜欢它而不是命名元组,因为我发现它更具可读性——而且它不会混淆静态代码分析或代码完成)。

我已经为它准备了一个食谱: http: //code.activestate.com/recipes/577999-bunch-class-created-from-attributes-in-class/

基本思想是您将您的类声明为 Bunch 的子类,它将在实例中创建这些属性(从默认值或从构造函数中传递的值):

class Point(Bunch):
    x = 0
    y = 0

p0 = Point()
assert p0.x == 0
assert p0.y == 0

p1 = Point(x=10, y=20)
assert p1.x == 10
assert p1.y == 20

此外,Alex Martelli 还提供了一堆实现:http ://code.activestate.com/recipes/52308-the-simple-but-handy-collector-of-a-bunch-of-named/的想法是更新来自参数的实例,但这会混淆静态代码分析(并且 IMO 会使事情更难遵循)所以,我只会将该方法用于在本地创建并在同一范围内丢弃而不将其传递到任何地方的实例别的)。

于 2011-12-31T18:08:15.713 回答
1

我使用locals()and为自己解决了这个问题__dict__

>>> class Test:
...     def __init__(self, a, b, c):
...         l = locals()
...         for key in l:
...             self.__dict__[key] = l[key]
... 
>>> t = Test(1, 2, 3)
>>> t.a
1
>>> 
于 2016-05-19T09:42:05.297 回答
0

免责声明

不要使用这个:我只是试图创建最接近 OP 最初意图的答案。正如评论中所指出的,这完全依赖于未定义的行为,并且明确禁止修改符号表。

但它确实有效,并且已经在极其基本的情况下进行了测试。

解决方案

class SomeClass():
    def __init__(self, a, b, c):
        vars(self).update(dict((k,v) for k,v in vars().iteritems() if (k != 'self')))

sc = SomeClass(1, 2, 3)
# sc.a == 1
# sc.b == 2
# sc.c == 3

使用vars()内置函数,此代码段遍历__init__方法中可用的所有变量(此时应该只是selfabc)并且设置的self变量等于相同,显然忽略了参数引用to self(因为这self.self似乎是一个糟糕的决定。)

于 2011-12-30T19:15:13.513 回答
0

@user3638162 的答案的问题之一是locals()包含“自我”变量。因此,您最终会得到一个额外的self.self. 如果一个人不介意额外的自我,那么这个解决方案可以简单地

class X:
    def __init__(self, a, b, c):
        self.__dict__.update(locals())

x = X(1, 2, 3)
print(x.a, x.__dict__)

self可以在施工后拆除del self.__dict__['self']

或者,可以使用 Python3 中引入的字典推导在构造过程中删除 self

class X:
    def __init__(self, a, b, c):
        self.__dict__.update(l for l in locals().items() if l[0] != 'self')

x = X(1, 2, 3)
print(x.a, x.__dict__)
于 2017-12-05T09:30:00.390 回答