PyCharm在调用issubclass()后错误地假设对象是BaseClass的示例

wz3gfoph  于 2023-03-12  发布在  PyCharm
关注(0)|答案(2)|浏览(160)

我用Python 3.10.9和PyCharm 2022.2.1(社区版)运行这个测试,这是PyCharm的问题,而不是Python本身。
在我的示例代码中,我使用了一个生成器方法,它接受一个类类型作为参数,并构造这个类。我想确保传递的类类型是BaseClass的子类,所以我在class_type参数上使用了issubclass()

class BaseClass:
    def __init__(self, foo):
        self.foo = foo

class SubClass(BaseClass):
    def __init__(self):
        super().__init__(foo='hi')
        self.bar = "hello"

def generate_sub_class(class_type):
    assert issubclass(class_type, BaseClass)
    new_obj = class_type()      # <--- PyCharm warning: "Parameter 'foo' unfilled"
    return new_obj

# Main
new_subclass = generate_sub_class(SubClass)
print(f"Type of new_subclass: {type(new_subclass)}")
print(f"new_subclass.foo: {new_subclass.foo}")
print(f"new_subclass.bar: {new_subclass.bar}")     # <--- PyCharm warning: "Unresolved attribute reference
                                                   #                        'bar' for class 'BaseClass'"

当在Python中运行时,这段代码可以正常工作,我们可以得到以下输出:

Type of new_subclass: <class '__main__.SubClass'>
new_subclass.foo: hi
new_subclass.bar: hello

然而,正如你在上面的代码中看到的,只要我调用issubclass(class_type, BaseClass),PyCharm就会认为构造的对象是BaseClass对象,构造函数调用会有一个警告,因为它正在查看错误的构造函数,Main中的new_subclass.bar调用会有一个警告,因为它在BaseClass中没有看到bar。尽管Python输出验证了它是SubClass对象。
有人知道为什么会发生这种情况吗?在我看来,这似乎是PyCharm的错误,我不知道为什么简单地调用issubclass会使PyCharm假设对象是BaseClass。如果不调用issubclass,PyCharm就可以理解它是SubClass的示例。
提前感谢您的帮助。

nnsrf1az

nnsrf1az1#

我怀疑这不是一个真正的bug,而是没有提供任何类型提示的副作用。PyCharm从class_type作为Any类型的假设开始,这意味着可以为名称赋任何值,并且该值可以用于任何操作,而不管类型如何。
那么PyCharm对assert语句做了什么呢?它尝试应用类型收缩,当Assert为真时,为class_type分配一个比Any更具体的类型。使AssertTrue生效的最常见类型是BaseClass,所以在Assert之后的代码中,它假设class_type具有BaseClass类型。而不是Any。(如果Assert失败,则不需要进行任何假设,因为下面的代码都不会执行。)
一旦类型被缩小到BaseClass,其余的警告就不言自明了。BaseClass * 不 * 提供BaseClass.__init__期望的参数,并且BaseClass的直接示例 * 不 * 具有bar属性。
在运行时,一切正常,因为没有关于class_type类型的假设;它 * 是 * SubClass,因此代码按预期执行。
也就是说,我没有安装PyCharm进行测试。mypy什么也不做,这使我怀疑PyCharm在执行类型收缩方面比mypy过于激进。

from typing import TypeVar
T = TypeVar('T', bound=BaseClass)

def generate_sub_class(class_type: type[T]) -> T:
    assert issubclass(class_type, BaseClass)
    new_obj = class_type()
    return new_obj

这里,类型提示提供了可从assert语句推断出的相同量的信息,但由于类型提示已经暗示T可被绑定到BaseClassBaseClass的子类,因此不应需要或应用类型缩窄。

qkf9rpyu

qkf9rpyu2#

所以多亏了@chepner,抑制PyCharm警告并使PyCharm正确理解类型的最好方法是使用类型提示而不是Assert。

from typing import TypeVar, Type
T = TypeVar('T', bound=BaseClass)

def generate_sub_class(class_type: Type[T]) -> T:
    new_obj = class_type()
    return new_obj

所有警告现在都消失了。

相关问题