代码之家  ›  专栏  ›  技术社区  ›  Pat

可以修饰类属性吗

  •  3
  • Pat  · 技术社区  · 6 年前

    如何在构建类时收集有关类属性的信息?

    在爪哇,我所要求的是可能的。

    在Python中,情况似乎并非如此。如果我错了就纠正我!

    我正在构造一个声明性定义的SQLAlchemy对象。

    class Foo(BASE):
       id = Column(Integer, primaryKey=True, nullable=False)
       text = Column(String(100))
    

    我想这样定义类:

    class Foo(declarative_base()):
       @persisted_property(id=true)
       id = Column(Integer, primaryKey=True, nullable=False)
    
       @persisted_property(mutable=True)
       text = Column(String(100))
    
       @persisted_property(set_once=True)
       created_by = Column(Integer)
    
       @classmethod
       def builder(cls, existing=None):
           return Builder(cls, existing)
    

    这个 persisted_property 类/函数/?目的是收集类属性。 有了这些知识,这些事情就会发生:

    1. builder() 类方法将添加到类中 Foo 返回生成的foobuilder。这个 FooBuilder 会有这些方法: set_text()->FooBuilder , set_created_by()->FooBuilder , build()->Foo

    2. (理想情况下)直接改变 对象将被阻止。(如何让SQLAlchemy工作?)

    示例行为:

    1. Foo.builder().set_text("Foo text").set_created_by(1).build()
    2. Foo.builder(existing_foo).set_text("Foo text").set_created_by(1).build() :将引发异常,因为 existing_foo 已经有一个值 created_by

    笔记:

    1. 添加一个类级装饰器将属性定义与装饰分离开来,并且感觉…错误的
    2. 类级别的装饰发生在sqlacalchemy变魔术之后。(这可能是好的也可能是坏的)

    选择?建议?

    2 回复  |  直到 6 年前
        1
  •  3
  •   Martijn Pieters    6 年前

    这个 @callable decorator语法确实是 def 功能和 class 类语句。然而,这只是 语法甜头 .

    语法

    @name(arguments)
    def functionname(...):
        # ...
    

    转换为:

    def functionname(...):
        # ...
    functionname = name(arguments)(functionname)
    

    也就是说,由 @[decorator] 调用并将结果分配回函数名(或类名,如果应用于 声明)。

    始终可以直接调用decorator,并分配返回值:

    id = persisted_property(id=true)(Column(Integer, primaryKey=True, nullable=False))
    

    但是,装饰器不能访问在其中构建对象的名称空间!一具尸体 语句的执行就像它是一个函数(尽管具有不同的作用域规则),并使用生成的本地命名空间生成类属性。decorator只是这个上下文中的另一个函数调用,而类主体的本地命名空间并不意味着可用。

    接下来,我甚至没有开始建立你的 建造者模式 . 这是一种Java模式,其中强制执行类隐私和不可更改性,从而破坏动态语言模式。Python不是Java,不要试图把它变成Java。例如,您不能真正使Python类的实例不可变,这不是动态语言所允许的。此外,Builder模式是一个在Python中不存在的问题的解决方案,在那里你可以建立你的参数来构造一个前面的类,例如,字典,然后动态地应用到类调用,而Java没有这样的动态调用支持。

    您不需要使用decorator模式来标记模式属性 无论如何 . 你应该依靠sqlacalchemy自己的 introspection support :

    from sqlalchemy.inspection import inspect
    
    class Builder:
        def __init__(self, cls, existing=None, **attrs):
            self.cls = cls
            if existing is not None:
                assert isinstance(existing, cls)
                existing_attrs = {n: s.value for n, s in inspect(existing).attrs.items()}
                # keyword arguments override existing attribute values
                attrs = {**existing_attrs, **attrs}
            self.attrs = attrs
        def _create_attr_setter(self, attrname):
            # create a bound attribute setter for the given attribute name
            def attr_setter(self, value):
                if attrname in self.attrs:
                    raise ValueError(f"{attrname} already has a value set")
                return type(self)(self.cls, **self.attrs, **{attrname: value})
            attr_setter.__name__ = f'set_{attrname}'
            return attr_setter.__get__(self, type(self))
        def __getattr__(self, name):
            if name.startswith('set_'):
                attrname = name[4:]
                mapper = inspect(self.cls)
                # valid SQLAlchemy descriptor name on the class?
                if attrname in mapper.attrs:
                    return self._create_attr_setter(attrname)
            raise AttributeError(name)
        def build(self):
            return self.cls(**self.attrs)
    
    class BuilderMixin:
        @classmethod
        def builder(cls, existing=None):
            return Builder(cls, existing)
    

    然后就用 BuilderMixin 作为混合类:

    >>> from sqlalchemy.ext.declarative import declarative_base
    >>> from sqlalchemy import Column, Integer, String
    >>> Base = declarative_base()
    >>> class Foo(Base, BuilderMixin):
    ...     __tablename__ = 'foo'
    ...     id = Column(Integer, primary_key=True, nullable=False)
    ...     text = Column(String(100))
    ...     created_by = Column(Integer)
    ...
    >>> Foo.builder().set_text('Demonstration text').set_created_by(1).build()
    <__main__.Foo object at 0x10f8314a8>
    >>> _.text, _.created_by
    ('Demonstration text', 1)
    

    您可以将其他信息附加到 info 词典:

    text = Column(String(100), info={'mutable': True})
    

    然后您的构建器代码可以通过映射器(例如 mapper.attrs['text'].info.get('mutable', False) )

    但是,不要重新创建Java Builder模式,只需构造 attrs 直接使用字典,最多使用 hybrid property 或与 ORM events .

        2
  •  0
  •   balintawak_eskrima    6 年前

    这对我很有用:

    from abc import ABCMeta, abstractmethod
    from functools import partial
    
    
    class BaseDecorator(object):
        __metaclass__ = ABCMeta
    
        def __init__(self, *args, **kwargs):
            pass
    
        @abstractmethod
        def decorate(self, method, obj, *args, **kwargs):
            raise NotImplementedError()
    
        def __call__(self, method):
            class Wrapper(object):
                def __init__(self, parent, method):
                    self.method = method
                    self.parent = parent
    
                def __call__(self, obj, *args, **kwargs):
                    return self.parent.decorate(self.method, obj, *args, **kwargs)
    
                def __get__(self, obj, cls):
                    return partial(self.__call__, obj)
            return Wrapper(self, method)
    
    
    class SomeDecorator(BaseDecorator):
        def __init__(self, goto=None):
            self.goto = goto
    
        def decorate(self, method, obj, *args, **kwargs):
            print("method was decorated")
            return method(obj, *args, **kwargs)
    
    
    class Foo(object):
        @SomeDecorator(goto='/promo/')
        def get(self, request):
            return 'response'
    
    
    if __name__ == '__main__':
        foo = Foo()
        print(foo.get('/layout/'))