【问题标题】:Pythonic way to assign the parameter into attribute?将参数分配给属性的 Pythonic 方式?
【发布时间】:2013-02-25 10:18:44
【问题描述】:

示例代码如下:

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

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

【问题讨论】:

  • 附带说明,您的代码存在一个潜在问题:如果您想实际分配值None,该怎么办?如果这很重要,您可以通过创建一个标记值来解决此问题(例如,sentinel = object(),然后在参数中使用input=sentinel, output=sentinel, …,然后检查if input is not sentinel 而不是if input)。
  • @abarnert 我明白了。改成**kwargs的方式,我现在不用管哨兵了。
  • 没问题。正如我所说,这只是一个“潜在问题”。如果没有您想要存储在属性中的有效 false-y 值(None0''[] 等),这很好。
  • 我给你发了答案

标签: python oop class methods


【解决方案1】:

你可以这样做:

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

这在某种程度上也是可检查的,因为用户将看到一组允许的 kwargs。

【讨论】:

  • 这正是我所需要的,谢谢 mgilson!
  • 小心if v:。这不会为在 python 中不被视为True 的任何值设置属性。所以obj.assign(a=0, b='', c=[]) 不会将这些属性中的任何一个分配给obj。我认为if v is not None: 会更安全。
  • @WarrenWeckesser -- 这是一个合理的观点。我以这种方式回答以反映 OP 的原始代码,但很高兴在这里注明这一点。
  • 使用您的解决方案,必须以关键字形式传递参数。在我的回答中,不是。
【解决方案2】:

显式优于隐式

def assign(self, input=None, output=None, param=None, p1=None, p2=None):

有很多优点

def assign(self, **kwargs)
  • 它是自我记录的
  • 如果一个无效参数被传递给一个有用的TypeError assign
  • assign 可以使用位置参数和关键字参数调用

值得称赞的是,尽管if-statements 是单调的,但 OP 发布的代码是完全明确的。为了减少单调, 你可以使用这样的东西:

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

【讨论】:

  • 如果你想重现他的assign 函数,你应该为任何key not in kwargs 提出TypeError 而不是跳过它。另外,我认为调用setattr 而不是self.__dict__.__update 更通用(例如,与__slots__ 一起使用),并且可能更明确。
  • 直接触摸 dict 时会忽略装饰器。因此,如果一个人要使用 @property@x.setter 来做某事,这会破坏这一点。 setattr 调用解决了这个问题(就像 abarnert 提到的那样。)
  • @BlackVegetable:是的,我只是想举一个例子说明何时需要setattr。还有自定义描述符__setattr__,有一些相同问题的超类,在 C/RPython/Java/.NET 中实现的超类有类似问题,...
  • 请问你觉得我的回答怎么样?
  • @eyquem:我感觉您正在尝试解决一个不同的、更复杂的问题,而不是 Firegun 提出的问题。我不相信这个问题的有效性,所以我对解决方案没有热情。 (我喜欢简单的事情;您的解决方案太复杂了。)但是,我确实喜欢您保留了显式调用签名的事实;因此我改变了答案。
【解决方案3】:

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

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

【讨论】:

  • 添加一个文档字符串'''parameters with default value None are: input, output, param, p1, p2'''help(assign)将显示两行assign(self, **kwargs)'''parameters with default value None are: input, output, param, p1, p2'''
  • 我仔细研究了您使用命名元组的提议。我不认为它在开始时看起来很有趣:1) 命名元组实例具有属性 _fields, _make, _asdict 除了编码器肯定希望作为其所需的唯一属性的那些实例,它们是实例中记录的多余信息。 2) 唯一有用的属性_replace 有一个可能是缺点的行为:它返回一个具有不同身份的对象。在某些情况下,这可能是不需要的。这一点在您提到命名元组是不可变的时所说的内容已涵盖。
  • 3) 第 2 点是关于更改现有属性的值。这个是关于添加新属性的:创建命名元组后不能添加新属性,命名元组的__dict__不能更改。它引起了命名元组的兴趣:“命名元组实例没有每个实例的字典,因此它们是轻量级的,并且不需要比常规元组更多的内存。” 但这使得它们不太适合其他任务,而不是它们的设计目的。我的意见。
  • 但是,您的回答让我想到了不同的主题,我对您提出的其他非常有趣的回答投了赞成票(前任本地人)。
  • 接受三个以上参数(self 除外)的方法经常受到怀疑。不同的参数通常属于一起,作为一组相关的属性。在 python 和其他语言中,属性通常分组的方式是使用类。传递一个轻量级的实例(由namedtuple 返回)是一种特别简洁和描述性的传递数据的方法,即使该数据将应用于另一个不太轻的类。当我说达到namedtuple 时,我并不是说在这个问题中替换拥有assign() 的类,只是替换函数。
【解决方案4】:

使用 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,),从而避免了这个问题。

如果有人想通过(None,) ???
认真的吗?
哎呀!

【讨论】:

    猜你喜欢
    • 2013-12-26
    • 2021-12-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多