为什么元类会改变 issubclass() 的工作方式?

发布于 2024-09-15 09:22:09 字数 2931 浏览 2 评论 0原文

好的,所以我正在编写一个框架,该框架在名为 task.py 的子目录中查找 python 文件,然后查找从基类 Task 派生的类并收集它们。 我决定需要向 Task 添加一个元类,但随后 issubclass() 开始表现得很奇怪。 布局如下:

start.py                
tasks/__init__.py
tasks/base.py
tasks/sub/__init__.py   # empty
tasks/sub/task.py

start.py:tasks/init.pytasks/base.pytasks/sub/task.py

#!/usr/bin/env python
from tasks.base import Task1, Task2
from tasks.sub.task import SubTask1, SubTask2

print "Normal import:"
print "is SubTask1 sub class of Task1? %s" % issubclass(SubTask1, Task1)
print "is SubTask2 sub class of Task2? %s" % issubclass(SubTask2, Task2)

from tasks import setup
print "Imp import:"
setup()

import os.path, imp, types
from tasks.base import Task1, Task2

# Find all task definitions
ALL_TASK1 = { }
ALL_TASK2 = { }
def _append_class(d, task, base):
    if (type(task) == types.TypeType) and issubclass(task, base):
        if task == base:
            return
        if not task.name in d:
            d[task.name] = task

this_dir = os.path.dirname(os.path.abspath(__file__))
for root, dirs, files in os.walk(this_dir):
    if not "task.py" in files:
        continue
    mod_name = "task"
    f, pathname, description = imp.find_module(mod_name, [ root ])
    m = imp.load_module(mod_name, f, pathname, description)
    f.close()

    for task in m.__dict__.itervalues():
        _append_class(ALL_TASK1, task, Task1)
        _append_class(ALL_TASK2, task, Task2)

def setup():
    print "All Task1: %s" % ALL_TASK1
    print "All Task2: %s" % ALL_TASK2

class MetaClass (type):
    def __init__(cls, name, bases, attrs):
        pass

class Base (object): 
    __metaclass__ = MetaClass

    def method_using_metaclass_stuff(self):
        pass

class Task1 (Base):
    pass

class Task2 (object):
    pass 

目录

from tasks.base import Task1, Task2

class SubTask1 (Task1): # Derived from the __metaclass__ class
    name = "subtask1"

class SubTask2 (Task2):
    name = "subtask2"

当我运行 setup.py 时 得到以下输出(ALL_TASK1 字典为空!):

Normal import:
is SubTask1 sub class of Task1? True
is SubTask2 sub class of Task2? True
Imp import:
All Task1: {}
All Task2: {'subtask2': <class 'task.SubTask2'>}

