簡體   English   中英

Python類型的類組合方法的返回類型

[英]Python typing return type of class composition's method

我有一個類Human ,它可以有一個寵物和一個讓它說話的方法,可以是DogCat 我用Dog初始化Human ,並且pet_speak方法具有正確的返回類型提示Literal["Woof"]

如果我在初始化后將Human的 pet 更改為Cat ,則方法返回類型提示不會更新為Literal["Meow"]

有沒有辦法改變這種行為? 這是我的問題的簡化版本,創建兩個類,例如HumanDogHumanCat比較復雜。

class Cat:
    def speak(self): # implicit return type hint Literal["Meow"]
        return "Meow"

class Dog:
    def speak(self): # implicit return type hint Literal["Woof"]
        return "Woof"

# Class `Human` has a `pet`, and can call its `speak()` method
class Human:
    pet = Dog()

    def pet_speak(self):
        return self.pet.speak()

# Helper function to change `human`'s pet
def change_pet(human):
    human.pet = Cat()
    return human

bob = Human()
woof = bob.pet_speak() # Return type is correctly Literal["Woof"]
bob = change_pet(bob) # change attribute `pet` to `Cat`
meow = bob.pet_speak() # Return type is Literal["Woof"], but should be Literal["Meow"]

我能找到的最接近的問題是您能否根據傳遞給構造函數的參數鍵入提示重載方法的返回類型? ,但它也增加了每個Human方法和子類的開銷。


對不起,如果我把問題簡化得太多了,這里它更類似​​於我正在嘗試做的事情:

from __future__ import annotations

class Api:
    def request(self):
        return "Response"

class AsyncApi:
    async def request(self):
        return "Response"

class Database:
    api: Api | AsyncApi = Api()

    def get(self):
        return self.api.request()

def Async(database):
    database.api = AsyncApi()
    return database


async def main():
    db = Database()
    db.get()
    adb = Async(db)
    await adb.get() # typing error

如果添加缺少的注釋,通過 Pyright(或 Mypy)運行代碼,它會在此處發現錯誤:

def change_pet(human: Human) -> None:
    human.pet = Cat()
    # ^
    # Cannot assign member "pet" for type "Human"
    #  Expression of type "Cat" cannot be assigned to member "pet" of class "Human"
    #    "Cat" is incompatible with "Dog"
    return human

實際上, human.pet的類型是Cat 通常,當你改變它時,對象的類型不能改變。 這是有道理的,例如因為一個對象可能是共享的:

class CatHumans:
    def __init__(self, humans: Iterable[Human]) -> None:
        # somehow establish that all the `humans` have a cat
        self._humans = list(humans)

    def do_something_assuming_humans_have_cats(self) -> None:
        for human in self._humans:
            assert human.pet.speak() == "Meow"
            ...

alice, bob, charlie = make_humans_with_cats(3)
cat_humans = CatHumans([alice, bob, charlie])

alice.pet = Dog()
cat_humans.do_something_assuming_humans_have_cats()

類型檢查器即使不是不可能也很難跟蹤這種情況。


我不知道你在解決什么真正的問題,但我有兩個解決方案:

  1. pet指定更通用的類型:

一個通用的協議/基類:

class Human:
    pet: Animal = Dog()

或聯合類型:

from typing import Union

class Human:
    pet: Union[Cat, Dog] = Dog()
  1. 創建一個泛型類
from typing import Generic, TypeVar

Pet = TypeVar("Pet", bound=Animal)

class Human(Generic[Pet]):
    def __init__(self, pet: Pet) -> None:
        self.pet = pet

這仍然不能讓您擁有相同的對象,即今天的Human[Dog]和明天的Human[Cat] ,但是您可以擁有不同的Human[Cat]Human[Dog]對象。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM