3

Consider this imported class and function. I can't edit them and it's not practical to patch them:

# Code I can't change

class Person:
    def __init__(self, age: int):
        self.age = age

def get_person() -> Person:
    return Person(42)

I'm wrapping get_person with my own function and amending the Person object. I must call/wrap get_person as it's not practical to reproduce the logic inside that function.

# My code

def get_person_with_name() -> ???:
    person = get_person()
    person.name = 'Tim'
    return person

How do I type hint the return value of get_person_with_name to "a Person object but also with a name attribute"? And how do I do it properly so that type checking and code completion respect the type of the amended object?

101
  • 8,514
  • 6
  • 43
  • 69
  • 3
    "a Person object but also with a name attribute" That isn't really a type. Dynamically adding attributes is pretty much at odds with nominal typing. You *can* maybe use something like a `Protocol` which allows for structural subtyping to say "a type that has a name attribute" – juanpa.arrivillaga Dec 16 '20 at 04:56
  • If I know I'm _always_ going to be adding the name attribute then can't I just define a new type (probably based on `Person`) and say that this function conforms to that new type, somehow? – 101 Dec 16 '20 at 05:00
  • If you are *always* going to add it, why don't you just make it part of the class an annotate it as such? – juanpa.arrivillaga Dec 16 '20 at 05:09
  • I can't edit the imported class/function. It's also not really practical to patch them either. – 101 Dec 16 '20 at 05:11
  • 1
    Why not just subclass Person by defining a NamedPerson class that defines the "name" attribute? I suppose that going that route is not defining a "dynamically added attribute", but I don't consider that to be what your `get_person_with_name` function is doing. – CryptoFool Dec 16 '20 at 05:15
  • That works for type hinting, but it's technically wrong as the returned object is still of type `Person` and not `NamedPerson`. – 101 Dec 16 '20 at 05:16
  • 1
    No...the returned object would be of type `NamedPerson` but would also satisfy any usage where a `Person` object is expected. This is the basic nature of object orientation, which type hinting naturally supports. – CryptoFool Dec 16 '20 at 05:19
  • See https://stackoverflow.com/questions/46092104/subclass-in-type-hinting - I pulled this up because I had a vague idea that something had changed recently with subclassing and type hints. I believe that this SO question/answer describes that change. In any case, a subclass of a type is a distinct type. – CryptoFool Dec 16 '20 at 05:25
  • Thanks Steve, but I don't think that link quite answers it. `Type` allows specifying "this class or its subclass", but I want "an instance of this class or an instance of its subclass". – 101 Dec 16 '20 at 07:32
  • Subclassing `Person` and specifying that subclass as the return type also doesn't work well. The two types are distinctly different and the type checker complains. The code still works of course, but that's only because type hints aren't enforced upon execution. – 101 Dec 16 '20 at 07:52
  • 2
    Dynamically adding attributes like that is **deliberately forbidden** in the static type system. It's perfectly fine in the *actual* runtime type system, but for static typing? Nope. There is no annotation for what you want to do. If you want to do things that aren't statically valid, you probably shouldn't be using type annotations at all. – user2357112 Dec 16 '20 at 08:06
  • @user2357112supportsMonica even if my function is always just dumbly adding the same attribute every time? There's no way to predefine that new type beforehand? – 101 Dec 16 '20 at 08:15

0 Answers0