Skip to content

Type inference and hasattr #1424

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Tracked by #11142
JukkaL opened this issue Apr 22, 2016 · 15 comments · Fixed by #13544
Closed
Tracked by #11142

Type inference and hasattr #1424

JukkaL opened this issue Apr 22, 2016 · 15 comments · Fixed by #13544
Labels

Comments

@JukkaL
Copy link
Collaborator

JukkaL commented Apr 22, 2016

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.
@gvanrossum gvanrossum added this to the Undetermined priority milestone Apr 28, 2016
@timabbott
Copy link

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.

@gvanrossum
Copy link
Member

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
#1424 (comment), or mute
the thread
https://github.com/notifications/unsubscribe/ACwrMgWW3Km5ug5KWyGjSWJtCKdAsPpuks5qIme4gaJpZM4INlY-
.

--Guido (mobile)

@timabbott
Copy link

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.

@gvanrossum
Copy link
Member

gvanrossum commented Jun 5, 2016

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

@jhance
Copy link
Collaborator

jhance commented Jun 10, 2016

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

@gvanrossum gvanrossum removed this from the Undetermined priority milestone Mar 29, 2017
@ilevkivskyi ilevkivskyi added priority-1-normal false-positive mypy gave an error on correct code and removed priority-2-low labels May 18, 2018
HiromuHota pushed a commit to HiromuHota/fonduer that referenced this issue Nov 6, 2019
lukehsiao pushed a commit to HazyResearch/fonduer that referenced this issue Nov 6, 2019
@ikelos
Copy link

ikelos commented Nov 23, 2019

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?

@isidentical
Copy link
Member

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")

@TornaxO7
Copy link

TornaxO7 commented Aug 6, 2021

This might be interesting for some people who encounters this problem. You could use the getattr function to get the value and set the default value to None (or any other value to check) and add an if condition afterwards to do some other action if this attribute doesn't exist. Here's a little example:

unknown = getattr(<object>, 'valname', None)
if unknown is None:
    # your action if it's None

@jenstroeger
Copy link

I came across this issue when using multiple inheritance in the context of SQLAlchemy’s Mixins. Take the Mixing in Columns example code which adds a property created_at, and which mypy then fails to identify as an attribute.

For both,

if hasattr(self, "create_at"):
    create_at = self.create_at

and

try:
    create_at = self.create_at
except AttributeError:
    # Do something else.

mypy reports the "Foo" has no attribute "bar" error, although both are safe at runtime. @TornaxO7’s suggestion above to use getattr() works well for any attribute value but None.

For the time being I’ve chosen to # type: ignore the guarded assignment above, but I wonder if a plugin for this particular case would make sense? (I’ve not written a plugin for mypy before 🤓)

@antonagestam
Copy link
Contributor

@jenstroeger Hmm, it sounds like something off, why is the definition of create_at not inherited from the mixin?

fgsalomon added a commit to alice-biometrics/meiga that referenced this issue Jul 26, 2022
Recommendation: python/mypy#1424 (comment)

Also ignore 2 mypy errors I don't know how to fix
fgsalomon added a commit to alice-biometrics/meiga that referenced this issue Jul 26, 2022
* fix: improve type hints

* fix: initialize message attribute in Error as None

Recommendation: python/mypy#1424 (comment)

Also ignore 2 mypy errors I don't know how to fix

* fix: return None in negative branch to fix mypy error

* fix: return None to avoid mypy error and fix type hint

* fix: add missing type in handle and improve the typing of certain Result methods.

Co-authored-by: acostapazo <[email protected]>
@alexmojaki
Copy link

Based on when the fix above was merged, it should be in mypy 0.982, right? It still doesn't seem to be working, e.g. this:

class C:
    pass

c = C()

setattr(c, "foo", 42)
if hasattr(c, "foo"):
    print(c.foo)

still leads to error: "C" has no attribute "foo"

@JelleZijlstra
Copy link
Member

It's not in 0.982. In the b29051c page you can see the tags that include the commit, and there aren't any. (Example commit that was in 0.981/2: 0ec789d)

@BlackHC
Copy link

BlackHC commented Dec 23, 2022

Any updates on this? When will the fix make it into mypy? Thanks so much!

@jenshnielsen
Copy link

@BlackHC if you follow @JelleZijlstra's instructions above you can see that it should be in mypy 0.991

@BlackHC
Copy link

BlackHC commented Dec 23, 2022

Thanks! Upgraded my package dependencies and it works perfectly now!

(You made me realize that poetry update was not doing what I thought it was doing and that I had to install the poetry up plugin. Apologies for my confusion around this!)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging a pull request may close this issue.