Is passing too many arguments to the constructor considered an anti-pattern?
I am considering using the factory_boy library for API testing. An example from the documentation is:
class UserFactory(factory.Factory):
class Meta:
model = base.User
first_name = "John"
last_name = "Doe"
For this to work, we need first_name
, last_name
, etc to be passed as parameters to the __init__()
method of the base.User() class
. However, if you have many parameters this leads to something like:
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
Now the question is, is this construction considered anti-pattern, and if yes, what alternatives do I have?
Thanks
python factory-boy
|
show 1 more comment
I am considering using the factory_boy library for API testing. An example from the documentation is:
class UserFactory(factory.Factory):
class Meta:
model = base.User
first_name = "John"
last_name = "Doe"
For this to work, we need first_name
, last_name
, etc to be passed as parameters to the __init__()
method of the base.User() class
. However, if you have many parameters this leads to something like:
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
Now the question is, is this construction considered anti-pattern, and if yes, what alternatives do I have?
Thanks
python factory-boy
You don't need an__init__ ()
method on your class to usefactory_boy
unless it's changed since v2.4.1.
– kylie.a
Jun 2 '15 at 15:01
2
This isn't an answer, but your issue could be a sign that the User class is trying to do too many things - ideally, you wouldn't want to have to modify the User class if you add, say, notifications over carrier pidgeon. It could also be a sign that its data model can be simplified - you could have a list of phone/fax numbers instead ofmobile
,landline
andfax
fields, make guest users a subclass instead of a field, and so on.
– Wander Nauta
Jun 2 '15 at 15:04
2
Maybe this is pedantic, but in python the phrase positional argument is defined as not being a keyword argument. Aside from the obligatoryself
you have no positional arguments in this__init__
method. No one needs to worry about ifmobile
goes in position 10 or 14 since it is specified by keyword.
– Eric Appelt
Jun 2 '15 at 15:08
2
You could also usedef __init__(self, **kwargs): self.name=kwargs.get('name', None)
– kylie.a
Jun 2 '15 at 15:08
1
If you igonore for a moment the desirability or not of having all this contact fields in the table, with possible maintenance hassles, what is wrong with the code? These are optional fields so your call will populate as few as them as necessary (which Essence is expressly set to disallow). The intent'is clear. A Pattern would likely make code less clear and still not help with table maintenance. A kwargs approach is nice but hides your field names (what happens if you pass in firstname).
– JL Peyret
Jun 2 '15 at 15:18
|
show 1 more comment
I am considering using the factory_boy library for API testing. An example from the documentation is:
class UserFactory(factory.Factory):
class Meta:
model = base.User
first_name = "John"
last_name = "Doe"
For this to work, we need first_name
, last_name
, etc to be passed as parameters to the __init__()
method of the base.User() class
. However, if you have many parameters this leads to something like:
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
Now the question is, is this construction considered anti-pattern, and if yes, what alternatives do I have?
Thanks
python factory-boy
I am considering using the factory_boy library for API testing. An example from the documentation is:
class UserFactory(factory.Factory):
class Meta:
model = base.User
first_name = "John"
last_name = "Doe"
For this to work, we need first_name
, last_name
, etc to be passed as parameters to the __init__()
method of the base.User() class
. However, if you have many parameters this leads to something like:
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
Now the question is, is this construction considered anti-pattern, and if yes, what alternatives do I have?
Thanks
python factory-boy
python factory-boy
edited Jun 2 '15 at 15:10
skamsie
asked Jun 2 '15 at 14:51


skamsieskamsie
1,41221735
1,41221735
You don't need an__init__ ()
method on your class to usefactory_boy
unless it's changed since v2.4.1.
– kylie.a
Jun 2 '15 at 15:01
2
This isn't an answer, but your issue could be a sign that the User class is trying to do too many things - ideally, you wouldn't want to have to modify the User class if you add, say, notifications over carrier pidgeon. It could also be a sign that its data model can be simplified - you could have a list of phone/fax numbers instead ofmobile
,landline
andfax
fields, make guest users a subclass instead of a field, and so on.
– Wander Nauta
Jun 2 '15 at 15:04
2
Maybe this is pedantic, but in python the phrase positional argument is defined as not being a keyword argument. Aside from the obligatoryself
you have no positional arguments in this__init__
method. No one needs to worry about ifmobile
goes in position 10 or 14 since it is specified by keyword.
– Eric Appelt
Jun 2 '15 at 15:08
2
You could also usedef __init__(self, **kwargs): self.name=kwargs.get('name', None)
– kylie.a
Jun 2 '15 at 15:08
1
If you igonore for a moment the desirability or not of having all this contact fields in the table, with possible maintenance hassles, what is wrong with the code? These are optional fields so your call will populate as few as them as necessary (which Essence is expressly set to disallow). The intent'is clear. A Pattern would likely make code less clear and still not help with table maintenance. A kwargs approach is nice but hides your field names (what happens if you pass in firstname).
– JL Peyret
Jun 2 '15 at 15:18
|
show 1 more comment
You don't need an__init__ ()
method on your class to usefactory_boy
unless it's changed since v2.4.1.
– kylie.a
Jun 2 '15 at 15:01
2
This isn't an answer, but your issue could be a sign that the User class is trying to do too many things - ideally, you wouldn't want to have to modify the User class if you add, say, notifications over carrier pidgeon. It could also be a sign that its data model can be simplified - you could have a list of phone/fax numbers instead ofmobile
,landline
andfax
fields, make guest users a subclass instead of a field, and so on.
– Wander Nauta
Jun 2 '15 at 15:04
2
Maybe this is pedantic, but in python the phrase positional argument is defined as not being a keyword argument. Aside from the obligatoryself
you have no positional arguments in this__init__
method. No one needs to worry about ifmobile
goes in position 10 or 14 since it is specified by keyword.
– Eric Appelt
Jun 2 '15 at 15:08
2
You could also usedef __init__(self, **kwargs): self.name=kwargs.get('name', None)
– kylie.a
Jun 2 '15 at 15:08
1
If you igonore for a moment the desirability or not of having all this contact fields in the table, with possible maintenance hassles, what is wrong with the code? These are optional fields so your call will populate as few as them as necessary (which Essence is expressly set to disallow). The intent'is clear. A Pattern would likely make code less clear and still not help with table maintenance. A kwargs approach is nice but hides your field names (what happens if you pass in firstname).
– JL Peyret
Jun 2 '15 at 15:18
You don't need an
__init__ ()
method on your class to use factory_boy
unless it's changed since v2.4.1.– kylie.a
Jun 2 '15 at 15:01
You don't need an
__init__ ()
method on your class to use factory_boy
unless it's changed since v2.4.1.– kylie.a
Jun 2 '15 at 15:01
2
2
This isn't an answer, but your issue could be a sign that the User class is trying to do too many things - ideally, you wouldn't want to have to modify the User class if you add, say, notifications over carrier pidgeon. It could also be a sign that its data model can be simplified - you could have a list of phone/fax numbers instead of
mobile
, landline
and fax
fields, make guest users a subclass instead of a field, and so on.– Wander Nauta
Jun 2 '15 at 15:04
This isn't an answer, but your issue could be a sign that the User class is trying to do too many things - ideally, you wouldn't want to have to modify the User class if you add, say, notifications over carrier pidgeon. It could also be a sign that its data model can be simplified - you could have a list of phone/fax numbers instead of
mobile
, landline
and fax
fields, make guest users a subclass instead of a field, and so on.– Wander Nauta
Jun 2 '15 at 15:04
2
2
Maybe this is pedantic, but in python the phrase positional argument is defined as not being a keyword argument. Aside from the obligatory
self
you have no positional arguments in this __init__
method. No one needs to worry about if mobile
goes in position 10 or 14 since it is specified by keyword.– Eric Appelt
Jun 2 '15 at 15:08
Maybe this is pedantic, but in python the phrase positional argument is defined as not being a keyword argument. Aside from the obligatory
self
you have no positional arguments in this __init__
method. No one needs to worry about if mobile
goes in position 10 or 14 since it is specified by keyword.– Eric Appelt
Jun 2 '15 at 15:08
2
2
You could also use
def __init__(self, **kwargs): self.name=kwargs.get('name', None)
– kylie.a
Jun 2 '15 at 15:08
You could also use
def __init__(self, **kwargs): self.name=kwargs.get('name', None)
– kylie.a
Jun 2 '15 at 15:08
1
1
If you igonore for a moment the desirability or not of having all this contact fields in the table, with possible maintenance hassles, what is wrong with the code? These are optional fields so your call will populate as few as them as necessary (which Essence is expressly set to disallow). The intent'is clear. A Pattern would likely make code less clear and still not help with table maintenance. A kwargs approach is nice but hides your field names (what happens if you pass in firstname).
– JL Peyret
Jun 2 '15 at 15:18
If you igonore for a moment the desirability or not of having all this contact fields in the table, with possible maintenance hassles, what is wrong with the code? These are optional fields so your call will populate as few as them as necessary (which Essence is expressly set to disallow). The intent'is clear. A Pattern would likely make code less clear and still not help with table maintenance. A kwargs approach is nice but hides your field names (what happens if you pass in firstname).
– JL Peyret
Jun 2 '15 at 15:18
|
show 1 more comment
4 Answers
4
active
oldest
votes
You could pack the __init__
method's keyword arguments into one dict, and alter the instance's __dict__
directly:
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
However, this has the drawback of disallowing you from supplying arguments without naming them - x = User("Mr", "Kevin")
works with your original code, but not with this code.
I prefer this solution, but you loose the code completion. Is there a way not to?
– jankos
Jun 2 '15 at 22:08
+1 but for completeness, you should throwTypeError
if keyword argument was passed and it's not invalid_keys
list.
– ElmoVanKielmo
Jun 3 '15 at 11:28
add a comment |
In Python 3.7, dataclasses (specified in PEP557) were added. This allows you to only write these arguments once and not again in the constructor, since the constructor is made for you:
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
It also adds a __repr__
to the class as well as some others. Note that explicitly inheriting from object
is no longer needed in Python 3, since all classes are new-style classes by default.
There are a few drawbacks, though. It is slightly slower on class definition (since these methods need to be generated). You need to either set a default value or add a type annotation, otherwise you get a name error. If you want to use a mutable object, like a list, as a default argument, you need to use dataclass.field(default_factory=list)
(normally it is discouraged to write e.g. def f(x=)
, but here it actually raises an exception).
add a comment |
Yes, too many arguments is an antipattern (as stated in Clean Code by RObert C. Martin)
To avoid this, you have two design approaches:
The essence pattern
The fluent interface/builder pattern
These are both similar in intent, in that we slowly build up an intermediate object, and then create our target object in a single step.
I'd recommend the builder pattern, it makes the code easy to read.
So is an "essence" the same thing as a "parameter object" for a constructor?
– Damian Yerrick
Jun 2 '15 at 15:48
1
Second link is pretty useless since it includes a lot of jumping through Java-specific hoops. There's a good code example in Wikipedia en.wikipedia.org/wiki/Builder_pattern
– Dave Kielpinski
Sep 7 '17 at 18:00
add a comment |
The biggest risk would be if you had a large number of positional arguments and then ended up not knowing which is which.. Keyword arguments definitely make this better.
As suggested by others, the builder pattern also works quite nicely.
If you have a very large number of fields, you can also do something more generic, like so:
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
add a comment |
Your Answer
StackExchange.ifUsing("editor", function () {
StackExchange.using("externalEditor", function () {
StackExchange.using("snippets", function () {
StackExchange.snippets.init();
});
});
}, "code-snippets");
StackExchange.ready(function() {
var channelOptions = {
tags: "".split(" "),
id: "1"
};
initTagRenderer("".split(" "), "".split(" "), channelOptions);
StackExchange.using("externalEditor", function() {
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled) {
StackExchange.using("snippets", function() {
createEditor();
});
}
else {
createEditor();
}
});
function createEditor() {
StackExchange.prepareEditor({
heartbeatType: 'answer',
autoActivateHeartbeat: false,
convertImagesToLinks: true,
noModals: true,
showLowRepImageUploadWarning: true,
reputationToPostImages: 10,
bindNavPrevention: true,
postfix: "",
imageUploader: {
brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
allowUrls: true
},
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
});
}
});
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f30599478%2fis-passing-too-many-arguments-to-the-constructor-considered-an-anti-pattern%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
4 Answers
4
active
oldest
votes
4 Answers
4
active
oldest
votes
active
oldest
votes
active
oldest
votes
You could pack the __init__
method's keyword arguments into one dict, and alter the instance's __dict__
directly:
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
However, this has the drawback of disallowing you from supplying arguments without naming them - x = User("Mr", "Kevin")
works with your original code, but not with this code.
I prefer this solution, but you loose the code completion. Is there a way not to?
– jankos
Jun 2 '15 at 22:08
+1 but for completeness, you should throwTypeError
if keyword argument was passed and it's not invalid_keys
list.
– ElmoVanKielmo
Jun 3 '15 at 11:28
add a comment |
You could pack the __init__
method's keyword arguments into one dict, and alter the instance's __dict__
directly:
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
However, this has the drawback of disallowing you from supplying arguments without naming them - x = User("Mr", "Kevin")
works with your original code, but not with this code.
I prefer this solution, but you loose the code completion. Is there a way not to?
– jankos
Jun 2 '15 at 22:08
+1 but for completeness, you should throwTypeError
if keyword argument was passed and it's not invalid_keys
list.
– ElmoVanKielmo
Jun 3 '15 at 11:28
add a comment |
You could pack the __init__
method's keyword arguments into one dict, and alter the instance's __dict__
directly:
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
However, this has the drawback of disallowing you from supplying arguments without naming them - x = User("Mr", "Kevin")
works with your original code, but not with this code.
You could pack the __init__
method's keyword arguments into one dict, and alter the instance's __dict__
directly:
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
However, this has the drawback of disallowing you from supplying arguments without naming them - x = User("Mr", "Kevin")
works with your original code, but not with this code.
answered Jun 2 '15 at 15:08


