Getting device type from user agent in Python - is explicit better than implicit? I'm debating with my colleagues which code is better:
user_agent is a class instance. The class itself comes from a third-party library so we don't have any control over it.

1.

def _get_ua_device_type(user_agent):
    property_to_type = {'is_mobile': 'mobile', 'is_tablet': 'tablet', 'is_pc': 'pc', 'is_bot': 'bot'}
    for prop, type in property_to_type.items():
        if get_attr(user_agent, prop) is True:
            return type
    return 'unknown'


2.

def _get_ua_device_type(user_agent):
    for type in ('mobile', 'tablet', 'pc', 'bot'):
        if get_attr(user_agent, f'is_{type}') is True:
            return type
    return 'unknown'


3.

def _get_ua_device_type(user_agent):
    if user_agent.is_mobile:
        return 'mobile'
    if user_agent.is_tablet:
        return 'tablet'
    if user_agent.is_pc:
        return 'pc'
    if user_agent.is_bot:
        return 'bot'
    return 'unknown'


Things we discussed:


Static code is easier to understand than dynamic.
Static code is easier to test.
Dynamic code is declarative and more idiomatic than metameric ifs.
Dynamic code will be as hard/easy to understand regardless of the number of choices.
It's hard to anticipate the number of choices up-front so why start with a more complex dynamic form?
Assuming that predicate names are derived from type names is bad.
Where is the line when code is spaghetti-like and should be rewritten in a more dynamic way?
What about Zen of Python (“Explicit is better than implicit.“, “Flat is better than nested.“, “Readability counts.”, “Simple is better than complex.”)


Do you have your own thoughts/suggestions?
Equivalence considerations

1 is not the same as 2 or 3 for older versions of Python. In 2 and 3, you have an ordered execution sequence that determines the priority of returned variables. In 1, you have a dictionary whose order is inherently unknown. This might be OK for you, or it might not. If it isn't, a workaround is to use an OrderedDict.

For newer versions of Python (3.6+) this should not be an issue.

Drop boolean redundancy

No matter what you do, you should probably stop writing is True if you know that the target variable is already boolean. If the variable can have mixed type (i.e. integer or boolean), which is bad but sadly often possible in Python, and if you care about this, then you need to keep is True.

Ownership

Do you control the design of user_agent? If so, then you can enforce a stable interface, and my favourite of your options is 2, simply because it's more concise. If not, then only (3) would work well with static analysis to catch a changing interface.

Other options

try:
    return next(type for type in ('mobile', 'tablet', 'pc', 'bot')
                if get_attr(user_agent, f'is_{type}'))
except StopIteration:
    return 'unknown'


The bigger problem

is that you're representing an idea of a type with multiple booleans when it should be a single enum-style variable. If you can change your user agent to do this, do this. If not, you may want to consider writing a multivariate-boolean-to-single-enum shim class to sanitize the rest of your business logic.