我尝试创建类似于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 ...?
编辑:我也需要在类中保持属性的顺序。
答案 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 }})。