KevinKevin
59.2k1169113
59.2k1169113
I prefer this solution, but you loose the code completion. Is there a way not to?
– jankos
Jun 2 '15 at 22:08
+1 but for completeness, you should throwTypeError
if keyword argument was passed and it's not invalid_keys
list.
– ElmoVanKielmo
Jun 3 '15 at 11:28
add a comment |
I prefer this solution, but you loose the code completion. Is there a way not to?
– jankos
Jun 2 '15 at 22:08
+1 but for completeness, you should throwTypeError
if keyword argument was passed and it's not invalid_keys
list.
– ElmoVanKielmo
Jun 3 '15 at 11:28
I prefer this solution, but you loose the code completion. Is there a way not to?
– jankos
Jun 2 '15 at 22:08
I prefer this solution, but you loose the code completion. Is there a way not to?
– jankos
Jun 2 '15 at 22:08
+1 but for completeness, you should throw
TypeError
if keyword argument was passed and it's not in valid_keys
list.– ElmoVanKielmo
Jun 3 '15 at 11:28
+1 but for completeness, you should throw
TypeError
if keyword argument was passed and it's not in valid_keys
list.– ElmoVanKielmo
Jun 3 '15 at 11:28
add a comment |
In Python 3.7, dataclasses (specified in PEP557) were added. This allows you to only write these arguments once and not again in the constructor, since the constructor is made for you:
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
It also adds a __repr__
to the class as well as some others. Note that explicitly inheriting from object
is no longer needed in Python 3, since all classes are new-style classes by default.
There are a few drawbacks, though. It is slightly slower on class definition (since these methods need to be generated). You need to either set a default value or add a type annotation, otherwise you get a name error. If you want to use a mutable object, like a list, as a default argument, you need to use dataclass.field(default_factory=list)
(normally it is discouraged to write e.g. def f(x=)
, but here it actually raises an exception).
add a comment |
In Python 3.7, dataclasses (specified in PEP557) were added. This allows you to only write these arguments once and not again in the constructor, since the constructor is made for you:
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
It also adds a __repr__
to the class as well as some others. Note that explicitly inheriting from object
is no longer needed in Python 3, since all classes are new-style classes by default.
There are a few drawbacks, though. It is slightly slower on class definition (since these methods need to be generated). You need to either set a default value or add a type annotation, otherwise you get a name error. If you want to use a mutable object, like a list, as a default argument, you need to use dataclass.field(default_factory=list)
(normally it is discouraged to write e.g. def f(x=)
, but here it actually raises an exception).
add a comment |
In Python 3.7, dataclasses (specified in PEP557) were added. This allows you to only write these arguments once and not again in the constructor, since the constructor is made for you:
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
It also adds a __repr__
to the class as well as some others. Note that explicitly inheriting from object
is no longer needed in Python 3, since all classes are new-style classes by default.
There are a few drawbacks, though. It is slightly slower on class definition (since these methods need to be generated). You need to either set a default value or add a type annotation, otherwise you get a name error. If you want to use a mutable object, like a list, as a default argument, you need to use dataclass.field(default_factory=list)
(normally it is discouraged to write e.g. def f(x=)
, but here it actually raises an exception).
In Python 3.7, dataclasses (specified in PEP557) were added. This allows you to only write these arguments once and not again in the constructor, since the constructor is made for you:
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
It also adds a __repr__
to the class as well as some others. Note that explicitly inheriting from object
is no longer needed in Python 3, since all classes are new-style classes by default.
There are a few drawbacks, though. It is slightly slower on class definition (since these methods need to be generated). You need to either set a default value or add a type annotation, otherwise you get a name error. If you want to use a mutable object, like a list, as a default argument, you need to use dataclass.field(default_factory=list)
(normally it is discouraged to write e.g. def f(x=)
, but here it actually raises an exception).
edited Jan 1 at 16:20
answered Jan 1 at 16:14
GraipherGraipher
4,4441533
4,4441533
add a comment |
add a comment |
Yes, too many arguments is an antipattern (as stated in Clean Code by RObert C. Martin)
To avoid this, you have two design approaches:
The essence pattern
The fluent interface/builder pattern
These are both similar in intent, in that we slowly build up an intermediate object, and then create our target object in a single step.
I'd recommend the builder pattern, it makes the code easy to read.
So is an "essence" the same thing as a "parameter object" for a constructor?
– Damian Yerrick
Jun 2 '15 at 15:48
1
Second link is pretty useless since it includes a lot of jumping through Java-specific hoops. There's a good code example in Wikipedia en.wikipedia.org/wiki/Builder_pattern
– Dave Kielpinski
Sep 7 '17 at 18:00
add a comment |
Yes, too many arguments is an antipattern (as stated in Clean Code by RObert C. Martin)
To avoid this, you have two design approaches:
The essence pattern
The fluent interface/builder pattern
These are both similar in intent, in that we slowly build up an intermediate object, and then create our target object in a single step.
I'd recommend the builder pattern, it makes the code easy to read.
So is an "essence" the same thing as a "parameter object" for a constructor?
– Damian Yerrick
Jun 2 '15 at 15:48
1
Second link is pretty useless since it includes a lot of jumping through Java-specific hoops. There's a good code example in Wikipedia en.wikipedia.org/wiki/Builder_pattern
– Dave Kielpinski
Sep 7 '17 at 18:00
add a comment |
Yes, too many arguments is an antipattern (as stated in Clean Code by RObert C. Martin)
To avoid this, you have two design approaches:
The essence pattern
The fluent interface/builder pattern
These are both similar in intent, in that we slowly build up an intermediate object, and then create our target object in a single step.
I'd recommend the builder pattern, it makes the code easy to read.
Yes, too many arguments is an antipattern (as stated in Clean Code by RObert C. Martin)
To avoid this, you have two design approaches:
The essence pattern
The fluent interface/builder pattern
These are both similar in intent, in that we slowly build up an intermediate object, and then create our target object in a single step.
I'd recommend the builder pattern, it makes the code easy to read.
answered Jun 2 '15 at 14:58
EzzoredEzzored
75239
75239
So is an "essence" the same thing as a "parameter object" for a constructor?
– Damian Yerrick
Jun 2 '15 at 15:48
1
Second link is pretty useless since it includes a lot of jumping through Java-specific hoops. There's a good code example in Wikipedia en.wikipedia.org/wiki/Builder_pattern
– Dave Kielpinski
Sep 7 '17 at 18:00
add a comment |
So is an "essence" the same thing as a "parameter object" for a constructor?
– Damian Yerrick
Jun 2 '15 at 15:48
1
Second link is pretty useless since it includes a lot of jumping through Java-specific hoops. There's a good code example in Wikipedia en.wikipedia.org/wiki/Builder_pattern
– Dave Kielpinski
Sep 7 '17 at 18:00
So is an "essence" the same thing as a "parameter object" for a constructor?
– Damian Yerrick
Jun 2 '15 at 15:48
So is an "essence" the same thing as a "parameter object" for a constructor?
– Damian Yerrick
Jun 2 '15 at 15:48
1
1
Second link is pretty useless since it includes a lot of jumping through Java-specific hoops. There's a good code example in Wikipedia en.wikipedia.org/wiki/Builder_pattern
– Dave Kielpinski
Sep 7 '17 at 18:00
Second link is pretty useless since it includes a lot of jumping through Java-specific hoops. There's a good code example in Wikipedia en.wikipedia.org/wiki/Builder_pattern
– Dave Kielpinski
Sep 7 '17 at 18:00
add a comment |
The biggest risk would be if you had a large number of positional arguments and then ended up not knowing which is which.. Keyword arguments definitely make this better.
As suggested by others, the builder pattern also works quite nicely.
If you have a very large number of fields, you can also do something more generic, like so:
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
add a comment |
The biggest risk would be if you had a large number of positional arguments and then ended up not knowing which is which.. Keyword arguments definitely make this better.
As suggested by others, the builder pattern also works quite nicely.
If you have a very large number of fields, you can also do something more generic, like so:
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
add a comment |
The biggest risk would be if you had a large number of positional arguments and then ended up not knowing which is which.. Keyword arguments definitely make this better.
As suggested by others, the builder pattern also works quite nicely.
If you have a very large number of fields, you can also do something more generic, like so:
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
The biggest risk would be if you had a large number of positional arguments and then ended up not knowing which is which.. Keyword arguments definitely make this better.
As suggested by others, the builder pattern also works quite nicely.
If you have a very large number of fields, you can also do something more generic, like so:
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
answered Jun 2 '15 at 15:31


