首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >类组合方法的Python类型返回类型

类组合方法的Python类型返回类型
EN

Stack Overflow用户
提问于 2022-05-24 21:07:46
回答 1查看 233关注 0票数 1

我有一个类Human,它可以有一个宠物和一个让它说话的方法,可以是Dog,也可以是Cat。我用一个Human初始化Dog,方法pet_speak有正确的返回类型提示Literal["Woof"]

如果在初始化后将Human的宠物更改为Cat,则不会将方法返回类型提示更新为Literal["Meow"]

有办法改变这种行为吗?这是我问题的一个简化版本,创建两个类,例如,HumanDogHumanCat更复杂。

代码语言:javascript
复制
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"]

我能找到的最接近的问题是Can you type hint overload a return type for a method based on an argument passed into the constructor?,但它也增加了每个Human方法和子类的开销。

对不起,如果我把这个问题说得太多了,这里与我想要做的事情更相似:

代码语言:javascript
复制
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
EN

回答 1

Stack Overflow用户

发布于 2022-05-24 22:06:00

如果添加缺少的注释,通过Pyright (或Mypy)运行代码,它将在这里发现一个错误:

代码语言:javascript
复制
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。通常情况下,对象的类型在发生变异时不会改变。这是有意义的,例如,因为对象可能是共享的:

代码语言:javascript
复制
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()

这将是非常困难的,如果不是不可能的类型检查跟踪这样的情况。

我不知道你在解决什么真正的问题,但我有两个解决方案:

pet

  1. 指定一种更通用的类型

公共协议/基类:

代码语言:javascript
复制
class Human:
    pet: Animal = Dog()

或者工会类型:

代码语言:javascript
复制
from typing import Union

class Human:
    pet: Union[Cat, Dog] = Dog()

  1. 创建一个泛型类

代码语言:javascript
复制
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]对象。

票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/72369483

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档