将参数指定给属性的Python方式?

将参数指定给属性的Python方式?,python,oop,class,methods,Python,Oop,Class,Methods,示例代码如下所示: def assign(self, input=None, output=None, param=None, p1=None, p2=None): if input: self.input = input if output: self.output = output if param: self.param = param if p1: self.p1 = p1 if p2

示例代码如下所示:

def assign(self, input=None, output=None, param=None, p1=None, p2=None):
    if input:
        self.input = input
    if output:
        self.output = output
    if param:
        self.param = param
    if p1:
        self.p1 = p1
    if p2:
        self.p2 = p2
class Foo(object):
    def assign(self, input=None, output=None, param=None, p1=None, p2=None):
        for name in 'input output param p1 p2'.split():
            val = vars()[name]
            if val is not None:
                setattr(self, name, val)

foo = Foo()
foo.assign(p1=123, p2='abc')
>>> def assign(self, input=None, output=None, param=None, p1=None, p2=None):
...     pass
... 

虽然这看起来很清楚,但是如果这个函数有10个参数,它就会受到影响。有没有人想到一种更方便的方法呢?

你可以这样做:

def assign(self,**kwargs):
    for k,v in kwargs.items():
        if v:
           setattr(self,k,v)
这非常简单,适用于许多情况。如果您想维护一组关键字,您将接受这些关键字并在其余部分引发TypeError:

#python2.7 and newer
def assign(self,allowed_kwargs={'foo','bar','baz'},**kwargs):
    if kwargs.keysview() - allowed_kwargs:
        raise TypeError('useful message here...')
    for k in allowed_kwargs:
        setattr(self,k,kwargs[k])
这在某种程度上也是可以检查的,因为用户将看到允许的KWARG集。

显式优于隐式

相比之下,它有许多优点

def assign(self, **kwargs)
  • 这是自我记录
  • 如果将无效参数传递给,则会引发有用的
    TypeError
    分配
  • assign
    可以使用位置参数和关键字参数调用
值得赞扬的是,OP发布的代码是完全明确的,尽管
if
-语句是单调的。为了减少单调, 您可以使用以下内容:

def assign(self, input=None, output=None, param=None, p1=None, p2=None):
    if input:
        self.input = input
    if output:
        self.output = output
    if param:
        self.param = param
    if p1:
        self.p1 = p1
    if p2:
        self.p2 = p2
class Foo(object):
    def assign(self, input=None, output=None, param=None, p1=None, p2=None):
        for name in 'input output param p1 p2'.split():
            val = vars()[name]
            if val is not None:
                setattr(self, name, val)

foo = Foo()
foo.assign(p1=123, p2='abc')
>>> def assign(self, input=None, output=None, param=None, p1=None, p2=None):
...     pass
... 

python的一大优点是它的交互式解释器。当您编写这样的代码时:

def assign(self, input=None, output=None, param=None, p1=None, p2=None):
    if input:
        self.input = input
    if output:
        self.output = output
    if param:
        self.param = param
    if p1:
        self.p1 = p1
    if p2:
        self.p2 = p2
class Foo(object):
    def assign(self, input=None, output=None, param=None, p1=None, p2=None):
        for name in 'input output param p1 p2'.split():
            val = vars()[name]
            if val is not None:
                setattr(self, name, val)

foo = Foo()
foo.assign(p1=123, p2='abc')
>>> def assign(self, input=None, output=None, param=None, p1=None, p2=None):
...     pass
... 
很容易理解您应该如何使用该功能:

>>> help(assign)
Python Library Documentation: function assign in module __main__

assign(self, input=None, output=None, param=None, p1=None, p2=None)
相比之下:

>>> def assign2(self, **kwargs):
...     pass
... 
给出:

>>> help(assign2)
Python Library Documentation: function assign2 in module __main__

assign2(self, **kwargs)
我希望你能理解为什么第一种形式更可取。但是你仍然希望避免把所有东西写两遍(在论点和正文中)

第一个问题是为什么要编写这种性质的代码?我发现这是一种非常常见的情况,我希望一个类有一堆它将携带的属性,但这些属性的集合基本上是固定的。在最常见的情况下,这些属性在对象的生命周期内不会改变;在这种情况下,python有一个内置的帮助程序来实现这一点

>>> import collections
>>> Assignment = collections.namedtuple('Assignment', 'input output param p1 p2')
>>> assign = Assignment(None, None, None, None, None)._replace
>>> assign(p1=10)
Assignment(input=None, output=None, param=None, p1=10, p2=None)
>>> help(Assignment)
Python Library Documentation: class Assignment in module __main__

class Assignment(__builtin__.tuple)
 |  Assignment(input, output, param, p1, p2)
 |  