VladVlad
15.3k43162
15.3k43162
add a comment |
add a comment |
Thanks for contributing an answer to Stack Overflow!
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
To learn more, see our tips on writing great answers.
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f30599478%2fis-passing-too-many-arguments-to-the-constructor-considered-an-anti-pattern%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
You don't need an
__init__ ()
method on your class to usefactory_boy
unless it's changed since v2.4.1.– kylie.a
Jun 2 '15 at 15:01
2
This isn't an answer, but your issue could be a sign that the User class is trying to do too many things - ideally, you wouldn't want to have to modify the User class if you add, say, notifications over carrier pidgeon. It could also be a sign that its data model can be simplified - you could have a list of phone/fax numbers instead of
mobile
,landline
andfax
fields, make guest users a subclass instead of a field, and so on.– Wander Nauta
Jun 2 '15 at 15:04
2
Maybe this is pedantic, but in python the phrase positional argument is defined as not being a keyword argument. Aside from the obligatory
self
you have no positional arguments in this__init__
method. No one needs to worry about ifmobile
goes in position 10 or 14 since it is specified by keyword.– Eric Appelt
Jun 2 '15 at 15:08
2
You could also use
def __init__(self, **kwargs): self.name=kwargs.get('name', None)
– kylie.a
Jun 2 '15 at 15:08
1
If you igonore for a moment the desirability or not of having all this contact fields in the table, with possible maintenance hassles, what is wrong with the code? These are optional fields so your call will populate as few as them as necessary (which Essence is expressly set to disallow). The intent'is clear. A Pattern would likely make code less clear and still not help with table maintenance. A kwargs approach is nice but hides your field names (what happens if you pass in firstname).
– JL Peyret
Jun 2 '15 at 15:18