Python How to check if list has only one data type

eye-catch Python

typing helps the Python development with the data type. But sometimes it’s hard to work with type. How can we implement it if we want to check if a list has only one data type?

Sponsored links

How to check the object type

Firstly, we should know how to check the object data type. We can know it by using type.

print(type("sss"))  # <class 'str'>
print(type(123))    # <class 'int'>
print(type(True))   # <class 'bool'>
print(type([1, 2])) # <class 'list'>

If it’s necessary to compare the type to do the different processes according to the type, we can write it this way.

print(type("SSS") == str) # True

if type(val) == str:
    # Do something here for string type
elif type(val) == int:
    # Do something here for int type

It’s also possible to use it for class.

class MyClass:
    pass


instance = MyClass()

print(type(instance) == MyClass) # True
Sponsored links

isinstance is equivalent to instanceof in TypeScript

I’m familiar with TypeScript. So I looked for an equivalent to instanceof. It checks if the object is an instance of something.

class Person {
    constructor(private name: string) { }
    public sayHello(): void {
        console.log(`Hello, I'm ${this.name}`);
    }
}

const instance = new Person("Yuto");
if (instance instanceof Person) {
    console.log("instance is Person");
}

In Python, isinstance has the same feature. It can be used for all types because everything is an object type in Python.

def typeof(val: Any) -> Type[Union[int, str, bool, None]]:
    if isinstance(val, str):
        return str
    elif isinstance(val, bool):
        return bool
    elif isinstance(val, int):
        return int
    else:
        return type(None)


print(typeof("abc"))  # <class 'str'>
print(typeof(122))    # <class 'int'>
print(typeof(True))   # <class 'bool'>
print(typeof(["abc", 122])) # None

But be careful with the order of the if-else if you need to do a different process for bool and int. Boolean is handled as int.

print(isinstance(True, int))    # True
print(isinstance(False, int))   # True
print(list(map(int, [True, False]))) # [1, 0]

If the int check comes first, the boolean value is also processed there.

We understand that isintance can be used in the same context as using type. The following two statements are the same.

if type(val) == str:
    # something
if isinstance(val, str):
    # something

We can use one of them according to our preference.

Check if the list has only one data type

Runtime:OK, type checker: NG

If it’s necessary to check if the list has only one data type, we can write the following functions.

def is_str_list(val: List[Any]) -> bool:
    if len(val) == 0:
        return False
    return all(isinstance(x, str) for x in val)


def is_int_list(val: List[Any]) -> bool:
    if len(val) == 0:
        return False
    return all(isinstance(x, int) for x in val)


print("--- is_str_list ---")
print(is_str_list([]))         # False
print(is_str_list([1, 2, 3]))  # False
print(is_str_list(["1", 2]))   # False
print(is_str_list(["1", "2"])) # True

print("--- is_int_list ---")
print(is_int_list([]))         # False
print(is_int_list([1, 2, 3]))  # True
print(is_int_list(["1", 2]))   # False
print(is_int_list(["1", "2"])) # False

It can be used in production but the type checker running on IDE doesn’t recognize that the list has only one data type.

Loot at the following function.

def process_list(values: Union[List[int], List[str], List[int | str]]) -> None:
    if is_str_list(values):
        # (variable) x: int | str
        # "x" is not accessed (Pylance)
        for x in values:
            # IDE doesn't recognize that there is only one data type in the list
            pass


def return_list(val: List[Any]) -> Union[List[int], List[str]]:
    if is_str_list(val):
        # (parameter) val: List[Any]
        return val
    elif is_int_list(val):
        # (parameter) val: List[Any]
        return val
    raise Exception("The specified list has mixed data type")

If a cursor is on the x variable in the first function, it shows the info. It is still a mixed data type.

In the second function, the returned value is recognized as List[Any] but not List[int], List[str].

runtime:OK, type checker: OK

I defined the following function to improve the previous problem.

def str_list(val: List[Any]) -> Optional[List[str]]:
    is_mix = any(not isinstance(i, str) for i in val)

    if is_mix:
        return None

    return val


print(str_list([]))         # []
print(str_list([1, 2, 3]))  # None
print(str_list(["1", 2]))   # None
print(str_list(["1", "2"])) # ['1', '2']

If it returns a list when the specified list has only one data type, the type checker recognizes the type and it can help us better.

def return_list2(val: List[Any]) -> Union[List[int], List[str]]:
    result = str_list(val)
    if result:
        # (variable) result: List[str]
        return result

    result = int_list(val)
    if result:
        # (variable) result: List[int]
        return result

    raise Exception("The specified list has mixed data type")

Overview

Currently (Python 3.10), it seems not possible to infer the data type of the first element of the list. If it’s possible to infer it, we could write it in another and better way.

So, this would be the current solution for me.

def same_type_list(val: List[Any]) -> Union[List[int], List[str]]:
    result = int_list(val)
    if result:
        return result

    result = str_list(val)
    if result:
        return result
    raise Exception("The specified list has mixed data type")

Comments

Copied title and URL