Reputation: 1362
I get an unresolved attribute reference 'get_opposite_node' for class 'list'
warning in PyCharm, even though the code works fine.
This the relevant part of the code:
def bfs(self, start, end):
visited = {start} # visited set contains int indices
start_node = self.Node(start)
q = deque()
q.append(start_node) # deque contains Node objects
while q:
cur_node = q.popleft() # Node object
if cur_node.idx == end:
return cur_node
for edge in self.get_out_edges(cur_node):
# HERE IS WHERE I GET THE WARNING:
child = self.Node(edge.get_opposite_node(i))
child_idx = child.idx
if child_idx not in visited:
visited.add(child_idx)
q.append(child)
child.parent = cur_node
return None
def get_out_edges(self, node):
return self.adj[node.idx]
def add_edge(self, line):
# line is just a string from the input file
(v, u, w) = [int(x) for x in line.split()]
e = Edge(v,u,w)
self.adj[v].append(e)
self.adj[u].append(e)
self.edges.append(e)
self.adj
is the list of lists:self.adj = [[] for _ in range(N)]
, which is later filled by the edges of class Edge
:
class Edge:
def __init__(self, v, u, w):
self.v = v
self.u = u
self.w = w
def get_node(self):
return self.v
def get_opposite_node(self, k=None):
return self.u if k == self.v or k is None else self.v
So, basically, while self.adj[i]
contains Edge
s, PyCharm thinks that it contains lists(?). Is there a way to 'convince' PyCharm that it is not like this?
Upvotes: 3
Views: 12533
Reputation: 4653
PyCharm is confused because you have explicitly declared adj
to be a list
of (empty) list
s. It can't tell that you've subsequently overwritten each element of adj
with an object which understands get_opposite_node()
. But it does know that you can't get_opposite_node()
on a list
.
You could work around this by declaring adj
to contain a list
of None
instead of a list
of empty list
s. The below may clarify a little:
class RealEdge:
def get_something(self):
return "something"
class SomeEdgyThing:
def __init__(self, size):
self.adj = [[] for _ in range(size)]
class SomeOtherEdgyThing:
def __init__(self, size):
self.adj = [None for _ in range(size)]
class ReallyEdgyThing:
def __init__(self, size):
self.adj = [RealEdge() for _ in range(size)]
my_complicated_thing = SomeEdgyThing(7)
for i in range(len(my_complicated_thing.adj)):
my_complicated_thing.adj[i] = RealEdge()
# PyCharm warning:
# Unresolved attribute reference 'get_something' for class 'list'
# Runs fine, because we overwrote all the values in adj
# with RealEdges instead of lists.
print(my_complicated_thing.adj[1].get_something())
my_thing = SomeEdgyThing(7)
try:
# PyCharm warning:
# Unresolved attribute reference 'get_something' for class 'list'
# Also a runtime error:
# AttributeError: 'list' object has no attribute 'get_something'
print(my_thing.adj[1].get_something())
except AttributeError as e:
print(e)
my_other_thing = SomeOtherEdgyThing(7)
try:
# PyCharm doesn't complain.
# Does it assume NoneType is part of a union type...?
# Runtime error:
# AttributeError: 'NoneType' object has no attribute 'get_something'
print(my_other_thing.adj[1].get_something())
except AttributeError as e:
print(e)
real_thing = ReallyEdgyThing(7)
# No error
print(real_thing.adj[1].get_something())
Output:
something
'list' object has no attribute 'get_something'
'NoneType' object has no attribute 'get_something'
something
I suspect that you don't really need to write self.adj = [[] for _ in range(N)]
and that self.adj = [None for _ in range(N)]
will serve just as well and will cause the warning to disappear.
Now, you are still exposed to the case where your code runs in the wrong order somehow and you try to call get_opposite_node()
on a member of adj
that you haven't updated to contain a real Edge
. You just aren't being warned about it any more. For that reason I would recommend wrapping these calls inside a try: ... except AttributeError:
block.
Upvotes: 3