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.