Почему мета класс меняет способ работы issubclass ()? - PullRequest
3 голосов
/ 24 августа 2010

ОК, поэтому я пишу каркас, который ищет файлы python в подкаталогах с именем task.py, а затем ищет классы, которые являются производными от базового класса 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()

задачи / 1012 * 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

задачи / 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 

задачи / суб / 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 dict пуст!):

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'>}

Но когда я закомментировал строку __metaclass__ в классе Base (базовый класс Task1), я получил ожидаемый вывод (текст 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 ]

2 голосов
/ 24 августа 2010

Ваш код также не работает при обычном импорте.

>>> 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.

0 голосов
/ 24 августа 2010

Когда вы определяете __metaclass__ в Base, type(Base) становится tasks.base.MetaClass, а не types.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: 
...