【问题标题】:Make static class in Python在 Python 中创建静态类
【发布时间】:2020-01-15 19:03:06
【问题描述】:

我喜欢创建可以被其他类使用并且所有方法都是静态的帮助类(staticmethod)。我需要用装饰器 @staticmethod 包装每个方法,但这种解决方案在我看来不是很美观。我决定为这些类创建一个元类 - 工具,这里是一个抽象实现示例:

import types
import math


class StaticClass(type):
    def __new__(mcs, name, bases, attr):
        for name, value in attr.items():
            if type(value) is types.MethodType:
                attr[name] = staticmethod(value)
        return super().__new__(mcs, name, bases, attr)


class Tool(metaclass=StaticClass):

    def get_radius_from_area(area):
        return math.sqrt(area/Tool.get_pi())

    def get_pi():
        return math.pi


class CalcRadius:
    def __init__(self, area):
        self.__area = area

    def __call__(self):
        return Tool.get_radius_from_area(self.__area)


if __name__ == '__main__':
    get_it = CalcRadius(100)
    print(get_it())  # 5.641895835477563

一切正常并给出正确的结果,但 IDE 中的代码检查存在可理解和可预测的问题(我使用 Pycharm 2019.2)。

  • 对于 def get_radius_from_area(area)通常是名为“self”的方法的第一个参数。 'area' 在方法的返回中以黄色突出显示。

  • 对于 get_pi()方法必须有第一个参数,通常称为'self' 没有参数的括号中的空白用红色下划线。

如果我在类上方添加行“#noinspection PyMethodParameters”,这部分解决了问题,但它看起来比几十个@staticmethods还要糟糕。

我理解为什么会发生这种情况,以及为什么 JetBrains 的开发人员专门针对 Django 调整其 IDE 中的部分代码。

但我能以某种方式精美地创建一个纯静态类,其中所有方法都是静态的吗? 也许元类不是最好的选择,是否有某种替代解决方案?

【问题讨论】:

  • 相关:stackoverflow.com/questions/30556857/…。我认为您正试图强迫 Python 做一些它并不真正想做的事情。我建议要么只处理所有类方法上的@staticmethod,要么将其全部移到一个模块中。对于它的价值,我认为大多数 Python 开发人员会不同意您对 @staticmethod 的审美抱怨。在你所有的方法中使用它比你可以想出的任何迂回方法更清楚地传达你的意图,并且清晰是好的。
  • @skrrgwasme,关于代码的可读性,我同意你的看法,嗯,美学是一个品味问题 :)

标签: python python-3.x pep8


【解决方案1】:

您的元类实际上并没有做任何事情,因为types.MethodType 只匹配绑定的方法对象。当您浏览 __new__ 中的类命名空间时,您不会得到任何这些,因此您永远不会在 staticmethod 中包装任何内容。

您可以通过将检查更改为types.FunctionType 来修复它(这可能会满足能够正确查看方法类型的自动化工具):

class StaticClass(type):
    def __new__(mcs, name, bases, attr):
        for name, value in attr.items():
            if type(value) is types.FunctionType:
                attr[name] = staticmethod(value)
        return super().__new__(mcs, name, bases, attr)

但我建议直接取消类并直接使用函数。函数是 Python 中的第一类对象,您可以在对象之间任意传递它们。如果您想要方便地对它们进行分组,可以将它们放在列表或字典中,或者使用模块将它们的代码收集到各种分组中(并使用包对模块进行分组)。我还建议您避免使用前导双下划线 __names 来尝试通过调用名称修改来获得您的属性的隐私。它实际上并没有保护您的数据不受任何影响(外部代码可以仍然可以获取它),并且它使调试变得更加困难。它包含在 Python 中是为了帮助您避免意外的名称冲突,而不是理所当然地保护成员变量。

【讨论】:

  • 感谢关于 types.FunctionType 的解释。至于双下划线,我知道访问它很容易 - get_it._CalcRadius__area。这可能是 C++ private 之后仍然存在的习惯。
【解决方案2】:

为什么不在模块级别而不是类封装您的代码,并为您的用户提供功能?

你的代码可以这么简单:

import math


def calc_radius(area):
    return math.sqrt(area/math.pi)


print(calc_radius(100))

【讨论】:

  • 这只是我在旅途中发明的一个抽象示例。如果你重新表述你的问题:“为什么我不能使用函数而不是静态方法?”,那么我会回答 - “我不喜欢混合功能级别和 OOP”,在我看来,这是不可接受的在同一个脚本中使用两者,虽然这当然不是脱色的。
  • @YeapyBen。在这种情况下,使用额外级别的命名空间似乎适得其反。静态类创建命名空间,模块创建命名空间。对您而言,它们之间没有实际区别。
  • @MadPhysicist,这不是它在低级别实现的方式,而是它的外观。
  • @YeapyBen。这就是我所指的。从模块访问方法的方式与类相同,只是没有额外的无意义的间接级别。在我看来,如果您从不打算实例化该类,那么拥有它看起来很糟糕。
  • 您说的是美学,但除了工作保障之外,证明元类合理的理由为零! @MadPhysicist 优雅地表述为一个简单的命名空间(这里是一个带有函数的模块)可以满足您的需求,我坚持我的回答!
【解决方案3】:

您还可以创建一个类装饰器,我个人更喜欢它而不是美学上的元类:

import types

def staticclass(cls):
    for name, value in vars(cls).items():
        if isinstance(value, types.FunctionType):
            setattr(cls, name, staticmethod(value))
    return cls

这样:

@staticclass
class Tool:

    def get_radius_from_area(area):
        return math.sqrt(area/Tool.get_pi())

    def get_pi():
        return math.pi


class CalcRadius:
    def __init__(self, area):
        self.__area = area

    def __call__(self):
        return Tool.get_radius_from_area(self.__area)


if __name__ == '__main__':
    get_it = CalcRadius(100)
    print(get_it())

输出:5.641895835477563

编辑:@Blckknght 正确指出函数不是绑定方法,直到它实际绑定到一个实例,而类对象不是。切换到isinstance(value, types.FunctionType) 将允许正确包装。

【讨论】:

  • 哦,这是一个很好的解决方案,我没想到。非常感谢!
  • UPD:不幸的是,问题仍然存在...... IDE 不像元类那样理解装饰器。总的来说,这里没有任何好处,Python 和 PyChart 坚持认为我的语法有误。但是,他们可能在某些方面是正确的:D
猜你喜欢
  • 2018-08-07
  • 2011-11-20
  • 1970-01-01
  • 2016-05-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-08-03
相关资源
最近更新 更多