Python 将太多参数传递给构造函数是否被视为反模式?

Python 将太多参数传递给构造函数是否被视为反模式?,python,factory-boy,Python,Factory Boy,我正在考虑使用factory_boy库进行API测试。文档中的一个示例是: class UserFactory(factory.Factory): class Meta: model = base.User first_name = "John" last_name = "Doe" 为此,我们需要将first\u name、last\u name等作为参数传递给base.User()类的\uuuu init\uuuuu()方法。但是,如果您有许多参数,

我正在考虑使用factory_boy库进行API测试。文档中的一个示例是:

class UserFactory(factory.Factory):
    class Meta:
        model = base.User

    first_name = "John"
    last_name = "Doe"
为此,我们需要将
first\u name
last\u name
等作为参数传递给
base.User()类的
\uuuu init\uuuuu()方法。但是,如果您有许多参数,则会导致以下情况:

class User(object):

    GENDER_MALE = 'mr'
    GENDER_FEMALE = 'ms'

    def __init__(self, title=None, first_name=None, last_name=None, is_guest=None,
             company_name=None, mobile=None, landline=None, email=None, password=None,
             fax=None, wants_sms_notification=None, wants_email_notification=None,
             wants_newsletter=None, street_address=None):

        self. title = title
        self.first_name = first_name
        self.last_name = last_name
        self.company_name = company_name
        self.mobile = mobile
        self.landline = landline
        self.email = email
        self.password = password
        self.fax = fax
        self.is_guest = is_guest
        self.wants_sms_notification = wants_sms_notification
        self.wants_email_notification = wants_email_notification
        self.wants_newsletter = wants_newsletter
        self.company_name = company_name
        self.street_address = street_address
现在的问题是,这种构造被认为是反模式的吗?如果是,我有什么选择


谢谢

是的,太多的参数是反模式的(正如RObert C.Martin在Clean代码中所述)

为了避免这种情况,您有两种设计方法:

这两者在意图上都是相似的,我们慢慢地建立一个中间对象,然后在一个步骤中创建我们的目标对象


我建议使用构建器模式,它使代码易于阅读。

您可以将
\uuuu init\uuuu
方法的关键字参数打包成一个dict,并使用
setattr
动态设置它们:

class User(object):
    GENDER_MALE = 'mr'
    GENDER_FEMALE = 'ms'
    def __init__(self, **kwargs):
        valid_keys = ["title", "first_name", "last_name", "is_guest", "company_name", "mobile", "landline", "email", "password", "fax", "wants_sms_notification", "wants_email_notification", "wants_newsletter","street_address"]
        for key in valid_keys:
            setattr(self, key, kwargs.get(key))

x = User(first_name="Kevin", password="hunter2")
print(x.first_name, x.password, x.mobile)

但是,这有一个缺点,即不允许您在不命名参数的情况下提供参数-
x=User(“Mr”,“Kevin”)
适用于您的原始代码,但不适用于此代码。

最大的风险是,如果您有大量的位置参数,然后不知道哪个是哪个。。关键字参数肯定会让这更好

正如其他人所建议的,builder模式也可以很好地工作。 如果有大量字段,还可以做一些更通用的操作,如:

class Builder(object):

    def __init__(self, cls):
        self.attrs = {}
        self.cls = cls

    def __getattr__(self, name):
        if name[0:3] == 'set':
            def setter(x):
                field_name = name[3].lower() + name[4:]
                self.attrs[field_name] = x
                return self
            return setter
        else:
            return super(UserBuilder, self).__getattribute__(name)

    def build(self):
        return self.cls(**self.attrs)

class User(object):

    def __str__(self):
        return "%s %s" % (self.firstName, self.lastName)

    def __init__(self, **kwargs):
        # TODO: validate fields
        for key in kwargs:
            setattr(self, key, kwargs[key])

    @classmethod
    def builder(cls):
        return Builder(cls)

print (User.builder()
  .setFirstName('John')
  .setLastName('Doe')
  .build()) # prints John Doe
