28

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?

Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
comte
  • 3,092
  • 5
  • 25
  • 41
  • 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) – comte Dec 09 '19 at 21:03

3 Answers3

48

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
Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
  • 1
    @comte: The user is *not* created; the `None` singleton is returned instead. – Martijn Pieters Aug 08 '14 at 10:08
  • @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 Aug 08 '14 at 10:14
  • 1
    @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. – Martijn Pieters Aug 08 '14 at 10:21
  • 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 !!! – comte Aug 08 '14 at 10:22
  • What would be the right typing annotation for the classmethod? – MKesper May 20 '20 at 09:40
  • 1
    @MKesper: See [Can you annotate return type when value is instance of cls?](https://stackoverflow.com/a/39205612), 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. – Martijn Pieters May 21 '20 at 20:29
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)  
Ivan
  • 490
  • 1
  • 7
  • 23
  • 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) ? – comte Aug 08 '14 at 10:11
  • ok from Martjin update i understand what you mean. thx both of you. – comte Aug 08 '14 at 10:19
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
Julien
  • 703
  • 1
  • 10
  • 17