简体   繁体   English

暗示一个类正在实现协议的正确方法?

[英]Correct way to hint that a class is implementing a Protocol?

On a path of improvement for my Python dev work.在改进我的 Python 开发工作的道路上。 I have interest in testing interfaces defined with Protocol at CI/deb building time, so that if a interface isn't actually implemented by a class we will know immediately after the unit tests run.我有兴趣在 CI/deb 构建时测试使用协议定义的接口,这样如果接口实际上没有由类实现,我们将在单元测试运行后立即知道。

My approach was typing with Protocol and using implements runtime_checkable to build unit test.我的方法是使用 Protocol 键入并使用实现 runtime_checkable 来构建单元测试。 That works, but the team got into a little debate about how to indicate a concretion was implementing a Protocol without busting runtime_checkable.这行得通,但团队就如何表明一个具体是在不破坏 runtime_checkable 的情况下实现一个协议进行了一些争论。 In C++/Java you need inheritance to indicate implementations of interfaces, but with Python you don't necessarily need inheritance.在 C++/Java 中,您需要继承来指示接口的实现,但在 Python 中,您不一定需要继承。 The conversation centered on whether we should be inheriting from a Protocol interface class.讨论的中心是我们是否应该从协议接口类继承。

Consider this code example at the end which provides most of the gist of the question.最后考虑这个代码示例,它提供了问题的大部分要点。 We were thinking about Shape and indicating how to hint to a future developer that Shape is providing IShape, but doing so with inheritance makes the runtime_checkable version of isinstance unusable for its purpose in unit-testing.我们正在考虑 Shape 并指示如何向未来的开发人员暗示 Shape 正在提供 IShape,但是这样做会导致 isinstance 的 runtime_checkable 版本无法用于单元测试。

There is a couple of paths to improvement here:这里有两条改进途径:

We could find a better way to hint that Shape implements IShape which doesn't involve direct inheritance.我们可以找到一种更好的方法来暗示 Shape 实现了不涉及直接继承的 IShape。 We could find a better way to check if an interface is implemented at test deb package build time.我们可以找到一种更好的方法来检查接口是否在测试 deb 包构建时实现。 Maybe runtime_checkable is the wrong idea.也许 runtime_checkable 是错误的想法。

Anyone got guidance on how to use Python better?有人获得有关如何更好地使用 Python 的指导吗? Thanks!谢谢!


from typing import (
    Protocol,
    runtime_checkable
)
import dataclasses

@runtime_checkable
class IShape(Protocol):
    x: float


@dataclasses.dataclass
class Shape(IShape):
    foo:float = 0.

s  = Shape()
# evaluates as True but doesnt provide the interface. Undermines the point of the run-time checkable in unit testing
assert isinstance(s, IShape)
print(s.x)  # Error.  Interface wasnt implemented




#
# Contrast with this assert
#
@dataclasses.dataclass
class Goo():
    x:float = 1

@dataclasses.dataclass
class Hoo():
    foo: float = 1

g = Goo()
h = Hoo()
assert isinstance(g, IShape)  # asserts as true
# but because it has the interface and not because we inherited.
print(g.x)


assert isinstance(h, IShape)  # asserts as False which is what we want

When talking about static type checking, it helps to understand the notion of a subtype as distinct from a subclass.在谈论静态类型检查时,有助于理解子类型与子类不同的概念。 (In Python, type and class are synonymous; not so in the type system implemented by tools like mypy .) (在 Python 中,类型和类是同义词;在mypy等工具实现的类型系统中则不然。)

A type T is a nominal subtype of type S if we explicitly say it is.类型T是类型S名义子类型,如果我们明确地说它是。 Subclassing is a form of nominal subtyping: T is a subtype of S if (but not only if) T is a subclass of S .子类化是名义子类化的一种形式:如果(但不仅是) TS的子类, TS的子类型。

A type T is a structural subtype of type S if it something about T itself is compatible with S .类型T是类型S结构子类型,如果它关于T本身的某些内容与S兼容。 Protocols are Python's implementation of structure subtyping.协议是 Python 对结构子类型化的实现。 Shape does not not need to be a nominal subtype of IShape (via subclassing) in order to be a structural subtype of IShape (via having an x attribute). Shape不需要是IShape的名义子类型(通过子类化)才能成为IShape的结构子类型(通过具有x属性)。

So the point of defining IShape as a Protocol rather than just a superclass of Shape is to support structural subtyping and avoid the need for nominal subtyping (and all the problems that inheritance can introduce).因此,将IShape定义为Protocol而不仅仅是Shape的超类的重点是支持结构子类型化并避免名义子类型化的需要(以及继承可能引入的所有问题)。

class IShape(Protocol):
    x: float


# A structural subtype of IShape
# Not a nominal subtype of IShape
class Shape:
    def __init__(self):
        self.x = 3

# Not a structural subtype of IShape
class Unshapely:
    def __init__(self):
        pass


def foo(v: IShape):
    pass

foo(Shape())  # OK
foo(Unshapely())  # Not OK

So is structural subtyping a replacement for nominal subtyping?那么结构子类型是否可以替代名义子类型? Not at all.一点也不。 Inheritance has its uses, but when it's your only method of subtyping, it gets used inappropriately.继承有它的用途,但是当它是你唯一的子类型化方法时,它会被不恰当地使用。 Once you have a distinction between structural and nominal subtyping in your type system, you can use the one that is appropriate to your actual needs.一旦您在类型系统中区分了结构子类型和名义子类型,就可以使用适合您实际需要的子类型。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM