遍历构造函数的参数

Posted

技术标签:

【中文标题】遍历构造函数的参数【英文标题】:Iterating through constructor's arguments 【发布时间】:2011-10-09 06:52:21 【问题描述】:

我经常发现自己编写这样的类构造函数:

class foo:
    def __init__(self, arg1, arg2, arg3):
        self.arg1 = arg1
        self.arg2 = arg2
        self.arg3 = arg3

如果参数(和类属性)的数量变多,这显然会变得很痛苦。我正在寻找最 Pythonic 的方式来列表并相应地分配属性。我正在使用 Python 2.7,所以理想情况下我正在寻求该版本的帮助。

【问题讨论】:

就是这样。如果你在任何函数中有太多参数,那你就错了。 你说起来容易 :) 我必须处理从 20 多列 CSV 文件中提取的数据。我需要从多个来源构造对象的方法。关键是,根据要求,您可能需要大量参数。 做一个,一个列表。然后,如果您想更轻松地访问,可以将其与关键字列表一起压缩以形成字典。 @Cat Plus Plus。我不确定这是否准确。 Hitchhiker's Guide to Python 说,过于依赖全局(或自身)变量来控制方法是结构不良的标志。又名“大量使用全局状态或上下文:而不是显式地将(高度、宽度、类型、木材)传递给彼此......”docs.python-guide.org/en/latest/writing/structure @BurningKrome 这句话与此无关。实例状态也不是全局状态,我不知道你是如何从“不使用全局状态”推断“self 不好”的。 【参考方案1】:

最 Pythonic 的方式是您已经编写的。如果您愿意要求命名参数,您可以这样做:

class foo:
    def __init__(self, **kwargs):
        vars(self).update(kwargs)

【讨论】:

我建议vars(self).update(kwargs) 更容易阅读。 YMMV。 @TimS1234:感谢您的建议。你每天都会学到新东西。【参考方案2】:

提供的答案依赖于 *vargs**kargs 参数,如果您想限制为具有特定名称的特定参数集,这可能根本不方便:您必须手动进行所有检查。

这是一个装饰器,它将方法提供的参数存储在其绑定实例中,作为具有各自名称的属性。

import inspect
import functools

def store_args(method):
    """Stores provided method args as instance attributes."""
    argspec = inspect.getargspec(method)
    defaults = dict(zip( argspec.args[-len(argspec.defaults):], argspec.defaults ))
    arg_names = argspec.args[1:]
    @functools.wraps(method)
    def wrapper(*positional_args, **keyword_args):
        self = positional_args[0]
        # Get default arg values
        args = defaults.copy()
        # Add provided arg values
        list(map( args.update, ( zip(arg_names, positional_args[1:]), keyword_args.items() ) ))
        # Store values in instance as attributes
        self.__dict__.update(args)
        return method(*positional_args, **keyword_args)

    return wrapper

然后你可以像这样使用它:

class A:
    @store_args
    def __init__(self, a, b, c=3, d=4, e=5):
        pass

a = A(1,2)
print(a.a, a.b, a.c, a.d, a.e)

结果将是 Python3.x 上的 1 2 3 4 5 或 Python2.x 上的 (1, 2, 3, 4, 5)

【讨论】:

如果您添加此行 if not argspec.defaults is None 您不需要以参数的默认值结尾。 else 语句应该是defaults = 【参考方案3】:

对于位置参数和关键字参数,您都可以这样做:

class Foo(object):
    def __init__(self, *args, **kwargs):
        for arg in args:
            print arg
        for kwarg in kwargs:
            print kwarg

* 将位置参数打包到一个元组中,** 关键字参数打包到一个字典中:

foo = Foo(1, 2, 3, a=4, b=5, c=6) // args = (1, 2, 3), kwargs = 'a' : 4, ...

【讨论】:

你甚至可以通过这种方式从位置参数赋值,只要它们是字符串,比如for arg in args: setattr(self, arg, arg) 我猜位置参数在他的情况下不起作用 - 他需要分配对象的属性。不知道属性名怎么办? @agf,让属性的字符串值等于其名称的意义何在? 打败了我,但如果你看这个问题,那就是他问的问题(尽管他的意思值得怀疑),所以我提供了答案。 位置参数可用于设置实例属性,只要这些属性有隐式顺序(参见例如 range 函数)。不过,并不是说我会推荐这个。关键字参数是这里的正确选择。【参考方案4】:
class foo:
    def __init__(self, **kwargs):
        for arg_name, arg_value in kwargs.items():
            setattr(self, arg_name, arg_value)

这需要命名参数:

obj = foo(arg1 = 1, arg2 = 2)

【讨论】:

dict.iteritems()dict.viewitems() 如果您不想复制。【参考方案5】:

这个怎么样?

class foo:
    def __init__(self, arg1, arg2, arg3):
        for _prop in dir():
            setattr(self, _prop, locals()[_prop])

这使用内置的 python dir 函数来迭代 all 局部变量。 它具有创建无关的自我引用的轻微副作用,但如果你真的想要,你可以过滤它。 此外,如果您要在 dir() 调用之前声明任何其他本地变量,它们也会被添加为构造对象的属性。

【讨论】:

【参考方案6】:

对显式变量名进行迭代怎么样?

class foo:
    def __init__(self, arg1, arg2, arg3):
        for arg_name in 'arg1,arg2,arg3'.split(','):
            setattr(self, arg_name, locals()[arg_name])

f = foo(5,'six', 7)

结果

print vars(f)

'arg1': 5, 'arg2': 'six', 'arg3': 7

我的建议类似于@peepsalot,除了它更明确并且不使用dir(),根据documentation

它的详细行为可能会因版本而异

【讨论】:

【参考方案7】:

正如其他人所指出的,在大多数情况下,您可能应该坚持原来的“pythonic”方法。

但是,如果你真的想走完整个九码,这里有一些代码可以巧妙地处理 args,如果需要,可以使用关键字 args,并避免样板重复:

def convert_all_args_to_attribs(self, class_locals):
    class_locals.pop('self')
    if 'kwargs' in class_locals:
        vars(self).update(class_locals.pop('kwargs'))
    vars(self).update(class_locals)

class FooCls:
    def __init__(self, foo, bar):
        convert_all_args_to_attribs(self, locals())

class FooClsWithKeywords:
    def __init__(self, foo, bar, **kwargs):
        convert_all_args_to_attribs(self, locals())

f1 = FooCls(1,2)
f2 = FooClsWithKeywords(3,4, cheese='stilton')


print vars(f1) #'foo': 1, 'bar': 2
print vars(f2) #'cheese': 'stilton', 'foo': 3, 'bar': 4

【讨论】:

【参考方案8】:

对于 python >= 3.7,请查看 @dataclass 类装饰器。 除此之外,它还可以处理您的__init__ 样板编码问题。

来自doc:

@dataclass
class InventoryItem:
    name: str
    unit_price: float
    quantity_on_hand: int = 0

会像这样自动添加__init__

def __init__(self, name: str, unit_price: float, quantity_on_hand: int=0):
    self.name = name
    self.unit_price = unit_price
    self.quantity_on_hand = quantity_on_hand

【讨论】:

【参考方案9】:

*args 是一个序列,因此您可以使用索引访问项目:

def __init__(self, *args):
        if args:       
            self.arg1 = args[0]    
            self.arg2 = args[1]    
            self.arg3 = args[2]    
            ...  

或者你可以遍历所有这些

for arg in args:
   #do assignments

【讨论】:

这是他试图避免的代码的两倍多!此外,您的参数随后被硬编码为 arg1 等。

以上是关于遍历构造函数的参数的主要内容,如果未能解决你的问题,请参考以下文章

什么是日期对象的构造函数

通过Java对象来遍历成员方法,成员变量,构造函数

java 反射 如何得到构造函数的参数列表

Kotlin类的初始化 ② ( 主构造函数 | 主构造函数定义临时变量 | 主构造函数中定义成员属性 | 次构造函数 | 构造函数默认参数 )

Kotlin类与对象 ② ( 主构造函数 | 主构造函数定义临时变量 | 主构造函数中定义成员属性 | 次构造函数 | 构造函数默认参数 )

java怎么调用带参数构造函数