... SNIP
namedtuple
是常规类,您可以从它们继承来赋予它们特殊的行为。不幸的是,它们是不可变的,如果你碰巧需要它,你将需要另一种技术;但您几乎总是应该首先找到命名元组。否则,我们可以利用其他一些魔法;我们可以得到所有局部变量,在函数开始时,这些局部变量只包括参数:

>>> class Assignable(object):
...     def assign(self, input=None, output=None, param=None, p1=None, p2=None):
...         _kwargs = vars()
...         _kwargs.pop('self')
...         vars(self).update((attr, value) for attr, value in _kwargs.items() if value is not None)
... 
>>> a = Assignable()
>>> vars(a)
{}
>>> a.assign(p1=6)
>>> vars(a)
{'p1': 6}
>>> a.p1
6
help()
文本仍然非常有用

>>> help(a.assign)
Python Library Documentation: method assign in module __main__

assign(self, input=None, output=None, param=None, p1=None, p2=None) method of __main__.Assignable instance

有了mgilson和unutbu的解决方案,编写所有类型的调用的可能性就消失了

在我的以下解决方案中,保留了这种可能性:

class Buu(object):
    def assign(self,
               input=None, output=None,
               param=None,
               p1=None, p2=None,
               **kw):
        for k,v in locals().iteritems():
            if v not in (self,None,kw):
                if v == (None,):
                    setattr(self,k,None)
                else:
                    setattr(self,k,v)
        for k,v in kw.iteritems():
            setattr(self,k,v)

buu = Buu()

buu.assign(None,(None,), p1=[])
print "buu's attributes:",vars(buu)
print

buu.assign(1,p2 = 555, xx = 'extra')
print "buu's attributes:",vars(buu)
结果

buu's attributes: {'output': None, 'p1': []}

buu's attributes: {'p2': 555, 'output': None, 'xx': 'extra', 'p1': [], 'input': 1}
顺便说一句,当编码人员将
None
作为参数的默认参数时,这是因为他预见到在执行过程中没有必要将
None
作为重要参数传递。
因此,我认为将
None
作为真正的重要参数传递是一个错误的问题。
但是,在上面的代码中,通过使用一种约定可以避免这个问题,即如果必须创建一个值为
None
的新属性,则参数应该是
(None,)

如果有人想通过
(无,)

真的吗?

哦,见鬼

这正是我所需要的,谢谢你,米吉尔森!如果v:,请小心使用
。这不会为python中未被视为
True
的任何值设置属性。因此
obj.assign(a=0,b='',c=[])
不会将这些属性中的任何一个分配给
obj
。我认为
如果v不是None:
会更安全。@WarrenWeckesser--这是一个合理的观点。我这样回答是为了镜像OP的原始代码,但很高兴在这里注意到这一点。对于您的解决方案,必须以关键字形式传递参数。在我的回答中,不是这样。顺便说一下,您的代码有一个潜在的问题:如果您想实际分配值
None
?如果这很重要,您可以通过在参数中创建一个sentinel值(例如,
sentinel=object()
,然后
input=sentinel,output=sentinel,
),然后检查
If input不是sentinel
,而不是
If input
。@abarnert我明白了。改成
**kwargs
方式,我现在不需要关心哨兵。没问题。正如我所说,这只是一个“潜在问题”。如果没有有效的false-y值(
None
0
'
[]
,等等),您可能希望存储在属性中,这是非常好的。如果您想重现他的
赋值
函数,我为您发布了一个答案,对于任何不在kwargs
中的
键,您应该引发
TypeError
,而不是跳过它。此外,我认为调用
setattr
而不是
self更为通用(例如,与
\uuuuuuu插槽一起工作),而且可能更为明确。直接触摸dict时,会忽略装饰符。因此,如果一个人使用
@property
@x.setter
来做某事,这将打破这种局面。
setattr
调用修复了这个问题(就像abarnert提到的那样)。@BlackPlanet:是的,我只是想举一个例子,说明什么时候需要
setattr
。还有一些自定义描述符,
\uuuuu setattr\uuuuu
,具有一些相同问题的超类,在C/RPython/Java/.NET中实现的具有类似问题的超类,……请您对我的答案有何看法?@eyquem:我感觉到您试图解决的问题与Firegun提出的问题不同,更复杂。我不相信这个问题的正确性,所以我对解决方案不感兴趣。(我喜欢简单的事情;你的解决方案太复杂了。)然而,我确实喜欢你保留了一个明确的调用签名;因此我更改了答案。添加一个docstring
''参数,默认值为None:input、output、param、p1、p2'
help(assign)
将显示两行
assign(self,**kwargs)
''参数,默认值为None:input、output、para