定义包含什么类

5

好的,这可能是一个非常愚蠢的问题,可能不太可能,或者我在以扭曲的方式思考。所以,请考虑以下内容:

class MyClass:
    def __init__(self, x):
        self.x = x
        self.y = self.x ** 2


class MyList:
    def __init__(self, list):
        self.list = list

    def __iter__(self):
        return iter(self.list)

foo = [MyClass(x=1), MyClass(x=2)]
bar = MyList(list=foo)

for i in bar:
    print(i.x, i.y)

好的,这里有两个类:MyClass只是一些很普通、没什么特别的东西。它有两个实例属性:xyMyList则应该是一个类,定义了一个迭代器,其中只包含来自第一个类MyClass的元素!
然后我创建了两个MyClass的实例,把它们放在一个列表foo中,并使用这个列表创建了一个MyList的实例(bar)。
现在,再次强调,传递给MyList的列表应该只包含MyClass元素!我想让Python知道这一点!当我编写一个循环,遍历bar的内容时,我希望Python知道bar中的元素是MyClass对象。
目前我程序中的一个主要不便之处是Python不知道这一点,因此自动补全无法工作。换句话说:在循环中,当我写i.时,我希望所有可能的参数都会弹出(在这种情况下是xy)。我能否在Python中为MyList定义某些神奇的方法,使其知道这个列表的内容?

我认为有两个独立的问题:如何强制MyList仅包含MyClass的实例(注意:我会将MyList作为list的子类并覆盖append、init等),以及如何使自动完成工作(注意:__dict__)。 - Cedric H.
6
自动补全不是 Python 的一部分,而是集成开发环境 (IDE) 的功能。 - furas
2
就此而言,通常 Python 的哲学是代码在接受输入时要“宽容”,在输出时要“严格”(参见鲁棒性原则);对类/函数所接受的数据施加不必要的限制会使它们变得不够灵活和可重用,因此只有在有充分理由的情况下才应该这样做。 - PM 2Ring
4
顺便说一句,“list”是一个不好的变量名,因为它会遮盖内置的“list”类型,这可能会导致神秘的错误。此外,“self.y = x * x”比“self.y = self.x ** 2”更简单。 - PM 2Ring
Python通常使用“鸭子类型”,它不被认为是一种类型化的语言,而是一种动态的语言。然而,你希望在参数和返回值上提供一些保证并不是新的想法,有一些方法可以改进Python:https://duckduckgo.com/?q=python+type+hints - Ulrich Eckhardt
3个回答

3
如果您正在使用PyCharm集成开发环境,那么您可以按照以下步骤操作:
for i in bar:
    assert isinstance(i, MyClass)

或者是这个:
for i in bar: # type: MyClass

是的,我正在使用PyCharm,尽管这不是适用于所有环境的解决方案,但至少目前对我有效!非常感谢这个提示!在这里需要注意的一点是:我并不是很喜欢assert语句,因为它在某些情况下会被忽略。 - HansSnah
一个后续问题:我猜这不可能添加到__iter__魔法方法中,所以IDE自动知道而不必一直在循环旁边提到它...!? - HansSnah
对于任何想了解上述问题的人:在PyCharm中,当我们在MyList类中编写self.list = list # type: list[MyClass]时,所有这些都是可能的。 - HansSnah

2

PEP 484

在Python ≥ 3.0中,您可以使用函数注释(PEP 3107)和PEP 0484的类型提示语义。尽管后者只被Python 3.5接受,并且在3.6版发布之前将是临时的, 但它在语法上向后兼容支持PEP 3107的所有Python版本,因此在任何3.x版本的Python中使用类型提示注释至少不会有害处[1]

无论对于Python ≥ 3.5,它是否有助于您的IDE或交互式解释器(REPL)更好地自动完成取决于该IDE或解释器,甚至可能取决于其设置。

对于Python 2,有一种使用注释的替代符号可用,但支持PEP 0484的工具可能会或可能不会尊重它。
添加您关心的类型提示
让我们看看基于注释的提示(Python 3.x)在您的代码中的样子。(基于注释的、兼容Python 2.x的提示留给读者作为练习。)
为了表示迭代MyList实例会产生MyClass对象,我们在函数定义的冒号后面加上一个->(由减号和大于号组成的箭头),后面跟着返回值的类型来提示__iter__()的返回类型。 __iter__()本身不返回MyClass,它返回一个迭代器。为了表示它将是一个MyClass的迭代器,我们使用通用 Iterator抽象基类来自typing模块[2]
from typing import Iterator

class MyClass:
    # ...

class MyList:
    # ...

    def __iter__(self): -> Iterator[MyClass]
        return iter(self.list)

为了能够兑现这个承诺,`self.list` 必须仅包含 `MyClass` 实例。因此,让我们友好地要求我们的调用者通过在 `__init__()` 参数上添加类型提示来提供这样的实例:
from typing import Iterator, Iterable

# ...

class MyList:
    def __init__(self, list: Iterable[MyClass]):
        self.list = list

    def __iter__(self): -> Iterator[MyClass]
        return iter(self.list)

请注意,我选择了泛型抽象基类Iterable,而不是更具体的List泛型抽象基类(也不是MappingSequenceAbstractSet),因为你的实现仅依赖于iter(...)

[1] 除非过度使用会影响可读性。所以,如Mark所写的那样,“如果你确实需要使用类型提示,请负责任地使用它们”。

[2] Python ≥ 3.5版本已经包含了类型提示。对于低版本,可以使用backport,通过pip install typing安装。


非常详细的解释,非常感谢!我会尝试将所有这些内容包含在我的代码中。 - HansSnah

1

在运行代码之前,Python不会意识到任何类型。只有在检查类型并根据类型执行操作时才会发生这种情况。

您需要的是开发代码时的IDE/REPL功能。在默认的Python REPL中,可能无法使用此类查找。在更复杂的环境中,实际上会发生这种情况,您可以查看给定类的属性。

例如,在我经常使用的IPython REPL中,在循环内部时:

for i in bar:

当我按下 i. 并敲击 tab 键时,两个元素 i.x i.y 都会显示出来。这是因为 REPL 已经被开发用于在编写代码时提供更多的内省。我相信大多数知名的 IDE 都提供了这一功能;Python 的默认 REPL 相当简单(3.5 版本有一些自动完成功能,不过!:D),并且没有提供太多的功能。

实际上,即使是Python 2的REPL也可以进行一些自动完成(至少在*nix系统上),但您需要启用它。最简单的方法是使用适当的PYTHONSTARTUP脚本来完成,如rlcompleter文档所示。 - PM 2Ring
感谢对此的澄清。我之前并不知道REPL功能取决于IDE。 - HansSnah

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接