Python 2.7: 使用 setattr 设置所有 args/kwargs 属性是好的做法吗?
Python 2.7: Is it good practice to set all args/kwargs attribute with setattr?
在 this 回答中显示了如何将 kwargs/args 自动设置为属性传递给 class __init__
例如,可以这样做:
class Employee(object):
def __init__(self, *initial_data, **kwargs):
# EDIT
self._allowed_attrs = ['name', 'surname', 'salary', 'address', 'phone', 'mail']
for dictionary in initial_data:
for key in dictionary:
if key in self._allowed_attrs: # EDIT
setattr(self, key, dictionary[key])
for key in kwargs:
if key in self._allowed_attrs: # EDIT
setattr(self, key, kwargs[key])
在我的例子中,我已经提前知道我要传递的参数,所以我考虑这个解决方案只是为了减少重复和缩短代码。
这被认为是好的做法吗?
pro/cons 反对手动初始化每个属性的解决方案有哪些?还有其他更好的方法吗?
编辑: 作为第一个 comments/answers(正确地)关注清理参数或列出参数,我认为在这个框架中可以很容易地解决这个问题。
这使得无法弄清楚 class 期望什么样的参数。
如果某人(或几个月后的你)想要在他们的代码中创建一个 Employee
,他们会查看构造函数的参数以查看他们应该传递什么(可能是手动,或者也许 IDE 会自动显示它们)。除了隐藏它之外,您的代码几乎没有实现。
完全可以使用语言自省功能来减少您必须执行的重复和键入操作。
当然,如果您注意正确处理属性甚至清理内容会更好 - 所以最好的办法是为 __init__
方法配备一个装饰器,或者等效于它在 base-class __init__
中将执行所需的所有操作:检查传递的参数是否适合特定的 class,然后使用 setattr 在实例中设置它们的值.
我认为不太神奇的方法是在您的 class 层次结构中有一个约定,将需要的参数声明为 class 属性。
这样,您可以使用那些 class 属性来记录预期的参数及其类型,并将 __init__
签名保持为 *args, **kwargs
并让您的 base-class 初始化句柄全部。
SQLAlchemy Base 模型执行此操作 - 您将 class 属性指定为特殊 "instrumented attributes" 并且在 __init__
.
中调用时会自动分配它们
更简单的方法是:
_sentinel = object()
class Base(object):
def __init__(self, *args, **kwargs):
for attr_name, class_attr in self.__class__.__dict__.items():
if isinstance(class_attr, type) and kwargs.get(attr_name, _sentinel) != _sentinel:
attr_value = kwargs[attr_name]
if not isinstance(attr_value, class_attr):
raise TypeError("Parameter {} is expected to be of type {}".format(attr_name, class_attr))
setattr(self, attr_name, attr_value)
class Person(Base):
name = str
age = int
phonenumber = Phone
...
这将要求 class 的所有参数都作为命名参数传递 - 但所有这些参数都会自动分配给实例属性,它会起作用,可记录且安全。如果你想变得更好,只需定义一些花哨的描述符 class 作为你的 class 属性值。
Question: ... less repetitive and shorter code
您的示例代码需要 9 行和 28 个关键字。
class Employee(object):
def __init__(self, name, surname, salary, address, phone, mail):
self.name = name
self.surname = surename
self.salary = salary
self.address = address
self.phone = phone
self.mail = mail
这个默认值需要 6 行和 19 个关键字。
总结,您的示例需要 more 而不是 "shorter code"。
我在默认中看不到任何"repetitive ... code",所有作业都完成一次。
比较这两行,做的一样。控制哪些参数可以传递:
self._allowed_attrs = ['name', 'surname', 'salary', 'address', 'phone', 'mail']
和
def __init__(self, name, surname, salary, address, phone, mail):
第二个更省力,一气呵成。
不需要 if key in self._allowed_attrs:
,因为 python 会为您完成。
在实际项目中,我会使用这样的东西
class Employee(object):
def __init__(self, person, salary=None):
self.id = unique_id()
self.person = person
self.salary = salary
所有 person
相关数据将汇总在 object person
中。
Conclusion:
For your given example class Employee
i would never use (*args, **kwargs)
.
(*args, **kwargs)
arguments are only usefull if one can't predict which args are passed.
之前的讨论:,
优点:
- 减少代码重复
缺点:
- 可读性较差
- 更难找到成员引用(因此也可能扰乱像
pylint
这样的静态分析器)
- 处理所有场景所需的样板代码量(例如,作为关键字传递的位置参数;检查是否存在所有必需的参数)使得代码减少不存在并且重复了库存子例程调用代码
在 this 回答中显示了如何将 kwargs/args 自动设置为属性传递给 class __init__
例如,可以这样做:
class Employee(object):
def __init__(self, *initial_data, **kwargs):
# EDIT
self._allowed_attrs = ['name', 'surname', 'salary', 'address', 'phone', 'mail']
for dictionary in initial_data:
for key in dictionary:
if key in self._allowed_attrs: # EDIT
setattr(self, key, dictionary[key])
for key in kwargs:
if key in self._allowed_attrs: # EDIT
setattr(self, key, kwargs[key])
在我的例子中,我已经提前知道我要传递的参数,所以我考虑这个解决方案只是为了减少重复和缩短代码。
这被认为是好的做法吗? pro/cons 反对手动初始化每个属性的解决方案有哪些?还有其他更好的方法吗?
编辑: 作为第一个 comments/answers(正确地)关注清理参数或列出参数,我认为在这个框架中可以很容易地解决这个问题。
这使得无法弄清楚 class 期望什么样的参数。
如果某人(或几个月后的你)想要在他们的代码中创建一个 Employee
,他们会查看构造函数的参数以查看他们应该传递什么(可能是手动,或者也许 IDE 会自动显示它们)。除了隐藏它之外,您的代码几乎没有实现。
完全可以使用语言自省功能来减少您必须执行的重复和键入操作。
当然,如果您注意正确处理属性甚至清理内容会更好 - 所以最好的办法是为 __init__
方法配备一个装饰器,或者等效于它在 base-class __init__
中将执行所需的所有操作:检查传递的参数是否适合特定的 class,然后使用 setattr 在实例中设置它们的值.
我认为不太神奇的方法是在您的 class 层次结构中有一个约定,将需要的参数声明为 class 属性。
这样,您可以使用那些 class 属性来记录预期的参数及其类型,并将 __init__
签名保持为 *args, **kwargs
并让您的 base-class 初始化句柄全部。
SQLAlchemy Base 模型执行此操作 - 您将 class 属性指定为特殊 "instrumented attributes" 并且在 __init__
.
更简单的方法是:
_sentinel = object()
class Base(object):
def __init__(self, *args, **kwargs):
for attr_name, class_attr in self.__class__.__dict__.items():
if isinstance(class_attr, type) and kwargs.get(attr_name, _sentinel) != _sentinel:
attr_value = kwargs[attr_name]
if not isinstance(attr_value, class_attr):
raise TypeError("Parameter {} is expected to be of type {}".format(attr_name, class_attr))
setattr(self, attr_name, attr_value)
class Person(Base):
name = str
age = int
phonenumber = Phone
...
这将要求 class 的所有参数都作为命名参数传递 - 但所有这些参数都会自动分配给实例属性,它会起作用,可记录且安全。如果你想变得更好,只需定义一些花哨的描述符 class 作为你的 class 属性值。
Question: ... less repetitive and shorter code
您的示例代码需要 9 行和 28 个关键字。
class Employee(object):
def __init__(self, name, surname, salary, address, phone, mail):
self.name = name
self.surname = surename
self.salary = salary
self.address = address
self.phone = phone
self.mail = mail
这个默认值需要 6 行和 19 个关键字。 总结,您的示例需要 more 而不是 "shorter code"。 我在默认中看不到任何"repetitive ... code",所有作业都完成一次。
比较这两行,做的一样。控制哪些参数可以传递:
self._allowed_attrs = ['name', 'surname', 'salary', 'address', 'phone', 'mail']
和
def __init__(self, name, surname, salary, address, phone, mail):
第二个更省力,一气呵成。
不需要 if key in self._allowed_attrs:
,因为 python 会为您完成。
在实际项目中,我会使用这样的东西
class Employee(object):
def __init__(self, person, salary=None):
self.id = unique_id()
self.person = person
self.salary = salary
所有 person
相关数据将汇总在 object person
中。
Conclusion:
For your given exampleclass Employee
i would never use(*args, **kwargs)
.
(*args, **kwargs)
arguments are only usefull if one can't predict which args are passed.
之前的讨论:
优点:
- 减少代码重复
缺点:
- 可读性较差
- 更难找到成员引用(因此也可能扰乱像
pylint
这样的静态分析器) - 处理所有场景所需的样板代码量(例如,作为关键字传递的位置参数;检查是否存在所有必需的参数)使得代码减少不存在并且重复了库存子例程调用代码