如何在保留订单的同时收集类变量

时间:2014-06-16 03:49:00

标签: python inheritance reflection abstract-syntax-tree metaclass

我尝试创建类似于SQLAlchemy declarative_base的模式类。架构类应该可以使用继承进行扩展。这是我到目前为止的尝试。除了属性(类变量)按字母顺序排序外,它工作正常。

import inspect
class BaseProp(object):
    def __init__(self, name):
        self.name = name
class StringProp(BaseProp): pass
class EmailProp(BaseProp): pass

class BaseSchema(object):
    @classmethod
    def _collect_props(cls):
        prop_filter =  lambda a:not(inspect.isroutine(a)) and issubclass(a.__class__, BaseProp)
        return inspect.getmembers(cls, prop_filter)

    @classmethod
    def as_list(cls):
        return cls._collect_props()

    @classmethod
    def as_dict(cls):
        return dict(cls._collect_props())

class UserIdentSchema(BaseSchema):
    email = EmailProp('email')
    password = StringProp('password')

class UserProfileSchema(UserIdentSchema):
    name = StringProp('name')
    phone = StringProp('phone')

from pprint import pprint
pprint(UserProfileSchema.as_list())

结果如下。请注意,属性按字母顺序排序。

[('email', <__main__.EmailProp object at 0x10518a950>),
 ('name', <__main__.StringProp object at 0x10517d910>),
 ('password', <__main__.StringProp object at 0x10517d8d0>),
 ('phone', <__main__.StringProp object at 0x10517d950>)]

我希望在顶部有基础架构道具,然后从子类获得道具。实现这一目标的最佳方法是什么?我是否必须通过AST ...?

编辑:我也需要在类中保持属性的顺序。

1 个答案:

答案 0 :(得分:2)

所以这是一个有效的解决方案 - 我不保证这是生产有价值的代码......例如如果你试图将另一个(非BaseSchema)类与BaseSchema子类混合,我还没有想过会发生什么。我认为可能会工作,但您需要尝试一下才能看到......

import ast
import inspect
from collections import OrderedDict

class _NodeTagger(ast.NodeVisitor):
    def __init__(self):
        self.class_attribute_names = {}

    def visit_Assign(self, node):
        for target in node.targets:
            self.class_attribute_names[target.id] = target.lineno

    # Don't visit Assign nodes inside Function Definitions.
    def visit_FunctionDef(self, unused_node):
        return None


class BaseProp(object):
    def __init__(self, name):
        self.name = name


class StringProp(BaseProp): pass


class EmailProp(BaseProp): pass


class _BaseSchemaType(type):
    def __init__(cls, name, bases, dct):
        cls._properties = OrderedDict()
        for b in bases:
          # if the object has a metaclass which is this
          # class (or subclass of this class...)
          # then we add it's properties to our own.
          if issubclass(type(b), _BaseSchemaType):
            cls._properties.update(b._properties)

        # Finally, we add our own properties.  We find our own source code
        # read it and tag the nodes where we find assignment.
        source = inspect.getsource(cls)
        nt = _NodeTagger()
        nt.visit(ast.parse(source))
        attr_names = nt.class_attribute_names
        properties = {n: prop for n, prop in dct.items()
                      if isinstance(prop, BaseProp)}
        sorted_attrs = sorted(properties.items(),
                             key=lambda item: attr_names[item[0]])
        cls._properties.update(sorted_attrs)

    # methods on a metaclass are basically just classmethods ...
    def as_list(cls):
      return list(cls._properties.items())

    def as_dict(cls):
      return cls._properties.copy()


class BaseSchema(object):
    __metaclass__ = _BaseSchemaType

class UserIdentSchema(BaseSchema):
    email = EmailProp('email')
    password = StringProp('password')

class UserProfileSchema(UserIdentSchema):
    name = StringProp('name')
    phone = StringProp('phone')

from pprint import pprint
pprint(UserProfileSchema.as_list())

对于元类很抱歉 - 但这确实让事情变得更容易了。现在所有的魔法都发生在导入类时 - 每次调用as_list时都没有杂乱的内省。它还让我可以快速访问该类的属性及其基础,我需要从__dict____bases__取出(或从{{1 }})。