12

I just started working with PyCharm Community Edition 2016.3.2 today. Every time I assign a value from my function at_square, it warns me that 'Function at_square doesn't return anything,' but it definitely does in every instance unless an error is raised during execution, and every use of the function is behaving as expected. I want to know why PyCharm thinks it doesn't and if there's anything I can do to correct it. (I know there is an option to suppress the warning for that particular function, but it does so by inserting a commented line in my code above the function, and I find it just as annoying to have to remember to take that out at the end of the project.)

This is the function in question:

def at_square(self, square): """ Return the value at the given square """ if type(square) == str: file, rank = Board.tup_from_an(square) elif type(square) == tuple: file, rank = square else: raise ValueError("Expected tuple or AN str, got " + str(type(square))) if not 0 <= file <= 7: raise ValueError("File out of range: " + str(file)) if not 0 <= rank <= 7: raise ValueError("Rank out of range: " + str(rank)) return self.board[file][rank] 

If it matters, this is more precisely a method of an object. I stuck with the term 'function' because that is the language PyCharm is using.

My only thought is that my use of error raising might be confusing PyCharm, but that seems too simple. (Please feel free to critique my error raising, as I'm not sure this is the idiomatic way to do it.)

Update: Humorously, if I remove the return line altogether, the warning goes away and returns immediately when I put it back. It also goes away if I replace self.board[file][rank] with a constant value like 8. Changing file or rank to constant values does not remove the warning, so I gather that PyCharm is somehow confused about the nature of self.board, which is a list of 8 other lists.

Update: Per the suggestion of @StephenRauch, I created a minimal example that reflects everything relevant to data assignment done by at_square:

class Obj: def __init__(self): self.nested_list = [[0],[1]] @staticmethod def tup_method(data): return tuple(data) def method(self,data): x,y = Obj.tup_method(data) return self.nested_list[x][y] def other_method(self,data): value = self.method(data) print(value) x = Obj() x.other_method([1,2]) 

PyCharm doesn't give any warnings for this. In at_square, I've tried commenting out every single line down to the two following:

def at_square(self, square): file, rank = Board.tup_from_an(square) return self.board[file][rank] 

PyCharm gives the same warning. If I leave only the return line, then and only then does the warning disappear. PyCharm appears to be confused by the simultaneous assignment of file and rank via tup_from_an. Here is the code for that method:

@staticmethod def tup_from_an(an): """ Convert a square in algebraic notation into a coordinate tuple """ if an[0] in Board.a_file_dict: file = Board.a_file_dict[an[0]] else: raise ValueError("Invalid an syntax (file out of range a-h): " + str(an)) if not an[1].isnumeric(): raise ValueError("Invalid an syntax (rank out of range 1-8): " + str(an)) elif int(an[1]) - 1 in Board.n_file_dict: rank = int(an[1]) - 1 else: raise ValueError("Invalid an syntax (rank out of range 1-8): " + str(an)) return file, rank 

Update: In its constructor, the class Board (which is the parent class for all these methods) saves a reference to the instance in a static variable instance. self.at_square(square) gives the warning, while Board.instance.at_square(square) does not. I'm still going to use the former where appropriate, but that could shed some light on what PyCharm is thinking.

5
  • This works fine for me. Perhaps you edited too much? Did you create a Minimal, Complete, and Verifiable example? Side note, I suggest you clean up all of the warning you can before spending anytime on the ones you do not understand. Eg: you are using file as a variable name, and PyCharm will complain about that. Commented Feb 9, 2017 at 7:40
  • @StephenRauch That's interesting. I wrote the function in IDLE before switching over, and the warning was apparent immediately. All that I've changed about it since using PyCharm is changing its name from mixed case to lowercase with underscores. I have been going through the warnings, but it hasn't said anything about my use of file as a variable name. That is a file as in chess terminology. Thank you for the reference to Minimal, Complete, and Verifiable examples. I learn more about SO every time I post. I'm not sure how I would recreate this, but I will take a stab at it. Commented Feb 9, 2017 at 15:26
  • @StephenRauch Updated the original question Commented Feb 9, 2017 at 22:11
  • 4
    FWIW I was getting this error for min function. I was actually passing an argument to min that was (statically) identified to be None. Commented Jun 15, 2017 at 16:52
  • What is the initial value of self.board? Commented May 14, 2018 at 15:32

2 Answers 2

6

PyCharm assumes a missing return value if the return value statically evaluates to None. This can happen if initialising values using None, and changing their type later on.

class Foo: def __init__(self): self.qux = [None] # infers type for Foo().qux as List[None] def bar(self): return self.qux[0] # infers return type as None 

At this point, Foo.bar is statically inferred as (self: Foo) -> None. Dynamically changing the type of qux via side-effects does not update this:

foo = Foo() foo.qux = [2] # *dynamic* type of foo.bar() is now ``(self: Foo) -> int`` foo_bar = foo.bar() # Function 'bar' still has same *static* type 

The problem is that you are overwriting a statically inferred class attribute by means of a dynamically assigned instance attribute. That is simply not feasible for static analysis to catch in general.

You can fix this with an explicit type hint.

import typing class Foo: def __init__(self): self.qux = [None] # type: typing.List[int] def bar(self): return self.qux[0] # infers return type as int 

Since Python 3.5, you can also use inline type hints. These are especially useful for return types.

import typing class Foo: def __init__(self): # initial type hint to enable inference self.qux: typing.List[int] = [None] # explicit return type hint to override inference def bar(self) -> int: return self.qux[0] # infers return type as int 

Note that it is still a good idea to rely on inference where it works! Annotating only self.qux makes it easier to change the type later on. Annotating bar is mostly useful for documentation and to override incorrect inference.

If you need to support pre-3.5, you can also use stub files. Say your class is in foomodule.py, create a file called foomodule.pyi. Inside, just add the annotated fields and function signatures; you can (and should) leave out the bodies.

import typing class Foo: # type hint for fields qux: typing.List[int] # explicit return type hint to override inference def bar(self) -> int: ... 
Sign up to request clarification or add additional context in comments.

Comments

3

Type hinting as of Python 3.6

The style in the example below is now recommended:

from typing import typing class Board: def __init__(self): self.board: List[List[int]] = [] 

Quick Documentation

Pycharm's 'Quick Documentation' show if you got the typing right. Place the cursor in the middle of the object of interest and hit Ctrl+Q. I suspect the types from tup_from_an(an) is not going to be as desired. You could, try and type hint all the args and internal objects, but it may be better value to type hint just the function return types. Type hinting means I don't need to trawl external documentation, so I focus effort on objects that'll be used by external users and try not to do too much internal stuff. Here's both arg and return type hinting:

@staticmethod def tup_from_an(an: List[int]) -> (int, int): ... 

Clear Cache

Pycharm can lock onto out dated definitions. Doesn't hurt to go help>find action...>clear caches

No bodies perfect

Python is constantly improving (Type hinting was updated in 3.7) Pycharm is also constantly improving. The price for the fast pace of development on these relatively immature advanced features means checking or submitting to their issue tracker may be the next call.

2 Comments

Note that type hints in comments is not only a Pycharm feature. "While annotations are normally the best format for type hints, there are times when it is more appropriate to represent them by a special comment, or in a separately distributed stub file. " See python.org/dev/peps/pep-0484/#acceptable-type-hints and python.org/dev/peps/pep-0484/#type-comments
@MisterMiyagi Thanks

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.