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 这样的静态分析器)
  • 处理所有场景所需的样板代码量(例如,作为关键字传递的位置参数;检查是否存在所有必需的参数)使得代码减少不存在并且重复了库存子例程调用代码