Skip to content

Instantly share code, notes, and snippets.

@pvcraven
Last active June 21, 2017 18:35
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save pvcraven/fbeeb2a6493130f4a363ba5b7818adc4 to your computer and use it in GitHub Desktop.
Save pvcraven/fbeeb2a6493130f4a363ba5b7818adc4 to your computer and use it in GitHub Desktop.
What is the proper way to do this code, so a linter does not see an error?
from typing import Iterable
from typing import TypeVar
from typing import Generic
"""
This is the API section.
The user should not be changing this part of the code.
"""
class MyAPIParentClass:
def f(self):
print("F")
T = TypeVar('T', bound=MyAPIParentClass)
class MyClassList(Generic[T]):
def __init__(self):
self.my_list = []
def append(self, item: MyAPIParentClass):
self.my_list.append(item)
def __iter__(self) -> Iterable[T]:
return iter(self.my_list)
"""
This is the user code section.
The users creates a class to extend the API.
The user is creating a custom class to extend the base MyAPIParentClass
and using the APIs MyClassList to hold the instances.
"""
class MyCustomChildClass(MyAPIParentClass):
def g(self):
print("G")
def main():
# Create instances of my derived class
my_list: MyClassList[MyCustomChildClass] = MyClassList()
# Add them to the list
for i in range(10):
new_item = MyCustomChildClass()
my_list.append(new_item)
# Now call my custom function. It works, but linting will error out
# because it doesn't know this is an instance of MyCustomChildClass, just
# MyParentClass.
# In C++ I'd use a cast to tell the compiler it was MyCustomChildClass.
# What am I supposed to do here?
for my_item in my_list:
my_item.g()
main()
@vlasovskikh
Copy link

@pvcraven The contract of PyClassList states that it iterates over any MyAPIParentClass instances, so it is legal to pass all subtypes of MyAPIParentClass into append() and __iter__() provides the ability to iterate over them.

In either case you have to make your types stricter for the users being able to invoke g() on the items of my_list.

One possibility it restrict MyClassList() to only certain kind of items, for example to only one particular subtype of MyAPIParentClass defined during the definition of an instance of MyClassList:

from typing import TypeVar

T = TypeVar('T', bound=MyAPIParentClass)

class MyClassList(Generict[T]):
    def __iter__(self) -> Iterable[T]: ...


def main():
    my_list: MyClassList[MyCustomChildClass] = MyClassList()
    ...
    for x in my_list:
        x.g()  # OK

Another possibility is to accept the fact that MyClassList can iterate over any MyAPIParentClass subclasses so you have to add an explicit check for an item being aMyCustomChildClass:

def main():
    my_list = MyClassList()
    ...
    for x in my_list:
        if isinstnace(x, MyCustomChildClass):
            x.g()  # OK

@pvcraven
Copy link
Author

pvcraven commented Jun 21, 2017

@vlasovskikh I think I'm looking for version #1 of your answer.

Version #2 will still cause the linter to be unhappy.

I took what you said and updated the example. The linter seems happy. At least with the call to g(). Let me know if there's a better way to code it than version 2 of the example here. Thanks.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment