Pycharm error message 'Unresolved attribute reference {attr/method} for class {class}'

Intro

I am currently trying to create something with the pygame library and python classes.

Although it is working as expected in its current state, there's an error message / hint from PyCharm which bugs me, since I don't know how to properly handle it, or how to figure out if it's a bug in PyCharm or pygame.

Problem

I created a Tile class and a TileMap class. The latter one has an attribute of a pygame.sprite.Group() - with its elements being instances of the Tile class.

The Tile class inherits from pygames sprite.Sprite() class. Adding a method in the Tile class and calling this method on a Tile instance from within the TileMap class yields the following message / error in PyCharm: [(A) in code below]

'Unresolved attribute reference 'collides_with' for class 'Sprite'

If the Tiles are stored in TileMap as an attribute with type list, no such message is shown. [(B) in code below]

Question(s)

  • Is this an issue with PyCharm, confusing the class with its parent? (It does not state that the attribute is not found in class 'Tile' where it is defined, but in the parent class 'Sprite')
    • If so, could i safely ignore this?
  • Could this alternatively be an issue with pygames' 'Sprite' class?
  • Or is this something python specific, meaning i do not understand its classes or am blind to something?

Conditions

  • python 3.9.1
  • pygame 2.0.2
  • PyCharm 2021.2.3
  • OS: macOS Catalina 10.15.7 (Same on Ubuntu 20.04)

This is the shortest i could condense the code to:

import pygame


class Tile(pygame.sprite.Sprite):
    def __init__(self, size):
        super().__init__()
        self.size = size

    @staticmethod
    def collides_with():
        return True


class TileMap:
    def __init__(self, tile_size):
        self.tile_size = tile_size
        self.terrain_sprites = self.create_tile_group()  <-- (A)  
        self.tiles = []                                  <-- (B) 

    def create_tile_group(self):
        sprite_group = pygame.sprite.Group()

        width, height = (10, 10)
        columns = int((width / self.tile_size))
        rows = int((height / self.tile_size))

        for col in range(columns):
            for row in range(rows):
                sprite = Tile(self.tile_size)
                sprite_group.add(sprite)
                self.tiles.append(sprite)
        return sprite_group

    def update(self):
        for tile in self.terrain_sprites:
            tile.collides_with()   <-- (A) This is where i get the error 
            print(type(tile))
        for i in self.tiles:
            i.collides_with()      <-- (B) Strangely this is ok
            print(type(i))


t = TileMap(1)
t.update()
 

1 answer

  • answered 2021-10-25 08:24 qouify

    I'm not familiar at all with pycharm (never used it) but it seems to me that it relies on mypy for type checking. Even if it relies on something else I think that my answer is still valid.

    The problem here is that the terrain_sprites attribute of TileMap is initialised, through create_tile_group, with a pygame.sprite.Group object. Therefore, terrain_sprites if of type pygame.sprite.Group.

    Hence the error message (A) makes sense because when you iterate over the items of a Group, you iterate over Sprite objects and not over Tile objects, even if you have initialised your Group with only Tile objects. In other words, for mypy, terrain_sprites can contain any type of Sprites and not only Tiles. And it does not matter that, during the execution, terrain_sprites only contain Tiles.

    Now, for the fact that the (B) line does not raise any problem, it also makes sense. Again, through the create_tile_group method you only put in tiles objects of type Tile. Therefore, for mypy tiles is a list of Tile objects and so your i.collides_with() statement is perfectly valid.

    Apparently, according to How can I reveal type hints in PyCharm? you can see what type mypy attributed to a variable. You should see in your update method that tile is considered as a pygame.sprite.Sprite while i is considered as Tile.

    Now to fix that problem you have different solutions. One is to add an assertion like assert isinstance(tile, Tile) right before the tile.collides_with() statement. By this way, mypy will learn that tile is not any Sprite but a Tile. You can also protect your tile.collides_with() statement with an isinstance check.

    (I added the typing tag to your question.)

How many English words
do you know?
Test your English vocabulary size, and measure
how many words do you know
Online Test
Powered by Examplum