我有一个代表业务部门的cython类。这个类以纯cython风格声明。
在一个项目中,我需要将业务单位映射到数据库。为此,我想导入.pxd文件并使用SQLAlchemy“映射”它。
让我们假设班级设备。该类在.pxd中定义了.pyx和类接口(因为我需要在其他模块中将其导入)。
cdef class Equipment:
cdef readonly int x
cdef readonly str y
cdef class Equipment:
def __init__(self, int x, str y):
self.x = x
self.y = y
我编译所有内容并获取一个equipment.pyd文件。到目前为止,没关系。 此文件包含业务逻辑模型,不得更改。
然后在一个应用程序中,我导入equipment.pyd并使用SQLAlchemy映射它。
from sqlalchemy import Table, Column, Integer, String
from sqlalchemy.orm import mapper
from equipment import Equipment
metadata = MetaData()
# Table definition
equipment = Table(
'equipment', metadata,
Column('id', Integer, primary_key=True),
Column('x', Integer),
Column('y', String),
)
# Mapping the table definition with the class definition
mapper(Equipment, equipment)
TypeError:无法设置内置/扩展类型'equipment.Equipment'的属性
确实,SQLAlchemy正在尝试创建Equipment.c.x,Equipment.c.y,......这在Cython中是不可能的,因为它没有在.pxd中定义...
那么如何将Cython类映射到SQLAlchemy?
如果我在.pyx文件中以python模式定义设备类,它的工作原理是因为最后它只是cython类定义中的'python'对象。
class Equipment:
def __init__(self, x, y):
self.x = x
self.y = y
但是我失去了许多功能,这就是为什么我需要纯粹的Cython。
谢谢! : - )
- 编辑部分 -
保留.pyx和.pxd文件。继承自.pyd。尝试映射。
from sqlalchemy import Table, Column, Integer, String
from sqlalchemy.orm import mapper
from equipment import Equipment
metadata = MetaData()
# Table definition
equipment = Table(
'equipment', metadata,
Column('id', Integer, primary_key=True),
Column('x', Integer),
Column('y', String),
)
# Inherit Equipment to a mapped class
class EquipmentMapped(Equipment):
def __init__(self, x, y):
super(EquipmentMapped, self).__init__(x, y)
# Mapping the table definition with the class definition
mapper(EquipmentMapped, equipment)
从映射导入EquipmentMapped
e = EquipmentMapped(2,3)
print e.x
##这是空的!
为了使其有效,我必须将每个属性定义为属性!
cdef class Equipment:
cdef readonly int _x
cdef readonly str _y
cdef class Equipment:
def __init__(self, int x, str y):
self.x = x
self.y = y
property x:
def __get__(self):
return self._x
def __set__(self, x):
self._x = x
property y:
def __get__(self):
return self._y
def __set__(self, y):
self._y = y
这并不令人满意,因为:lazy_programmer_mode on:我在业务逻辑中做了很多改动......:lazy_programmer_mode off:
答案 0 :(得分:0)
我认为基本问题是,当你致电mapper
时,它会(除其他事项外)
Equipment.x = ColumnProperty(...) # with some arguments
Equipment.y = ColumnProperty(...)
当ColumnProperty
是sqlalchemy定义的属性时,所以当你执行e.x = 5
时,它会注意到该值已经改变了所有与数据库相关的东西。
显然,下面的Cython类并不能很好地与你试图用来控制存储空间一起玩。
就个人而言,我怀疑唯一真正的答案是定义一个包含Cython类和sqlalchemy映射类的包装类,并拦截所有属性访问和方法调用以使它们保持同步。下面是一个粗略的实现,它应该适用于简单的情况。虽然它几乎没有经过测试,但几乎可以肯定它有错过的错误和角落情况。小心!
def wrapper_class(cls):
# do this in a function so we can create it generically as needed
# for each cython class
class WrapperClass(object):
def __init__(self,*args,**kwargs):
# construct the held class using arguments provided
self._wrapped = cls(*args,**kwargs)
def __getattribute__(self,name):
# intercept all requests for attribute access.
wrapped = object.__getattribute__(self,"_wrapped")
update_from = wrapped
update_to = self
try:
o = getattr(wrapped,name)
except AttributeError:
# if we can't find it look in this class instead.
# This is reasonable, because there may be methods defined
# by sqlalchemy for example
update_from = self
update_to = wrapped
o = object.__getattribute__(self,name)
if callable(o):
return FunctionWrapper(o,update_from,update_to)
else:
return o
def __setattr__(self,name,value):
# intercept all attempt to write to attributes
# and set it in both this class and the wrapped Cython class
if name!="_wrapped":
try:
setattr(self._wrapped,name,value)
except AttributeError:
pass # ignore errors... maybe bad!
object.__setattr__(self,name,value)
return WrapperClass
class FunctionWrapper(object):
# a problem we have is if we call a member function.
# It's possible that the member function may change something
# and thus we need to ensure that everything is updated appropriately
# afterwards
def __init__(self,func,update_from,update_to):
self.__func = func
self.__update_from = update_from
self.__update_to = update_to
def __call__(self,*args,**kwargs):
ret_val = self.__func(*args,**kwargs)
# for both Cython classes and sqlalchemy mappings
# all the relevant attributes exist in the class dictionary
for k in self.__update_from.__class__.__dict__.iterkeys():
if not k.startswith('__'): # ignore private stuff
try:
setattr(self.__update_to,k,getattr(self.__update_from,k))
except AttributeError:
# There may be legitmate cases when this fails
# (probably relating to sqlalchemy functions?)
# in this case, replace raise with pass
raise
return ret_val
要使用它,您可以执行以下操作:
class EquipmentMapped(wrapper_class(Equipment)):
# you may well have to define __init__ here
# you'll have to check yourself, and see what sqlalchemy does...
pass
mapper(EquipmentMapped,equipment)
请记住,这是一个可怕的工作区,基本上只是在两个地方重复所有数据,然后拼命试图保持同步。
编辑:这个版本的原始版本提供了一种机制,可以自动调查OP尝试但确定需要花费大量精力才能手动完成(定义Cython类的属性,只能成功覆盖sqlalchemy跟踪变更的机制)进一步测试证实它不起作用。如果您对不该做的事情感到好奇,那么请查看编辑历史记录!