在Python 3.7中,添加了(在中指定)。这允许您只在构造函数中写入这些参数一次,而不在构造函数中再次写入,因为构造函数是为您制作的:

from dataclasses import dataclass

@dataclass
class User:
    title: str = None
    first_name: str = None
    last_name: str = None
    company_name: str = None
    mobile: str = None
    landline: str = None
    email: str = None
    password: str = None
    fax: str = None
    is_guest: bool = True
    wants_sms_notification: bool = False
    wants_email_notification: bool = False
    wants_newsletter: bool = False
    street_address: str = None
它还向类以及其他一些类添加了一个
\uuuu repr\uuu
。请注意,在Python3中不再需要显式继承
对象
,因为默认情况下所有类都是新样式的类

但也有一些缺点。它在类定义上稍慢一些(因为需要生成这些方法)。您需要设置默认值或添加,否则会出现名称错误。如果您想使用可变对象(如列表)作为默认参数,则需要使用
dataclass.field(default\u factory=list)
(通常不鼓励编写,例如
def(x=[])
,但在这里它实际上引发了一个异常)


这在构造函数中必须包含所有这些参数的情况下非常有用,因为它们都属于同一个对象,并且不能提取到子对象,例如。

如果重载不是问题,那么python中的每个类都可以简化为一个方法,我们可以将其称为doIt(…)。与所有事情一样,做事要适度。用无数参数重载任何方法都是不好的做法。相反,允许用户在相关数据的小块中构建对象。这更符合逻辑。在您的情况下,您可以将调用分为名称、通信,或者其他。

您不需要类上的
\uuu init\uuu()
方法来使用
factory\u boy
,除非它自v2.4.1以来发生了更改。这不是一个答案,但您的问题可能表明用户类正在尝试做太多的事情-理想情况下,如果通过运营商pidgeon添加通知,您就不需要修改用户类。这也可能是它的数据模型可以简化的一个标志-你可以有一个电话/传真号码列表,而不是
手机
固定电话
传真
字段,让来宾用户成为子类而不是字段,等等。也许这是迂腐的,但是在python中,短语位置参数不是关键字参数。除了强制性的
self
之外,此
\uuuu init\uuu
方法中没有位置参数。没有人需要担心
mobile
是否进入位置10或14,因为它是由关键字指定的。您也可以使用
def\uuuu init\uuuuself(self,**kwargs):self.name=kwargs.get('name',None)
如果您暂时考虑一下是否需要在表中包含所有这些联系人字段,并且可能存在维护问题,代码有什么问题?这些字段是可选字段,因此您的调用将根据需要填充尽可能少的字段(本质明确设置为不允许)。目的很清楚。模式可能会使代码变得不那么清晰,仍然无助于表维护。kwargs方法很好,但会隐藏字段名(如果传入firstname会发生什么情况)。那么“本质”与构造函数的“参数对象”是一样的吗?第二个链接非常无用,因为它包含了大量Java特定的跳跃。维基百科中有一个很好的代码示例,您介意在这里详细说明为什么它是反模式吗?我感谢你的链接,但我找不到一点,它是一个反模式,为什么。在一个类似的线程中,大多数链接答案对链接都有帮助,但在链接死亡的情况下,摘录也会有帮助。我更喜欢这个解决方案,但你没有完成代码。有没有办法不使用?+1但为了完整性,如果传递了关键字参数,并且该参数不在有效关键字列表中,则应抛出
TypeError
。直接更改
\u dict\u
,以防止签名中的参数数量像是把婴儿和洗澡水一起扔掉。与后者相比,后者甚至还没有被确定为有效的反模式,这难道不会导致更多的错误代码吗?@Marc我不知道为什么我最初建议直接访问
\uu dict\uu
setattr
无需使用双下划线即可完成相同的操作。但你的反对意见仍然适用。即使是
setattr
对于我的口味来说也有点“怪异”,当涉及到产品质量代码时。。。我宁愿重新设计这个类,使用一个合理的数字