如何在构建类时收集有关类属性的信息?
在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
类/功能/?目的是收集类属性。
有了这些知识,这些事情就会发生:
将builder()
classmethod添加到返回生成的FooBuilder的类Foo
中。 FooBuilder
会使用以下方法:set_text()->FooBuilder
,set_created_by()->FooBuilder
,build()->Foo
(理想情况下)任何直接更改Foo
对象的尝试都将被阻止。 (如何让sqlalchemy工作?)
示例行为:
Foo.builder().set_text("Foo text").set_created_by(1).build()
Foo.builder(existing_foo).set_text("Foo text").set_created_by(1).build()
:由于existing_foo
已有created_by
注意:
替代?建议?
答案 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 property或ORM 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/'))