Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

like image 970
skamsie Avatar asked Jun 02 '15 14:06

skamsie


People also ask

How many arguments can be passed to a constructor?

This method takes four arguments: the loan amount, the interest rate, the future value and the number of periods.

How do you fix constructors with too many parameters?

Using a builder can solve the issue of having to many parameters in a constructor when all the fields are not mandatory. It also takes away the problems with having multiple constructors for different purposes.

How many maximum parameters we can use with difficult constructor?

Sometimes overloaded constructors are also used. But Checkstyle points out that the maximum number of arguments given to a method or constructor must be 7.

How many arguments does parameterized constructor contain?

Constructors that can take at least one argument are termed as parameterized constructors.


2 Answers

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).

This is useful where you have to have all those arguments in the constructor, because they all belong to the same object and cannot be extracted to sub-objects, for example.

like image 187
Graipher Avatar answered Oct 17 '22 02:10

Graipher


You could pack the __init__ method's keyword arguments into one dict, and set them dynamically with 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)

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.

like image 39
Kevin Avatar answered Oct 17 '22 03:10

Kevin