将函数参数分配给`self`

时间:2011-12-30 18:47:43

标签: python variable-assignment boilerplate function-parameter

我注意到我使用的一个常见模式是将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将为您创建boilerplate code

是否有不同的,简短而优雅的方式来执行此任务?

10 个答案:

答案 0 :(得分:9)

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

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

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

为了使这一点更具体,让我们考虑一个函数,它接受一个对象,一个字典和一个名字列表,并将dict中的值分配给列表中的名称。这可以确保您仍然明确指出哪些变量被分配给自己。 (我永远不会建议这个问题的解决方案没有要求明确枚举要分配的变量;这将是一个稀土bug磁铁):

>>> 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行更难以理解。根据具体情况,打字也比一,二,甚至三四线更麻烦。因此,您只能从五参数案例开始获得一定的回报。但这也是你开始接近人类short-term memory capacity(= 7 +/- 2“块”)的限制的完全时刻。因此,在这种情况下,您的代码已经有点难以阅读,这只会让它更具挑战性。

答案 1 :(得分:7)

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

>>>  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,然后你可以编写像{ {1}},然后使用Colors = Enum(Red=0, Green=1, Blue=2)Colors.RedColors.Green。)

如果您将Colors.Blue设置为self.__dict__而不是kwargs,那么找出可能遇到的问题是值得的。

答案 2 :(得分: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

答案 3 :(得分:3)

@ pcperini的回答:

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

>>> s = SomeClass(7,8)
>>> print s.a,s.b,s.c
7 8 2

答案 4 :(得分:1)

我使用locals()__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
>>> 

答案 5 :(得分:1)

装饰魔术!!

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

答案 6 :(得分: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可能会使事情更难以遵循),所以,我只对那个在本地创建的实例使用该方法,并将其丢弃在同一范围内而不将其传递到其他任何地方。)

答案 7 :(得分: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']

答案 8 :(得分: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__方法中可用的所有变量(此时应该只有self,{{ 1}},ab)并设置c的变量等于相同,显然忽略了对self的参数引用(因为self似乎是一个糟糕的决定。)

答案 9 :(得分:0)

@ user3638162答案的一个问题是locals()包含'self'变量。因此,您最终得到了额外的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__)