Mypy: Type inference and hasattr

Created on 22 Apr 2016  Â·  7Comments  Â·  Source: python/mypy

I just encountered code like this:

if hasattr(x, 'initialize'):
     x.initialize()

The type of x was an ABC but it doesn't include initialize. This can be easily worked around by rewriting it like this:

if hasattr(x, 'initialize'):
     cast(Any, x).initialize()

However, mypy could do better, plausibly. For example:

  • If x has a union type, infer only union item types with attribute initialize after the hasattr check. So if type of x is Union[str, X] and X has initialize, infer type of x to be X in the if body.
  • Allow specifying "potentially undefined" attributes in types. Accessing these requires a hasattr check (a little like Optional[...] requiring something like an is not None check). Not sure what the syntax for this would be like. It would be nice to support these in ABCs as well.
false-positive needs discussion priority-1-normal

Most helpful comment

I was wondering whether the protocol/structural subtyping work could be used on a hasattr call to in some way to coerce the type into one that accepts the attribute within that scope?

All 7 comments

I ran into an instance of this in the Zulip test runner code, where we add an attribute to a test method via a decorator to track the fact it's expected to be slow, and then check it like this:

def enforce_timely_test_completion(test_method, test_name, delay):                                                                                                     
    # type: (Callable[[], None], str, float) -> None                                                                                                                   
    if hasattr(test_method, 'expected_run_time'):                                                                                                                      
        # Allow for tests to run 50% slower than normal due                                                                                                            
        # to random variations.                                                                                                                                        
        max_delay = 1.5 * test_method.expected_run_time

See https://github.com/zulip/zulip/blob/master/zerver/lib/test_runner.py for the full code.

In general i despise hasattr() checks; it's usually much better to add a
class level initialization to None and check for that.

On Saturday, June 4, 2016, Tim Abbott [email protected] wrote:

I ran into an instance of this in the Zulip test runner code, where we add
an attribute to a test method via a decorator to track the fact it's
expected to be slow, and then check it like this:

def enforce_timely_test_completion(test_method, test_name, delay):
# type: (Callable[[], None], str, float) -> None
if hasattr(test_method, 'expected_run_time'):
# Allow for tests to run 50% slower than normal due
# to random variations.
max_delay = 1.5 * test_method.expected_run_time

See https://github.com/zulip/zulip/blob/master/zerver/lib/test_runner.py
for the full code.

—
You are receiving this because you are subscribed to this thread.
Reply to this email directly, view it on GitHub
https://github.com/python/mypy/issues/1424#issuecomment-223795217, or mute
the thread
https://github.com/notifications/unsubscribe/ACwrMgWW3Km5ug5KWyGjSWJtCKdAsPpuks5qIme4gaJpZM4INlY-
.

--Guido (mobile)

Just to be clear, these are attributes monkey-added to a function, not a class, so I don't think that solution would work here.

So there's no place to declare their type anyway. Maybe using three-arg getattr is better?

If x has a union type, infer only union item types with attribute initialize after the hasattr check. So if type of x is Union[str, X] and X has initialize, infer type of x to be X in the if body.

Likely has false positives if one of the elements on the union is an ABC

I was wondering whether the protocol/structural subtyping work could be used on a hasattr call to in some way to coerce the type into one that accepts the attribute within that scope?

This also affects using features from future python versions (I'm aware that we can do sys.version_info compare but IMHO this case shouldn't give errors)

import ast
if hasattr(ast, "unparse"):
    print(ast.unparse)
else:
    print("no")
Was this page helpful?
0 / 5 - 0 ratings