How do you set a conditional in python based on datatypes?
How about,
if isinstance(x, int):
but a cleaner way would simply be
sum(z for z in y if isinstance(z, int))
TLDR:
- Use
if isinstance(x, int):
unless you have a reason not to. - Use
if type(x) is int:
if you need exact type equality and nothing else. - Use
try: ix = int(x)
if you are fine with converting to the target type.
There is a really big "it depends" to type-checking in Python. There are many ways to deal with types, and all have their pros and cons. With Python3, several more have emerged.
Explicit type equality
Types are first-class objects, and you can treat them like any other value.
So if you want the type of something to be equal to int
, just test for it:
if type(x) is int:
This is the most restrictive type of testing: it requires exact type equality. Often, this is not what you want:
- It rules out substitute types: a
float
would not be valid, even though it behaves like anint
for many purposes. - It rules out subclasses and abstract types: a pretty-printing
int
subclass orenum
would be rejected, even though they are logically Integers.- This severely limits portability: Python2 Strings can be either
str
orunicode
, and Integers can be eitherint
orlong
.
- This severely limits portability: Python2 Strings can be either
Note that explicit type equality has its uses for low-level operations:
- Some types cannot be subclassed, such as
slice
. An explicit check is, well, more explicit here. - Some low-level operations, such as serialisation or C-APIs, require specific types.
Variants
A comparison can also be performed against the __class__
attribute:
if x.__class__ is int:
Note if a class defines a __class__
property, this is not the same as type(x)
.
When there are several classes to check for, using a dict
to dispatch actions is more extensible and can be faster (≥5-10 types) than explicit checks.
This is especially useful for conversions and serialisation:
dispatch_dict = {float: round, str: int, int: lambda x: x}
def convert(x):
converter = self.dispatch_dict[type(x)] # lookup callable based on type
return converter(x)
Instance check on explicit types
The idiomatic type test uses the isinstance
builtin:
if isinstance(x, int):
This check is both exact and performant. This is most often what people want for checking types:
- It handles subtypes properly. A pretty-printing
int
subclass would still pass this test. - It allows checking multiple types at once. In Python2, doing
isinstance(x, (int, long))
gets you all builtin integers.
Most importantly, the downsides are negligible most of the time:
- It still accepts funky subclasses that behave in weird ways. Since anything can be made to behave in weird ways, this is futile to guard against.
- It can easily be too restrictive: many people check for
isinstance(x, list)
when any sequence (e.g.tuple
) or even iterable (e.g. agenerator
) would do as well. This is more of a concern for general purpose libraries than scripts or applications.
Variant
If you already have a type, issubclass
behaves the same:
if issubclass(x_type, int):
Instance check on abstract type
Python has a concept of abstract base classes. Loosely speaking, these express the meaning of types, not their hierarchy:
if isinstance(x, numbers.Real): # accept anything you can sum up like a number
In other words, type(x) does not necessarily inherit from numbers.Real
but must behave like it.
Still, this is a very complex and difficult concept:
- It is often overkill if you are looking for basic types. An Integer is simply an
int
most of the time. - People coming from other languages often confuse its concepts.
- Distinguishing it from e.g. C++, the emphasis is abstract base class as opposed to abstract base class.
- ABCs can be used like Java interfaces, but may still have concrete functionality.
However, it is incredibly useful for generic libraries and abstractions.
- Many functions/algorithms do not need explicit types, just their behaviour.
- If you just need to look up things by key,
dict
restricts you to a specific in-memory type. By contrast,collections.abc.Mapping
also includes database wrappers, large disk-backed dictionaries, lazy containers, ... - anddict
.
- If you just need to look up things by key,
- It allows expressing partial type constraints.
- There is no strict base type implementing iteration. But if you check objects against
collections.abc.Iterable
, they all work in afor
loop.
- There is no strict base type implementing iteration. But if you check objects against
- It allows creating separate, optimised implementations that appear as the same abstract type.
While it is usually not needed for throwaway scripts, I would highly recommend using this for anything that lives beyond a few python releases.
Tentative conversion
The idiomatic way of handling types is not to test them, but to assume they are compatible. If you already expect some wrong types in your input, simply skip everything that is not compatible:
try:
ix = int(x)
except (ValueError, TypeError):
continue # not compatible with int, try the next one
else:
a.append(ix)
This is not actually a type check, but usually serves the same intention.
- It guarantees you have the expected type in your output.
- It has some limited leeway in converting wrong types, e.g. specialising
float
toint
. - It works without you knowing which types conform to
int
.
The major downside is that it is an explicit transformation.
- You can silently accept "wrong" values, e.g. converting a
str
containing a literal. - It needlessly converts even types that would be good enough, e.g.
float
toint
when you just need numbers.
Conversion is an effective tool for some specific use cases. It works best if you know roughly what your input is, and must make guarantees about your output.
Function dispatch
Sometimes the goal of type checking is just to select an appropriate function. In this case, function dispatch such as functools.singledispatch
allows specialising function implementations for specific types:
@singledispatch
def append_int(value, sequence):
return
@append_int.register
def _(value: int, sequence):
sequence.append(value)
This is a combination of isinstance
and dict
dispatch. It is most useful for larger applications:
- It keeps the site of usage small, regardless of the number of dispatched types.
- It allows registering specialisations for additional types later, even in other modules.
Still, it doesn't come without its downsides:
- Originating in functional and strongly typed languages, many Python programmers are not familiar with single- or even multiple-dispatch.
- Dispatches require separate functions, and are therefore not suitable to be defined at the site of usage.
- Creating the functions and "warming up" the dispatch cache takes notable runtime overhead. Dispatch functions should be defined once and re-used often.
- Even a warmed up dispatch table is slower than a hand-written if/else or
dict
lookup.
Controlling the input
The best course of action is to ensure you never have to check for type in the first place. This is a bit of a meta-topic, as it depends strongly on the use case.
Here, the source of somelist
should never have put non-numbers into it.