【问题标题】:In Python, is it acceptable to use a metaclass to add and remove many class attributes?在 Python 中,是否可以使用元类来添加和删除许多类属性?
【发布时间】:2018-06-30 01:10:32
【问题描述】:

具体来说,使用类属性创建更多类属性,然后删除原始属性是否可以接受/良好做法?

假设我有一个超类。由此,我派生了许多子类,但所有这些都需要定义一些类属性。如果我正常执行此操作,它看起来像:

class Bar(Foo):
    someval = 0
    someotherval = ('a', 'b', 'c')
    importantval = [1, 2, 3]
    thisval = 'this'
    names = ['John', 'Joe']

每个子类都需要定义所有这些属性。但是,如果我们以某种方式使用一个变量来创建所有这些变量,它会看起来像:

class Bar(Foo):
    classvars = (0, ('a', 'b', 'c'), [1, 2, 3], 'this', ['John', 'Joe'])

然后,在创建类之后,它在内部看起来与我们分别定义所有属性的类的版本相同。

看,我之前尝试过只使用没有元类的超类来做到这一点,例如:

class Foo:
    @classmethod
    def __init__(cls, val):
        cls.val = val

class Bar(Foo):
    Foo.__init__(5)

但是如果你知道@classmethod 是如何工作的,那么你就会知道cls 最终会成为一个类Foo 引用,而不是一个类Bar 引用(在Bar 的创建过程中调用时,并且cls 仅在 Bar 实例创建期间成为 Bar 引用)。然后我也尝试改用@staticmethod,但是据我所知,在创建类时,您不能引用在方法定义之外创建的类。

class Bar(Foo):
    Foo.__init__(Bar, 5)

这会引发 NameError(如果 Foo__init__ 方法是静态方法)。

最终,我了解了元类,并发现我可以做到这一点:

class MetaFoo(type):
    def __init__(cls, name, bases, namespace):
        super(MetaFoo, cls).__init__(name, bases, namespace)
        if '_superclass' not in namespace:  # A special attribute to distinguish between classes that we want to affect and those that we don't
            attrnames = ('someval', 'someotherval', 'importantval', 'thisval', 'names')
            if 'classvars' in namespace:
                for attr, attrname in zip(getattr(cls, 'classvars'), attrnames):
                    setattr(cls, attrname, attr)
                    namespace[attrname] = attr
                delattr(cls, 'classvars')
            else:  # Allow the definitions to be separate, instead of through "classvars", but also make sure that all the required attributes are defined
                for attrname in attrnames:
                    if attrname not in namespace:
                        raise AttributeError('"%s" not found.' % attrname)


class Foo(metaclass=MetaFoo):
    _superclass = True  # The value of this attribute doesn't matter

    def __init__(self, value):
        self.value = value

    def dosomething(self):
        return self.value * self.someval  # Can use type(self).someval for the class attribute, as long as the class itself is never passed as self


class Bar(Foo):
    classvars = (5, ('c', 'b', 'a'), [3, 2, 1], 'This', ['Jimmy', 'Bob'])

简而言之,如果它找到一个名为classvars 的类属性,它会使用它来创建其他类属性,然后删除classvars。在这个例子中,做:

class Bar(Foo):
    classvars = (5, ('c', 'b', 'a'), [3, 2, 1], 'This', ['Jimmy', 'Bob'])

给你一个相同的结果:

class Bar(Foo):
    someval = 5
    someotherval = ('c', 'b', 'a')
    importantval = [3, 2, 1]
    thisval = 'This'
    names = ['Jimmy', 'Bob']

我的主要问题是:以这种方式使用元类是否可以接受(或者由于某种原因通常会避免),尤其是如果您已经出于充分的理由使用元类?

附带的问题是:没有元类有没有办法做到这一点?

【问题讨论】:

  • 我不会那样做,只是因为“显式比隐式好。”
  • @cco 我知道这与编码风格本身有关,但是如果您明确(通过代码/文档中的 cmets)创建 Bar 的两种风格是相同的,那只是那个更快,更有效。如果您有 10 个需要定义的不同类变量和 25 个需要创建的不同子类,那么您至少需要 275 行正常执行此操作,而短途只需 50 行。
  • 我会给你更短的时间,但我怀疑它是否更快(运行,而不是编写)或更高效。对我来说,名称与值的分离是一个重大损失。不同意见同样有效。关于另一种方法的问题,您可以使用类装饰器,这将使 _superclass 舞蹈变得不必要,并使魔法不那么隐藏(一件好事,恕我直言)。
  • 您是否在运行时创建类?你在自动化模块创建吗?您要解决什么问题?- 出于您的目的,为什么具有单个 classvars 属性的子类定义更好 比具有所有类属性的子类定义写出?
  • @cco 我的意思是,就编写代码而言,它会更有效率:而不是花 20 分钟来编写所有子类,它可能只需要 2 分钟。我什至不知道我是怎么做到的会使用类装饰器来做到这一点,但我确实知道如何使用元类来做到这一点(这实际上很容易),这就是我这样做的原因。

标签: python python-3.x class inheritance metaclass


【解决方案1】:

是的,你可以这样做。但我不知道这是否比复制+粘贴所有需要的属性更好。

如果您这样做,为了让查看您的某个子类的代码的人(包括从现在起一周后的您)保持最低限度的可读性,您至少应该使用字典而不是序列。这样,在查看子类时,您将始终知道哪个值映射到每个属性。

另外,请记住,如果至少某些属性具有对许多子类有效的良好默认值,那么继承机制可以很好地保留这些默认值,同时允许您仅显式声明您指定的属性每个子类确实需要有所不同(并且键入的内容比使用字典要少,字典至少需要在名称周围加上额外的引号)。

否则,就语言而言,没关系。您宁愿在基类上有一个特殊的属性名称,允许您在基类主体上输入所需/所需的属性名称,而不是将它们硬编码在元类上:这看起来真的很“不文明”。通过在基类上拥有一个属性,元类可以针对不同的对象层次结构重用。

(只需使用for 迭代元类的bases 参数来检查是否存在具有属性名称的参数)

啊,因为它是问题的一部分:关于使用元类删除在类创建后无用的类属性 - 完全可以。

【讨论】:

  • 就继承而言,我实际情况中的每个属性都必须特定于子类,它们不能有默认值,否则没有意义,因为它们是具体到子类代表什么。使用字典几乎不会消除必须重复名称和键入值的问题。属性名称的超类属性是个好主意,但我没有这样做,因为就我而言,所需的特定属性根本不可能改变(另外,如果需要,我将只使用 cmets 作为名称“提示”)。总的来说,很好,有用的答案。
  • 正如我所说:不,没有什么反对它 - 主要缺点是我概述的那个 - 但没有什么是带有参数顺序的注释行无法解决的。
猜你喜欢
  • 2014-08-13
  • 1970-01-01
  • 2011-08-21
  • 1970-01-01
  • 2019-09-18
  • 1970-01-01
  • 2014-07-13
  • 2020-10-02
  • 2012-08-03
相关资源
最近更新 更多