我正在考虑使用factory_boy库进行API测试。文档中的一个例子是:
class UserFactory(factory.Factory):
class Meta:
model = base.User
first_name = "John"
last_name = "Doe"
要实现这一点,我们需要将first_name
,last_name
等作为参数传递给__init__()
的{{1}}方法。但是,如果您有许多参数,则会导致类似:
base.User() class
现在的问题是,这种结构是否被视为反模式,如果是,我有哪些替代方案?
由于
答案 0 :(得分:7)
您可以将__init__
方法的关键字参数打包到一个dict中,并直接更改实例的__dict__
:
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:
self.__dict__[key] = kwargs.get(key)
x = User(first_name="Kevin", password="hunter2")
print x.first_name, x.password, x.mobile
但是,这样做有一个缺点,即如果不提及参数就不允许你提供参数 - x = User("Mr", "Kevin")
可以使用原始代码,但不能使用此代码。
答案 1 :(得分:3)
在Python 3.7中,添加了dataclasses(在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__
。请注意,Python 3不再需要从object
显式继承,因为默认情况下所有类都是新样式类。
尽管有一些缺点。类定义的速度稍慢一些(因为需要生成这些方法)。您需要设置默认值或添加type annotation,否则会出现名称错误。如果要使用可变对象(如列表)作为默认参数,则需要使用dataclass.field(default_factory=list)
(通常不建议编写def f(x=[])
,但实际上会引发异常)。
答案 2 :(得分:2)
是的,有太多的论据是反模式(如RObert C. Martin的清洁法典中所述)
为避免这种情况,您有两种设计方法:
The fluent interface/builder pattern
这些都是意图相似的,因为我们慢慢建立一个中间对象,然后在一个步骤中创建我们的目标对象。
我推荐使用构建器模式,它使代码易于阅读。
答案 3 :(得分: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
答案 4 :(得分:0)
如果重载不是问题,那么python中的每个类都可以简化为一个方法,我们可以将其称为doIt(....)。与所有内容一样,最好适度地进行操作。重载带有多个参数的任何方法都是不好的做法。相反,允许用户以一口大小的相关数据块来构建对象。这更合乎逻辑。在您的情况下,您可以将通话分为名称,通讯和其他内容。