29

Currently I have something like

if name and password:
     user = User(name, password)
     ...do stuff

I'd like to refactor it to something:

user = User(name, password)
if user:
     ...do stuff

I create a User() class:

class User():
    def __init__(self, name, password):
        if name and password:
            self.name, self.password = name, password

but in this case even if name or password are None, user still gets instantiated (empty but still exists, so the if user: test is true).

How to would I not instantiate an object based on specific arguments?

1
  • 5
    5 years after asking this question, I ended to avoid such pattern and use a classic self.exists = False in init. Then set it to True if your instance is ok. Condition is no more if user: but if user.exists: which is still very understandable, but object constructor pattern is far simplier (without new method) Commented Dec 9, 2019 at 21:03

3 Answers 3

50

You'll have to use a __new__ method; by the time __init__ is called a new instance has already been created.

class User(object):
     def __new__(cls, name, password):
         if name and password:
             instance = super(User, cls).__new__(cls)
             instance.name, instance.password = name, password
             return instance

However, you are violating expectations; when you call a class, I'd expect to always get a new instance. Either raise an exception, or use a classmethod instead.

Raising an exception:

class User(object):
    def __init__(self, name, password):
        if not (name and password):
            raise ValueError('Empty name or password not allowed')
        self.name, self.password = name, password

try:
    user = User(name, password)
except ValueError:
    # oops, no username or password
else:
    # ...do stuff

or using a classmethod:

class User(object):
    def __init__(self, name, password):
        self.name, self.password = name, password

    @classmethod
    def create_valid_user(cls, name, password):
        """Create a user, or return None if conditions are not met"""
        if not (name and password):
            return None
        return cls(name, password)

user = User.create_valid_user(name, password)
if user is not None:
    # ...do stuff
Sign up to request clarification or add additional context in comments.

6 Comments

@comte: The user is not created; the None singleton is returned instead.
@Martikn, true, 'user' is not created but variable user is. (i mean memory is allocated). also i remarked that it works if i only change init to new in my initial code. does your 'instance' additions have a real added value, or is it just because it's cleaner code ?
@comte: Not sure what you mean; __new__ still needs to create the new instance object (by calling object.__new__), as __new__ is a static method called on the class.
sidenote : i feel my first code (testing name and password BEFORE calling User constructor) is far simplier. i'm conforted in my OOP opinion regarding code complexity !!!
@MKesper: See Can you annotate return type when value is instance of cls?, use T = TypeVar('T', bound='User') at the module level, then use def create_valid_user(cls: Type[T], ...) -> Optional[T] on the classmethod (with whatever types are appropriate for name and password, of course). The Optional shows None is a possible result.
|
1

It's wrong way. You shold add a method in your User class named check(). Or(better way), create a static method createUser(name, password). It looks like:

user = User.createUser(name, password)  

2 Comments

why it is a wrong way (it's a question only) ? i don't like OOP since i feel it complexify code beyond reason. what i like in Martijn answer is that it's short. why do you add a new "createUser" method when constructor do the job (or am i missing something) ?
ok from Martjin update i understand what you mean. thx both of you.
1

Something like this :

class User():
    def __init__(self, name, password):
        self._name = self._password = None
        self.name = name
        self.password = password

    @property
    def name(self):
        return self._name

    @name.setter
    def name(self, name: str) -> None:
        if not name:
            raise TypeError("name is required")
        if len(name) < 5: # exemple
            raise ValueError("name must have 5 characters or more")
        self._name = name

    # Do the same for password

Then do something like this in your code, or put it in a class method as Factory pattern and return it.

try:
    user = User(name, password)
except (TypeError, ValueError):
    user = None

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.