42

I am looking for some kind of a mapping function f() that does something similar to this:

f(str) = ''
f(complex) = 0j
f(list) = []

Meaning that it returns an object of type that evaluates to False when cast to bool.

Does such a function exist?

MattS
  • 1,701
  • 1
  • 14
  • 20
  • 7
    There isn't always such a value – Mad Physicist Mar 15 '18 at 08:04
  • 8
    Why do this though? – Veedrac Mar 15 '18 at 11:09
  • @Veedrac I want to write a smart iff function that its default False value (if not passed explicitly) will be the false object of the type of the True value. So if the signature is something like (cond,t,f), and f is optional and wasn't passed, it will be set to type(t)() – MattS Mar 15 '18 at 14:07
  • 4
    I think that just pushes the question one level down. That doesn't seem like a fundamental technical problem. – Veedrac Mar 15 '18 at 14:25
  • @Veedrac Yes, I require it for a simple use, but maybe other people could benefit from this question in the future. – MattS Mar 15 '18 at 14:54
  • 3
    @Veedrac It seems to me that this is an attempt to implement the Null object pattern: instead of returning `None`, it can be convenient to return an object which behaves like the expected type, but does nothing. This saves from having to check for `None` all the time. – glglgl Mar 15 '18 at 15:29
  • @MadPhysicist and sometimes there are more than one. – Baldrickk Mar 15 '18 at 15:31
  • @Baldrickk. Yes, but then there is at least one to choose from, so it's not as important. – Mad Physicist Mar 15 '18 at 16:12
  • This is one of the most dreaded features of most languages created in the '90s. why would you want to bring it back? – Paul Manta Mar 16 '18 at 07:30
  • @Bazingaa all answers are correct. I just accepted the one that was posted earliest and with the most votes – MattS Mar 27 '19 at 14:08

5 Answers5

67

No, there is no such mapping. Not every type of object has a falsy value, and others have more than one. Since the truth value of a class can be customized with the __bool__ method, a class could theoretically have an infinite number of (different) falsy instances.

That said, most builtin types return their falsy value when their constructor is called without arguments:

>>> str()
''
>>> complex()
0j
>>> list()
[]
Aran-Fey
  • 39,665
  • 11
  • 104
  • 149
  • 10
    "a class could theoretically have an infinite number of (different) falsy instances" or zero, for that matter. – Challenger5 Mar 15 '18 at 15:04
  • 3
    This works for surprisingly many of the types in https://docs.python.org/3/library/stdtypes.html ; `type()`, `range()` and `memoryview()` fail with a `TypeError` as they expect an argument. And other types like module and class are probably not so relevant. As far as I can see this never returns a truthy value for any builtin type. – RemcoGerlich Mar 15 '18 at 15:39
  • 8
    @RemcoGerlich: You forgot to test `object()`, which is truthy. If you count exceptions, I believe all built-in exception types return a truthy object when called with no arguments, too. Some built-ins that are technically types but usually not treated as types also return truthy values, like `property()` or `zip()`. – user2357112 Mar 15 '18 at 18:04
  • 1
    Just because you have an infinite amount of (different) falsey instances doesn't mean you can't enumerate them or that there can't exist a mapping :) – Benjamin Gruenbaum Mar 20 '18 at 18:01
26

Nope, and in general, there may be no such value. The Python data model is pretty loose about how the truth-value of a type may be implemented:

object.__bool__(self)

Called to implement truth value testing and the built-in operation bool(); should return False or True. When this method is not defined, __len__() is called, if it is defined, and the object is considered true if its result is nonzero. If a class defines neither __len__() nor __bool__(), all its instances are considered true.

So consider:

import random
class Wacky:
    def __bool__(self):
        return bool(random.randint(0,1))

What should f(Wacky) return?

juanpa.arrivillaga
  • 88,713
  • 10
  • 131
  • 172
  • 6
    Or [__nonzero__](https://docs.python.org/2/reference/datamodel.html#object.__nonzero__) for python 2 – Sohaib Farooqi Mar 15 '18 at 08:14
  • @juanpa.arrivillaga. I started working on a (cheating) solution in my update, but it only works for classes that allow no-arg constructors. Perhaps you could suggest a workaround? – Mad Physicist Mar 15 '18 at 08:31
  • You are correct, perhaps it was too much to ask for a general, builtin function. But my usage to such function will be trivial at most times, I was planning to use it for common standard python types without explicitly defining the mapping myself. – MattS Mar 15 '18 at 09:01
6

This is actually called an identity element, and in programming is most often seen as part of the definition of a monoid. In python, you can get it for a type using the mzero function in the PyMonad package. Haskell calls it mempty.

Karl Bielefeldt
  • 47,314
  • 10
  • 60
  • 94
5

Not all types have such a value to begin with. Others may have many such values. The most correct way of doing this would be to create a type-to-value dict, because then you could check if a given type was in the dict at all, and you could chose which value is the correct one if there are multiple options. The drawback is of course that you would have to somehow register every type you were interested in.

Alternatively, you could write a function using some heuristics. If you were very careful about what you passed into the function, it would probably be of some limited use. For example, all the cases you show except complex are containers that generalize with cls().

complex actually works like that too, but I mention it separately because int and float do not. So if your attempt with the empty constructor fails by returning a truthy object or raising a TypeError, you can try cls(0). And so on and so forth...

Update

@juanpa.arrivillaga's answer actually suggests a clever workaround that will work for most classes. You can extend the class and forcibly create an instance that will be falsy but otherwise identical to the original class. You have to do this by extending because dunder methods like __bool__ are only ever looked up on the class, never on an instance. There are also many types where such methods can not be replaced on the instance to begin with. As @Aran-Fey's now-deleted comment points out, you can selectively call object.__new__ or t.__new__, depending on whether you are dealing with a very special case (like int) or not:

def f(t):
    class tx(t):
        def __bool__(self):
            return False
    try:
        return object.__new__(tx)
    except TypeError:
        return tx.__new__(tx)

This will only work for 99.9% of classes you ever encounter. It is possible to create a contrived case that raises a TypeError when passed to object.__new__ as int does, and does not allow for a no-arg version of t.__new__, but I doubt you will ever find such a thing in nature. See the gist @Aran-Fey made to demonstrate this.

Mad Physicist
  • 107,652
  • 25
  • 181
  • 264
2

No such function exists because it's not possible in general. A class may have no falsy value or it may require reversing an arbitrarily complex implementation of __bool__.

What you could do by breaking everything else is to construct a new object of that class and forcibly assign its __bool__ function to one that returns False. Though I suspect that you are looking for an object that would otherwise be a valid member of the class.

In any case, this is a Very Bad Idea in classic style.

Kafein
  • 357
  • 1
  • 7