【问题标题】:Weird inheritance with metaclasses元类的奇怪继承
【发布时间】:2012-08-20 21:17:46
【问题描述】:

在尝试从具有元类的类继承时,我在 Python 中遇到了一些非常奇怪的问题。我有这个:

class NotifierMetaclass(type):

    def __new__(cls, name, bases, dct):

        attrs = ((name, value) for name, value in dct.items()
                    if not name.startswith('__'))

        def wrap_method(meth):
            return instance_wrapper()(meth) # instance_wrapper is a decorator of my own

        def is_callable(value):
            return hasattr(value, '__call__')

        decorated_meth = dict(
            (name, value) if not is_callable(value)
            else (name, wrap_method(value))
            for name, value in attrs
        )

        return super(NotifierMetaclass, cls).__new__(
            cls, name, bases, decorated_meth
        )


class Notifier(object):

    def __init__(self, instance):
        self._i = instance

    __metaclass__ = NotifierMetaclass

然后,在 notifiers.py 中:

from helpers import Notifier

class CommentNotifier(Notifier):

    def __notification__(self, notification):
        return '%s has commented on your board' % self.sender

    def __notify__(self):
        receivers = self.retrieve_users()
        notif_type = self.__notificationtype__()
        for user in receivers:
            Notification.objects.create(
                object_id=self.id,
                receiver=user,
                sender_id=self.sender_id,
                type=notif_type
            )

但是,当我尝试导入 CommentNotifier 时,它会返回 Notifier。在外壳中:

$ python
Python 2.7.3 (default, Apr 20 2012, 22:44:07) 
[GCC 4.6.3] on linux2
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
>>> from logic.notifiers import CommentNotifier
>>> CommentNotifier
<class 'helpers.CommentNotifier'>

事实上,这(至少我是这么认为的)实际上与我一周前遇到的一些Django models 的问题相同。起初我认为这与 Django 的工作方式有关,但现在我怀疑它更像是元类和继承的 Python“问题”。
这是一个已知问题还是我只是做错了什么?希望你能帮助我。
编辑:我忘了提到我将此“错误”归因于元类,因为如果我不给通知程序一个元类,它会按预期工作。

【问题讨论】:

  • 您的示例对我来说是正确的。你没有在“东西”部分省略一些重要的东西吗?请提供一个实际表现出您所指行为的示例。
  • 刚刚编辑了真实案例的代码
  • 我得到&lt;class 'helpers.CommentNotifier'&gt;(仅使用两个文件,helpers.pynotifiers.py),如果我把它放在一个包中,则得到&lt;class 'logic.helpers.CommentNotifier'&gt;。尝试进一步减少它,并在不同的地方粘贴一些打印语句,以排除您没有导入您认为的版本的可能性。
  • 我也得到了@DSM 看到的行为。我相信您的问题不在于它导入了错误的类,而是它给了它错误的名称(或者至少是错误的__module__)。 This question 提到了同样的问题,但没有答案。似乎__module__ 被错误地设置为元类的模块而不是类的模块。我仍在试图弄清楚。 . .

标签: python inheritance metaclass


【解决方案1】:

好吧,我想我明白了。正在导入正确的类。它只是有错误的名字。如果您在类上设置属性,您应该能够看到这一点。如果您将 someJunk = "Notifier" 放入 Notifier 定义中并将 someJunk = "CommentNotifier" 放入 CommentNotifier 定义中,那么当您导入 CommentNotifier 时,它将具有正确的值。

问题在于,在创建attrs 时,您排除了所有双下划线属性,包括__module__。当您调用超类__new__ 时,您传入了您的attrs,它没有__module__ 条目,因此Python 为您创建了一个。但是由于这段代码是在包含元类的文件中执行的,因此模块被错误地设置为元类的文件,而不是实际类的文件。

我没有看到您观察到的类的实际名称的行为,仅适用于模块。也就是说,对我来说,导入的类名为metafile.CommentNotifier,其中metafile 是包含元类的文件。它应该命名为submeta.CommentNotifier,其中submeta 是包含CommentNotifierClass 的文件。我不知道你为什么会在__name__ 上看到它,但如果模块/名称分配的一些微妙处理在不同的 Python 版本中有所不同,我也不会感到惊讶。

__notify____notification__ 不是 Python 魔术方法。您似乎排除了双下划线方法,因为您使用双下划线来表示出于您自己的目的。你不应该这样做。如果必须,请为您自己的方法使用其他前缀(例如 _Notifier 或其他东西),然后排除这些方法并单独保留双下划线的方法。排除双下划线方法可能会导致其他问题。特别是,如果您决定在使用此元类的类上定义真正的魔术方法(例如,__str__),则会导致失败。

(澄清一下:如果需要,您可以使用以双下划线开头的方法作为私有属性,尽管这仍然可能不是一个好主意。但是,如果您这样做,您需要确保您只对这些属性进行特殊处理,而不是那些以 以双下划线结尾的属性,这是 Python 内部的魔术方法。你不应该做的是创建自己的名称以双下划线开头和结尾,例如__notify__。)

【讨论】:

  • +1,但我发现类名差异确实令人费解。我也在使用 2.7.3。
  • 不是 +1 而是 +1000,你真的给了我一堂 Python 课。首先,我要为“helpers.Notifier”产生的所有混乱道歉。实际上解释器所说的是“helpers.CommentNotifier”,但我不知何故修改了文本而没有注意到。对于那个很抱歉。其次,您的猜测是完全正确的,如果我省略了对'__' 的检查,它实际上会正确设置__module__ 属性并保留__foo__ 方法。最后但并非最不重要的一点是方法命名的事实。我从未对__foo__ 命名完全满意,但它确实有效。我应该重新考虑一下。
猜你喜欢
  • 2011-03-13
  • 1970-01-01
  • 2014-06-30
  • 1970-01-01
  • 1970-01-01
  • 2012-11-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多