#๐Ÿ”’ Type hinting: Classes and subclasses

43 messages ยท Page 1 of 1 (latest)

silent hemlock
#

Suppose that I have classes Drawable, Placeable, and Draggable. The latter 2 are subclasses of the first. See bare implementation below

class Drawable:
    def __init__(self, parent: Union['Drawable', None], children: list['Drawable']):
        self.children = children
        self.parent = parent

class Placeable(Drawable):

    def __init__(self, rect: pygame.rect.Rect, parent: Union['Drawable', None]):
        super().__init__(rect, parent, [])
    
    def remove_child(self, target):
        self.children.remove(target)
    
    def add_child(self, target):
        self.children.append(target)


class Draggable(Drawable):

    def __init__(self, rect: pygame.rect.Rect, parent: Placeable):
        super().__init__(rect, parent, [])
    
    def release_click(self, pos: Tuple[int, int]):
      self.parent.remove_child(self)

I noticed that my IntelliSense doesn't realize that the Draggble.parent is supposed to be a Placeable but only sees it as a Drawable. Is it possible to type hint this better so that this wouldn't be an issue? I'm trying to learn how to type hint properly so that my code can be more readable in the future.

dense fogBOT
#

@silent hemlock

Python help channel opened

Remember to:

  • Ask your Python question, not if you can ask or if there's an expert who can help.
  • Show a code sample as text (rather than a screenshot) and the error message, if you've got one.
  • Explain what you expect to happen and what actually happens.

:warning: Do not pip install anything that isn't related to your question, especially if asked to over DMs.

ancient cargo
#

you could type parent as Self | None

#

(from typing import Self)

silent hemlock
#

but in this case, the parent is a Placeable, and self would be Draggable

undone tartan
#

sadly, if Drawable.parent: Drawable, and Draggable(Drawable), then Draggable.parent is also supposed to be Drawable, otherwise the liskov substituion principle isnt fullfilled
imagine something accepting a Drawable, it would try to set its .parent to some other Drawable, but your Draggable only works with a parent that is Placeable
if the parent was immutable it could be fine, me thinks (as using a Placeable as a Drawable is fine)
what is the actual hierarchy you want to build? how would you describe it without python specific stuff?

silent hemlock
#

A Placeable is also a Drawable

undone tartan
#

yes, but not all drawables are placeables

silent hemlock
#

I'll try to explain my end goal to see if i'm going about this hierarchy the right way

undone tartan
#
def f(x: Drawable, y: Drawable):
  x.parent = y

x: Draggable
y: Drawable
f(x, y)
# uh oh! Draggable.parent was only supposed to be a Placeable, but was set to a Drawable
silent hemlock
#

mmm

undone tartan
#

you could make Drawable generic over the parent type (bound by Drawable), and then say that Draggable(Drawable[Placeable])

class Drawable[Parent: Drawable]:
  parent: Parent

class Placeable(Drawable[Drawable]):
  ...

class Draggable(Drawable[Placeable]):
  ...

or make the parent attribute immutable, if thats fine with your design

silent hemlock
#

immutable just means that attribute can't be changed and is static, but can be reassigned, right?

undone tartan
#

no, cant be reassigned, e.g. via a @dataclass(frozen=True), only set on construction

silent hemlock
#

then I don't think that that would be feasible unfortunately

undone tartan
#

consider trying the generic approach then, though that can easily "spread" across the whole codebase

silent hemlock
#

What exactly does that mean?

undone tartan
#

are you familiar with how you can say "i have a list of some specific type T" with list[T]?

silent hemlock
#

Draggable(Drawable[Placeable])

#

yes

undone tartan
#

thats because list is generic over 1 type parameter

#

you could make your Drawable generic over 1 type parameter (bound by the Drawable type, probally, so e.g. the parent couldnt be.. an int) and use that as the type of the parent attribute, then, Drawable[Placeable] would mean "a drawable the parent of which is a placeable"

silent hemlock
#

aren't you unable to use methods of T in a case like that or am I remembering incorrectly

undone tartan
#

depends
it is parametric polymorphism, yes, but if you have it bound - you can use stuff thats in the bound

undone tartan
# undone tartan you could make Drawable generic over the parent type (bound by Drawable), and th...

you could make stuff like

class Drawable[Parent: Drawable]:
    parent: Parent
    def draw(self) -> None:
        ...

class Placeable(Drawable[Drawable]):
    ...

class Draggable(Drawable[Placeable]):
    ...

def f[Parent: Drawable](drawable: Drawable[Parent], new_parent: Parent):
    drawable.parent = new_parent
    drawable.draw()

and that would be safe
(it would probally also make the most sense if Drawable was an ABC, im not sure what concrete implementation it could have)

silent hemlock
#

it's supposed to be one

#

but the main thing is that Draggable is only supposed to have a parent that is a Placeable

undone tartan
#

yes, that is expressed here, with class Draggable(Drawable[Placeable])

silent hemlock
#

would it be able to call a Placeable method within that?

undone tartan
#
reveal_type(Draggable.parent) # Placeable

so yes

silent hemlock
#

wait I just realized yea

#

before I start implementing these changes, I'd like to say what this is all for to see if this is the best way I would go about doing this

undone tartan
#

i think this approach is pretty clean

silent hemlock
#

I think so too but I still might be overcomplicating it

undone tartan
#

well, not seeing the "concrete" code, im not entirely sure myself, but the type logic seems fine here
often it happens that the concrete code you need for solving your actual problem is much simpler

silent hemlock
#

I'm trying to create a GUI that can assign players to a team. There are going to be 3 columns. One representing team 1, one representing team 2, and the other is unassigned. The gui is to support drag and drop on the players allowing them to be placed from one team to another. These are all abcs representing the gui representation of the teams (placeable) and the players (draggable)

undone tartan
#

if an ABC has only one concrete implementation, it is kind of a bad ABC
maybe just write the

class Team:
  ...
class Player:
  ...

and be done

silent hemlock
#

ig

undone tartan
#

i too have fallen into the over-abstraction and over-genericness trap many times
you'll know when you need it, and then designing it will be simpler
if you just have a "team and player" program - thats what should be in the program (unless its a library :d)

silent hemlock
#

fair enough

#

Thank you

dense fogBOT
#
Python help channel closed

This help channel has been closed and it's no longer possible to send messages here. If your question wasn't answered, feel free to create a new post in #1035199133436354600. To maximize your chances of getting a response, check out this guide on asking good questions.