1

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

AST를 수행 할 필요는 없지만 직접 클래스 계층 구조를 살펴야합니다. – BrenBarn

+0

@BrenBarn -'email'과'password'가 어떻게 정렬되는지 신경 쓰면, AST가 BaseSchema 메타 클래스 ('type')에 전달되기 때문에 AST를 볼 필요가 있습니다. – mgilson

+0

참고, 했어요. [이 답변] (http://stackoverflow.com/a/20381633/748858)과 비슷한 것입니다. 가장 큰 차이점은 대답은 상속을 다루지 않았지만 여기서 중요하다는 것입니다. 그래도 상속을 다루기 위해 이전 대답을 확장하는 것은 그리 어렵지 않을 것입니다 ... – mgilson

답변

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__을 꺼내야하는 기본입니다 (또는 inspect에서 찾아냅니다).

+0

와우 이것은 매우 멋지다. 나는 원래의 질문에 대한 답보다 당신의 코드에서 뭔가를 배웠다. –

+0

@KenjiNoguchi - 메타 스노우에 관해 생각할 새로운 것을 나에게 주었다. 나는 내가 실제로 그들을 이해할 수있는 지점에 다가 가고 있다고 생각한다. (비록 내가 아직 완전히 확신 할 수없는 몇 가지 모서리가있다. 예를 들어,'Metaclass .__ call__'. 나는 그것이 어떻게 작동해야하는지에 대한 생각을 가지고있다. 그러나 나는 그것을 시험해 볼 시간이 없었다). 어쨌든, 당신은 정말로 여기에 메타 클래스를 _ 필요하지 않습니다. 여러분은 너무 많은 변경없이 내'__init__'을 가져 와서'_collect_props'에 드롭 할 수 있습니다 ...하지만 ... 어쨌든 재미 있습니다 :-) – mgilson

관련 문제