但是当我注释掉类 Base 中的 __metaclass__ 行(Task1's base类)我得到了预期的输出(ALL_TASK1 字典不为空):

Normal import:
is SubTask1 sub class of Task1? True
is SubTask2 sub class of Task2? True
Imp import:
All Task1: {'subtask1': <class 'task.SubTask1'>}
All Task2: {'subtask2': <class 'task.SubTask2'>}

我不明白当通过 imp< 导入模块时,元类如何影响 issubclass() /code> 函数,但当使用普通 import 导入模块时则不起作用。

有人可以向我解释一下吗(我正在使用 python 2.6.1)?

OK, so I'm writing a framework that looks for python files in sub directories that are named task.py and then look for classes that are derived from the base class Task and collect them.
I decided that I needed to add a meta class to Task, but then the issubclass() started to behave in a weird way.
Here is how the directory layout looks:

start.py                
tasks/__init__.py
tasks/base.py
tasks/sub/__init__.py   # empty
tasks/sub/task.py

start.py:

#!/usr/bin/env python
from tasks.base import Task1, Task2
from tasks.sub.task import SubTask1, SubTask2

print "Normal import:"
print "is SubTask1 sub class of Task1? %s" % issubclass(SubTask1, Task1)
print "is SubTask2 sub class of Task2? %s" % issubclass(SubTask2, Task2)

from tasks import setup
print "Imp import:"
setup()

tasks/init.py

import os.path, imp, types
from tasks.base import Task1, Task2

# Find all task definitions
ALL_TASK1 = { }
ALL_TASK2 = { }
def _append_class(d, task, base):
    if (type(task) == types.TypeType) and issubclass(task, base):
        if task == base:
            return
        if not task.name in d:
            d[task.name] = task

this_dir = os.path.dirname(os.path.abspath(__file__))
for root, dirs, files in os.walk(this_dir):
    if not "task.py" in files:
        continue
    mod_name = "task"
    f, pathname, description = imp.find_module(mod_name, [ root ])
    m = imp.load_module(mod_name, f, pathname, description)
    f.close()

    for task in m.__dict__.itervalues():
        _append_class(ALL_TASK1, task, Task1)
        _append_class(ALL_TASK2, task, Task2)

def setup():
    print "All Task1: %s" % ALL_TASK1
    print "All Task2: %s" % ALL_TASK2

tasks/base.py

class MetaClass (type):
    def __init__(cls, name, bases, attrs):
        pass

class Base (object): 
    __metaclass__ = MetaClass

    def method_using_metaclass_stuff(self):
        pass

class Task1 (Base):
    pass

class Task2 (object):
    pass 

tasks/sub/task.py

from tasks.base import Task1, Task2

class SubTask1 (Task1): # Derived from the __metaclass__ class
    name = "subtask1"

class SubTask2 (Task2):
    name = "subtask2"

When I run setup.py I got the following output (ALL_TASK1 dict is empty!):

Normal import:
is SubTask1 sub class of Task1? True
is SubTask2 sub class of Task2? True
Imp import:
All Task1: {}
All Task2: {'subtask2': <class 'task.SubTask2'>}

But when I comment out the __metaclass__ line in the class Base (Task1's base class) I got the output I expected (ALL_TASK1 dict isn't empty):

Normal import:
is SubTask1 sub class of Task1? True
is SubTask2 sub class of Task2? True
Imp import:
All Task1: {'subtask1': <class 'task.SubTask1'>}
All Task2: {'subtask2': <class 'task.SubTask2'>}

I don't understand how the meta class can affect issubclass() when the module is imported via the imp functions, but not when the module is imported with the normal import.

Can someone explain this to me (I'm using python 2.6.1)?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

墨离汐 2024-09-22 09:22:09

您的代码也无法通过正常导入运行。

>>> from tasks.base import Task1
>>> type(Task1)
<class 'tasks.base.MetaClass'>
>>> from types import TypeType
>>> type(Task1) == TypeType
False
>>> issubclass(type(Task1), TypeType)
True
>>> 

当您实例化元类(作为类)时,实例的类型不是 TypeType,而是 tasks.base.MetaClass 类型。如果您除了 TypeType 之外还对其进行测试,那么您的代码将按预期工作。

def _append_class(d, task, base):
    if (issubclass(type(task), types.TypeType) and issubclass(task, base):

这会产生与注释掉元类行相同的输出,并且比原始代码有一个优势,因为它允许您的用户定义自己的元类并让它们作为任务运行。如果您想明确禁止这一点(请不要这样做,有一天我可能想使用您的框架)您可以专门检查您的元类或 TypeType

Your code doesn't work via normal import either.

>>> from tasks.base import Task1
>>> type(Task1)
<class 'tasks.base.MetaClass'>
>>> from types import TypeType
>>> type(Task1) == TypeType
False
>>> issubclass(type(Task1), TypeType)
True
>>> 

When you instantiate the metaclass (as a class), the instance doesn't have type TypeType, it has type tasks.base.MetaClass. if you test for that in addition to TypeType, then your code works as expected.

def _append_class(d, task, base):
    if (issubclass(type(task), types.TypeType) and issubclass(task, base):

This yields the same output as when you commented out the metaclass line and has an advantage over your origninal code in that it allows your users to define their own metaclasses and have them function as tasks. If you want to explicitly dissallow this (and please don't, I might want to use your framework someday) you can just check for both your metaclass or TypeType specifically.

要走就滚别墨迹 2024-09-22 09:22:09

当您在 Base 中定义 __metaclass__ 时,type(Base) 变为 tasks.base.MetaClass,而不是 <代码>类型.TypeType。

_append_class 中,您真正关心的似乎只是获取那些属于基类子类的任务。因此,

if (type(task) == types.TypeType) and issubclass(task, base):

您可以只测试 issubclass(task,base) 并捕获异常,否则:

try: isbase=issubclass(task, base)
except TypeError: isbase=False
if isbase: 

When you define __metaclass__ in Base, the type(Base) becomes tasks.base.MetaClass, rather than types.TypeType.

It seems in _append_class that all you really care about is grabbing those tasks which are subclasses of base. So instead of

if (type(task) == types.TypeType) and issubclass(task, base):

you could just test issubclass(task,base) and catch exceptions otherwise:

try: isbase=issubclass(task, base)
except TypeError: isbase=False
if isbase: 
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文