遍历构造函数的参数
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
等。以上是关于遍历构造函数的参数的主要内容,如果未能解决你的问题,请参考以下文章
Kotlin类的初始化 ② ( 主构造函数 | 主构造函数定义临时变量 | 主构造函数中定义成员属性 | 次构造函数 | 构造函数默认参数 )
Kotlin类与对象 ② ( 主构造函数 | 主构造函数定义临时变量 | 主构造函数中定义成员属性 | 次构造函数 | 构造函数默认参数 )