18

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

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

    first_name = "John"
    last_name = "Doe"

为此,我们需要将first_name,last_name等作为参数传递__init__()base.User() class. 但是,如果你有很多参数,这会导致类似:

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

现在的问题是,这种结构是否被认为是反模式,如果是,我有什么替代方案?

谢谢

4

5 回答 5

15

在 Python 3.7 中,添加了数据类(在PEP557中指定)。这允许您在构造函数中只编写一次这些参数,而不是再次编写这些参数,因为构造函数是为您创建的:

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

它还添加了一个__repr__类以及其他一些类。请注意,object在 Python 3 中不再需要显式继承 from,因为默认情况下所有类都是新样式的类。

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

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

于 2019-01-01T16:14:41.680 回答
12

您可以将方法的关键字参数打包__init__到一个字典中,并使用以下命令动态设置它们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")适用于您的原始代码,但不适用于此代码。

于 2015-06-02T15:08:47.667 回答
2

是的,太多的参数是一种反模式(如 RObert C. Martin 在 Clean Code 中所述)

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

本质模式

流畅的界面/构建器模式

它们的意图相似,因为我们慢慢地建立一个中间对象,然后一步创建我们的目标对象。

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

于 2015-06-02T14:58:45.580 回答
1

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

正如其他人所建议的那样,构建器模式也可以很好地工作。如果你有大量的字段,你也可以做一些更通用的事情,像这样:

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
于 2015-06-02T15:31:04.133 回答
0

如果重载不是问题,那么 python 中的每个类都可以简化为一个方法,我们可以称之为 doIt (....)。与所有事情一样,最好适度地做事。用无数参数重载任何方法都是不好的做法。相反,允许用户以一口大小的相关数据块来构建对象。这更合乎逻辑。在您的情况下,您可以将呼叫拆分为姓名、通信,也许还有其他。

于 2020-07-17T18:03:45.247 回答