考虑这个玩具混合:
class MyMixin:
def foo(self):
return self.x + self.y
它与实际提供 x
和 y
的类一起使用。但是:
- linter 不知道这一点,所以提示
- 我可以创建一个类及其实例,但当我尝试使用
foo
(请参阅下一个代码)
class A(MyMixin):
def __init__(self, x=0):
self.x = x
a = A(10) # no problem!
a.foo() # problem: AttributeError: 'A' object has no attribute 'y'
相反,我想要
- linter 不会提示
MyMixin
中缺少x
和y
- 当我在没有提供必要属性的情况下使用它时收到通知(此通知可以以 linter 的形式(所有可选)出现,在创建
A
时出现语法错误,和/或在实例化时出现语法错误)A
)
我觉得元类可以做到这一点,但在提到元怪物时已经可以听到悲惨的故事了。
还有其他建议吗?
最佳答案
首先关注您的主要问题:在创建使用 mixin 的类时如何知道是否缺少属性。事实上,Python 代码没有简单的方法,即使它具有自省(introspection)功能来了解方法内设置或需要哪些属性。
Linters 通过“作弊”来实现这一点:它们解析代码并从 Python 代码的“外部”而不是从“内部”静态地查看它。即:它们遵循源代码的文本,而通过在运行时使用内省(introspection),我们将函数作为对象,并且我们必须跟踪其字节码以查看它将使用哪些属性。
但是,如果您可以接受在类主体本身中声明所需的属性,我认为如果您合并需要类不知道的属性的方法,则可以使 linter 保持沉默,并且让 Python 尖叫。
我们将使用抽象基类提供的功能,并稍微扩展它们,以便必须使用 mixin 在类中覆盖所需的属性。
所以这里是,用一些样板来消除 linter 中的其他警告:
"""module doc"""
from abc import ABC, abstractmethod
# This thing works as an "abstract attribute":
# The class can't be instantiated unless it is overriden
# in the class declaration:
AbstractField = lambda: property(abstractmethod(lambda s: s))
class Aa(ABC):
"""doc"""
x = AbstractField()
y = AbstractField()
def b_b(self):
"""doc"""
return self.x + self.y
class Bb(Aa):
"""doc"""
x = 0
y = 0
def __init__(self):
"""doc"""
print(self.b_b())
# Class 'Bb' can be declared, but if both x and y are not
# declared in the class body, instantiating it would raise
# a runtime error, due to the abstractclass mechanism:
b = Bb()
(env39) [gwidion@village tmp01]$ pylint -d R0903 module.py
--------------------------------------------------------------------
Your code has been rated at 10.00/10 (previous run: 10.00/10, +0.00)
现在,由于您正在关注 linter 和类似的工具,这些工具有时会带来更多的麻烦,而不是它们的帮助,您可能会想要创建 也为这些属性提供适当的注释。
我不太精通正确的类型提示,但创建“属性”的联合和字段在运行时应包含的实际类型似乎可以完成这项工作(即:MyPy 运行时没有错误或警告,linter 确实如此)不显示错误,代码按预期工作):
"""module doc"""
from abc import ABC, abstractmethod as abstract
from typing import Union
ABIntField = Union[property, int]
AbstractField = lambda: property(abstract(lambda s: s))
class Aa(ABC):
"""doc"""
x: ABIntField = AbstractField()
y: ABIntField = AbstractField()
def b_b(self):
"""doc"""
return self.x + self.y
class Bb(Aa):
"""doc"""
x = 0
y = 0
def __init__(self):
"""doc"""
print(self.b_b())
b = Bb()
我试图想出其他方法来实现这一点 - 尽管有可能(如果很难)有一个元类(或一些 __init_subclass__
代码)来找出所需的属性mixin 方法,无需在主体中声明它们,相反:让 linter 知道“那些属性已被覆盖”是不可能的。
上述方式是“显而易见的事情”。根据项目的不同,可能值得在 AbstractField
上打包更多功能,达到不需要 ABIntField
注释的程度。
关于python - 让 python mixins 使用更安全(并告诉 linter 闭嘴),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/66973562/