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 an int for many purposes.
  • It rules out subclasses and abstract types: a pretty-printing int subclass or enum would be rejected, even though they are logically Integers.
    • This severely limits portability: Python2 Strings can be either str or unicode, and Integers can be either int or long.

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. a generator) 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, ... - and dict.
  • 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 a for loop.
  • 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 to int.
  • 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 to int 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.