我正在尝试创建类似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…吗?
编辑:我需要保持一个类中的属性的顺序,以及
这是一个可行的解决方案——我不保证这是具有生产价值的代码……例:我没有想太多会发生什么,如果你试图混合在另一个(非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__
中取出(或从inspect
中找出)。