是否可以装饰一个类属性

时间:2018-05-22 20:38:19

标签: python python-3.x sqlalchemy

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

在Java中,我要求的是可能的。

在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() classmethod添加到返回生成的FooBuilder的类Foo中。 FooBuilder会使用以下方法:set_text()->FooBuilderset_created_by()->FooBuilderbuild()->Foo

  2. (理想情况下)任何直接更改Foo对象的尝试都将被阻止。 (如何让sqlalchemy工作?)

  3. 示例行为:

    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
    3. 的值,因此会引发异常

      注意:

      1. 添加一个类级装饰器将属性定义与装饰分开并感觉......错误
      2. 等级装饰发生在sqlalchemy魔术之后。 (这可能是好事还是坏事)
      3. 替代?建议?

2 个答案:

答案 0 :(得分:3)

@callable装饰器语法确实是def函数和class类语句所独有的。但是,它只是语法糖

语法

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

被翻译为:

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

即调用由@[decorator]生成的可调用对象,并将结果分配回函数名称(或类名,如果应用于class语句)。

您可以随时直接调用装饰器,并指定返回值:

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

但是,装饰器无法访问构造对象的命名空间! class语句的主体被执行就好像它是一个函数(虽然具有不同的作用域规则),并且生成的本地名称空间用于生成类属性。装饰器只是此上下文中的另一个函数调用,并且类主体的本地命名空间不可用。

接下来,我甚至没有开始构建构建器模式。这是一种Java模式,其中强制实施类隐私和不变性,从而损害动态语言模式。 Python不是Java,不要试图把它变成Java。例如,你不能真正使Python类的实例不可变,这根本不是动态语言允许你做的事情。此外,构建器模式是Python中不存在的问题的解决方案,在这里你可以构建你的参数来预先构建一个类,比如一个字典,然后动态地应用于类调用,而Java没有这样的动态呼叫支持。

您无需使用装饰器模式来标记您的架构属性 。您应该依赖SQLAlchemy自己的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作为mixin类:

>>> 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构建器模式,而是直接构造attrs字典,并且最多使用hybrid propertyORM events编码可变性规则。

答案 1 :(得分:0)

这对我有用:

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/'))