如何避免 __init__ 中的 "self.x = x; self.y = y; self.z = z" 模式?
How do I avoid the "self.x = x; self.y = y; self.z = z" pattern in __init__?
我看到这样的模式
def __init__(self, x, y, z):
...
self.x = x
self.y = y
self.z = z
...
非常频繁,通常有更多的参数。有没有什么好的方法可以避免这种乏味的重复? class 应该继承自 namedtuple
吗?
显式优于隐式...
当然,你可以让它更简洁:
def __init__(self,a,b,c):
for k,v in locals().items():
if k != "self":
setattr(self,k,v)
更好的问题是:你应该吗?
...如果你想要一个命名的元组,我会推荐使用 namedtuple(记住元组有特定的附加条件)......也许你想要一个 OrderedDict 或者甚至只是一个字典......
免责声明:似乎有几个人关心提出这个解决方案,所以我会提供一个非常明确的免责声明。您不应使用此解决方案。我只提供它作为信息,因此您知道该语言能够做到这一点。剩下的答案只是展示语言能力,而不是支持以这种方式使用它们。
将参数显式复制到属性中并没有什么错。如果 ctor 中的参数太多,有时会被认为是代码异味,也许您应该将这些参数分组到更少的对象中。其他时候,这是必要的,也没有错。 无论如何,明确地做是正确的方法。
但是,既然您问的是如何完成(而不是是否应该完成),那么一种解决方案是:
class A:
def __init__(self, **kwargs):
for key in kwargs:
setattr(self, key, kwargs[key])
a = A(l=1, d=2)
a.l # will return 1
a.d # will return 2
你也可以这样做:
locs = locals()
for arg in inspect.getargspec(self.__init__)[0][1:]:
setattr(self, arg, locs[arg])
当然,您必须导入 inspect
模块。
正如其他人所提到的,重复并不坏,但在某些情况下,namedtuple 可能非常适合解决此类问题。这避免了使用 locals() 或 kwargs,这通常不是一个好主意。
from collections import namedtuple
# declare a new object type with three properties; x y z
# the first arg of namedtuple is a typename
# the second arg is comma-separated or space-separated property names
XYZ = namedtuple("XYZ", "x, y, z")
# create an object of type XYZ. properties are in order
abc = XYZ("one", "two", 3)
print abc.x
print abc.y
print abc.z
我发现它的用途有限,但您可以像继承任何其他对象一样继承 namedtuple(继续示例):
class MySuperXYZ(XYZ):
""" I add a helper function which returns the original properties """
def properties(self):
return self.x, self.y, self.z
abc2 = MySuperXYZ(4, "five", "six")
print abc2.x
print abc2.y
print abc2.z
print abc2.properties()
编辑:
如果你有 python 3.7+ 只需使用 dataclasses
保留签名的装饰器方案:
import decorator
import inspect
import sys
@decorator.decorator
def simple_init(func, self, *args, **kws):
"""
@simple_init
def __init__(self,a,b,...,z)
dosomething()
behaves like
def __init__(self,a,b,...,z)
self.a = a
self.b = b
...
self.z = z
dosomething()
"""
#init_argumentnames_without_self = ['a','b',...,'z']
if sys.version_info.major == 2:
init_argumentnames_without_self = inspect.getargspec(func).args[1:]
else:
init_argumentnames_without_self = tuple(inspect.signature(func).parameters.keys())[1:]
positional_values = args
keyword_values_in_correct_order = tuple(kws[key] for key in init_argumentnames_without_self if key in kws)
attribute_values = positional_values + keyword_values_in_correct_order
for attribute_name,attribute_value in zip(init_argumentnames_without_self,attribute_values):
setattr(self,attribute_name,attribute_value)
# call the original __init__
func(self, *args, **kws)
class Test():
@simple_init
def __init__(self,a,b,c,d=4):
print(self.a,self.b,self.c,self.d)
#prints 1 3 2 4
t = Test(1,c=2,b=3)
#keeps signature
#prints ['self', 'a', 'b', 'c', 'd']
if sys.version_info.major == 2:
print(inspect.getargspec(Test.__init__).args)
else:
print(inspect.signature(Test.__init__))
为了扩展 gruszczy
的回答,我使用了如下模式:
class X:
x = None
y = None
z = None
def __init__(self, **kwargs):
for (k, v) in kwargs.items():
if hasattr(self, k):
setattr(self, k, v)
else:
raise TypeError('Unknown keyword argument: {:s}'.format(k))
我喜欢这种方法,因为它:
- 避免重复
- 构造对象时可以防止错别字
- 与 subclassing 配合使用效果很好(可以
super().__init(...)
)
- 允许在 class 级别(它们所属的级别)而不是
X.__init__
级别记录属性
在 Python 3.6 之前,这无法控制属性的设置顺序,如果某些属性是具有访问其他属性的 setter 的属性,这可能会成为问题。
它可能会有所改进,但我是自己代码的唯一用户,所以我不担心任何形式的输入卫生问题。也许 AttributeError
会更合适。
这是一个没有任何额外导入的解决方案。
辅助函数
一个小的辅助函数使它更方便和可重复使用:
def auto_init(local_name_space):
"""Set instance attributes from arguments.
"""
self = local_name_space.pop('self')
for name, value in local_name_space.items():
setattr(self, name, value)
申请
你需要用locals()
调用它:
class A:
def __init__(self, x, y, z):
auto_init(locals())
测试
a = A(1, 2, 3)
print(a.__dict__)
输出:
{'y': 2, 'z': 3, 'x': 1}
不改变locals()
如果您不想更改 locals()
使用此版本:
def auto_init(local_name_space):
"""Set instance attributes from arguments.
"""
for name, value in local_name_space.items():
if name != 'self':
setattr(local_name_space['self'], name, value)
我的 0.02 美元。它非常接近 Joran Beasley 的答案,但更优雅:
def __init__(self, a, b, c, d, e, f):
vars(self).update((k, v) for k, v in locals().items() if v is not self)
此外,Mike Müller 的答案(最符合我口味的答案)可以用这种技术减少:
def auto_init(ns):
self = ns.pop('self')
vars(self).update(ns)
而刚刚从您的 __init__
调用 auto_init(locals())
这是在 Python 做事的自然方式。不要试图发明更聪明的东西,它会导致你的团队中没有人理解的过于聪明的代码。如果你想成为一个团队合作者,那么就继续这样写吧。
处理此问题(并避免许多其他样板文件)的有趣库是 attrs。例如,您的示例可以简化为(假设 class 被称为 MyClass
):
import attr
@attr.s
class MyClass:
x = attr.ib()
y = attr.ib()
z = attr.ib()
您甚至不再需要 __init__
方法,除非它还执行其他操作。这里是 a nice introduction by Glyph Lefkowitz.
Python 3.7 起
在 Python 3.7 中,您可以(滥用)使用 dataclass
装饰器,可从 dataclasses
模块获得。来自文档:
This module provides a decorator and functions for automatically adding generated special methods such as __init__()
and __repr__()
to user-defined classes. It was originally described in PEP 557.
The member variables to use in these generated methods are defined using PEP 526 type annotations. For example this code:
@dataclass
class InventoryItem:
'''Class for keeping track of an item in inventory.'''
name: str
unit_price: float
quantity_on_hand: int = 0
def total_cost(self) -> float:
return self.unit_price * self.quantity_on_hand
Will add, among other things, a __init__()
that looks like:
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
Note that this method is automatically added to the class: it is not directly specified in the InventoryItem definition shown above.
如果您的class又大又复杂,可能不适合使用dataclass
。我在 Python 3.7.0 发布之日写这篇文章,因此使用模式尚未完全确定。
我看到这样的模式
def __init__(self, x, y, z):
...
self.x = x
self.y = y
self.z = z
...
非常频繁,通常有更多的参数。有没有什么好的方法可以避免这种乏味的重复? class 应该继承自 namedtuple
吗?
显式优于隐式... 当然,你可以让它更简洁:
def __init__(self,a,b,c):
for k,v in locals().items():
if k != "self":
setattr(self,k,v)
更好的问题是:你应该吗?
...如果你想要一个命名的元组,我会推荐使用 namedtuple(记住元组有特定的附加条件)......也许你想要一个 OrderedDict 或者甚至只是一个字典......
免责声明:似乎有几个人关心提出这个解决方案,所以我会提供一个非常明确的免责声明。您不应使用此解决方案。我只提供它作为信息,因此您知道该语言能够做到这一点。剩下的答案只是展示语言能力,而不是支持以这种方式使用它们。
将参数显式复制到属性中并没有什么错。如果 ctor 中的参数太多,有时会被认为是代码异味,也许您应该将这些参数分组到更少的对象中。其他时候,这是必要的,也没有错。 无论如何,明确地做是正确的方法。
但是,既然您问的是如何完成(而不是是否应该完成),那么一种解决方案是:
class A:
def __init__(self, **kwargs):
for key in kwargs:
setattr(self, key, kwargs[key])
a = A(l=1, d=2)
a.l # will return 1
a.d # will return 2
你也可以这样做:
locs = locals()
for arg in inspect.getargspec(self.__init__)[0][1:]:
setattr(self, arg, locs[arg])
当然,您必须导入 inspect
模块。
正如其他人所提到的,重复并不坏,但在某些情况下,namedtuple 可能非常适合解决此类问题。这避免了使用 locals() 或 kwargs,这通常不是一个好主意。
from collections import namedtuple
# declare a new object type with three properties; x y z
# the first arg of namedtuple is a typename
# the second arg is comma-separated or space-separated property names
XYZ = namedtuple("XYZ", "x, y, z")
# create an object of type XYZ. properties are in order
abc = XYZ("one", "two", 3)
print abc.x
print abc.y
print abc.z
我发现它的用途有限,但您可以像继承任何其他对象一样继承 namedtuple(继续示例):
class MySuperXYZ(XYZ):
""" I add a helper function which returns the original properties """
def properties(self):
return self.x, self.y, self.z
abc2 = MySuperXYZ(4, "five", "six")
print abc2.x
print abc2.y
print abc2.z
print abc2.properties()
编辑: 如果你有 python 3.7+ 只需使用 dataclasses
保留签名的装饰器方案:
import decorator
import inspect
import sys
@decorator.decorator
def simple_init(func, self, *args, **kws):
"""
@simple_init
def __init__(self,a,b,...,z)
dosomething()
behaves like
def __init__(self,a,b,...,z)
self.a = a
self.b = b
...
self.z = z
dosomething()
"""
#init_argumentnames_without_self = ['a','b',...,'z']
if sys.version_info.major == 2:
init_argumentnames_without_self = inspect.getargspec(func).args[1:]
else:
init_argumentnames_without_self = tuple(inspect.signature(func).parameters.keys())[1:]
positional_values = args
keyword_values_in_correct_order = tuple(kws[key] for key in init_argumentnames_without_self if key in kws)
attribute_values = positional_values + keyword_values_in_correct_order
for attribute_name,attribute_value in zip(init_argumentnames_without_self,attribute_values):
setattr(self,attribute_name,attribute_value)
# call the original __init__
func(self, *args, **kws)
class Test():
@simple_init
def __init__(self,a,b,c,d=4):
print(self.a,self.b,self.c,self.d)
#prints 1 3 2 4
t = Test(1,c=2,b=3)
#keeps signature
#prints ['self', 'a', 'b', 'c', 'd']
if sys.version_info.major == 2:
print(inspect.getargspec(Test.__init__).args)
else:
print(inspect.signature(Test.__init__))
为了扩展 gruszczy
的回答,我使用了如下模式:
class X:
x = None
y = None
z = None
def __init__(self, **kwargs):
for (k, v) in kwargs.items():
if hasattr(self, k):
setattr(self, k, v)
else:
raise TypeError('Unknown keyword argument: {:s}'.format(k))
我喜欢这种方法,因为它:
- 避免重复
- 构造对象时可以防止错别字
- 与 subclassing 配合使用效果很好(可以
super().__init(...)
) - 允许在 class 级别(它们所属的级别)而不是
X.__init__
级别记录属性
在 Python 3.6 之前,这无法控制属性的设置顺序,如果某些属性是具有访问其他属性的 setter 的属性,这可能会成为问题。
它可能会有所改进,但我是自己代码的唯一用户,所以我不担心任何形式的输入卫生问题。也许 AttributeError
会更合适。
这是一个没有任何额外导入的解决方案。
辅助函数
一个小的辅助函数使它更方便和可重复使用:
def auto_init(local_name_space):
"""Set instance attributes from arguments.
"""
self = local_name_space.pop('self')
for name, value in local_name_space.items():
setattr(self, name, value)
申请
你需要用locals()
调用它:
class A:
def __init__(self, x, y, z):
auto_init(locals())
测试
a = A(1, 2, 3)
print(a.__dict__)
输出:
{'y': 2, 'z': 3, 'x': 1}
不改变locals()
如果您不想更改 locals()
使用此版本:
def auto_init(local_name_space):
"""Set instance attributes from arguments.
"""
for name, value in local_name_space.items():
if name != 'self':
setattr(local_name_space['self'], name, value)
我的 0.02 美元。它非常接近 Joran Beasley 的答案,但更优雅:
def __init__(self, a, b, c, d, e, f):
vars(self).update((k, v) for k, v in locals().items() if v is not self)
此外,Mike Müller 的答案(最符合我口味的答案)可以用这种技术减少:
def auto_init(ns):
self = ns.pop('self')
vars(self).update(ns)
而刚刚从您的 __init__
auto_init(locals())
这是在 Python 做事的自然方式。不要试图发明更聪明的东西,它会导致你的团队中没有人理解的过于聪明的代码。如果你想成为一个团队合作者,那么就继续这样写吧。
处理此问题(并避免许多其他样板文件)的有趣库是 attrs。例如,您的示例可以简化为(假设 class 被称为 MyClass
):
import attr
@attr.s
class MyClass:
x = attr.ib()
y = attr.ib()
z = attr.ib()
您甚至不再需要 __init__
方法,除非它还执行其他操作。这里是 a nice introduction by Glyph Lefkowitz.
Python 3.7 起
在 Python 3.7 中,您可以(滥用)使用 dataclass
装饰器,可从 dataclasses
模块获得。来自文档:
This module provides a decorator and functions for automatically adding generated special methods such as
__init__()
and__repr__()
to user-defined classes. It was originally described in PEP 557.The member variables to use in these generated methods are defined using PEP 526 type annotations. For example this code:
@dataclass class InventoryItem: '''Class for keeping track of an item in inventory.''' name: str unit_price: float quantity_on_hand: int = 0 def total_cost(self) -> float: return self.unit_price * self.quantity_on_hand
Will add, among other things, a
__init__()
that looks like: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
Note that this method is automatically added to the class: it is not directly specified in the InventoryItem definition shown above.
如果您的class又大又复杂,可能不适合使用dataclass
。我在 Python 3.7.0 发布之日写这篇文章,因此使用模式尚未完全确定。