为什么元类会改变issubclass()的行为?
好的,我在写一个框架,这个框架会在子目录中寻找名为 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:
#!/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"
当我运行 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
(也就是 Task1
的基类)中的 __metaclass__
行注释掉时,输出结果就变成我预期的样子(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'>}
我不明白为什么元类会影响 issubclass()
,当模块通过 imp
函数导入时会有这种情况,但正常用 import
导入时却没有。
有人能给我解释一下吗(我用的是 Python 2.6.1)?
2 个回答
当你在 Base
里定义 __metaclass__
时,type(Base)
的类型会变成 tasks.base.MetaClass
,而不是 types.TypeType
。
在 _append_class
这个地方,你其实只关心那些是 base
的子类的任务。所以你可以不需要这样:
if (type(task) == types.TypeType) and issubclass(task, base):
你可以直接测试 issubclass(task,base)
,如果不是子类就捕捉异常:
try: isbase=issubclass(task, base)
except TypeError: isbase=False
if isbase:
你的代码通过正常的导入方式也无法运行。
>>> 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
。