Home > Enterprise >  How can I tell PyCharm that an object is a specialization (subclass) of a type without using "i
How can I tell PyCharm that an object is a specialization (subclass) of a type without using "i

Time:04-16

I have a method that receives objects that share a common superclass but are of many different subtypes. It checks the type of each object, and, depending on the type, passes it to different functions. The receiver functions are annotated to receive specific subtypes.

The object types are known ahead of time and are part of a finite set. Objects will only ever be depth-1 subclasses, not sub-subclasses.

This code is very performance critical. To avoid the overhead of calling isinstance multiple times when dispatching, I store the type in a variable.

However, I'm having trouble getting PyCharm to detect the "specialization" of each object based on that stored type variable. It indicates typeerrors because I'm passing objects whose type it only knows as the superclass to functions that receive specific subclasses.

For example, my code looks like this:

class Superclass: ...
class Subtype1(Superclass): ...
class Subtype2(Superclass): ...
# ...many more subtypes

def receiver1(arg: Subtype1): ...
def receiver2(arg: Subtype2): ...
# ...many more receivers

def dispatch():
    item = get_item()
    item_type = type(item)
    if item_type is Subtype1:
        receiver(item)
    elif item_type is Subtype2:
        receiver2(item)
    # ... many more cases

With that code, PyCharm detects type mismatches on the calls to receiver1 and receiver2.

Ordinarily, I'd give up here, since Python's a dynamically typed language and it seems unreasonable to insist that an IDE structurally understand code that dispatches on dynamic type.

However, PyCharm can do that specialization; it just only seems to work with isinstance checks. If I replace dispatch with the below code, PyCharm type checks it correctly:

def dispatch():
    item = get_item()
    if isinstance(item, Subtype1):
        receiver1(item)
    elif isinstance(item, Subtype2):
        receiver2(item)
    # ... many more cases

The problem is, that's too slow. isinstance itself is much slower than an is comparison, and I have to call it many times to check each case; there are many cases.

Is there a way to make PyCharm detect simple specialization types without using isinstance? If so, how?

What I've tried

  • Ordinarily, this would be a good fit for object orientation. If the receiver functions were instead methods on each subtype, I could simply call item.recieve() or something each time. However, the subtypes are declared in code I don't control (and are record classes implemented in C, so they can't be subclassed--weird, I know, but I did disclaim that it's performance-critical code :)).
  • Various dispatch table approaches using a dict keyed by type with receiver methods as values similarly defeat PyCharm's type checking.

CodePudding user response:

You could give additional type hints to PyCharm

def dispatch():
    item = get_item()
    item_type = type(item)
    if item_type is Subtype1:
        item: Subtype1
        receiver1(item)
    elif item_type is Subtype2:
        item: Subtype2
        receiver2(item)

This should not affect performance of the code.

Another approach would be to inject the receiver method into Subtype1/2 classes:

def receiver1(arg: Subtype1):
    print("receiver1 called")

def receiver2(arg: Subtype2):
    print("receiver2 called")

class MyMeta(type):
    pass

d = dict(Subtype1.__dict__)
d.update({"receiver": receiver1})
Subtype1 = MyMeta(Subtype1.__name__,
                  Subtype1.__bases__,
                  d)

d = dict(Subtype2.__dict__)
d.update({"receiver": receiver2})
Subtype2 = MyMeta(Subtype2.__name__,
                  Subtype2.__bases__,
                  d)

def dispatch():
    item = get_item()
    item.receiver()
  • Related