SQLAlchemy在模型中自定义本地属性

use*_*007 3 python scope sqlalchemy

我定义了一个简单的模型,并在类中添加了一些非SQL相关的属性.

如果属性是int或string类型的东西正在工作.但如果它是字典或列表类型,那么神奇地在模型类的所有不同实例上使用相同的列表.

例:

# Sample model class
class TestClass(Base):
   # SQL Mappings   
   __tablename__ = 'test1'

   pid = Column("id", Integer, primary_key=True)
   name = Column('name', String)

   # Non SQL related attributes
   works_var = 0
   works_not_var = []
   # ...
Run Code Online (Sandbox Code Playgroud)

在查询处理检索到的TestClass实例之后的其他地方

my_test_class.works_not_var.append("testval1")
my_test_class2.works_not_var.append("testval2")
Run Code Online (Sandbox Code Playgroud)

不知何故,两个附加值最终都在同一个列表中:

print id(my_test_class.works_not_var)
print id(my_test_class2.works_not.var)
Run Code Online (Sandbox Code Playgroud)

我的是一样的.但它适用于"works_var".我有不同的.

jav*_*vex 7

这绝对与SQLAlchemy无关,是标准的Python行为.问题的原因与Python处理类型和解析事物的方式有关.考虑这个有趣的例子:

class A(object):
    print "Hello"
print "A is now defined"
a = A()
print "I now have an instance of A"
Run Code Online (Sandbox Code Playgroud)

这当然完全没用,但在执行时,观察打印顺序:

Hello
A is now defined
I now have an instance of A
Run Code Online (Sandbox Code Playgroud)

你有没有想到这个?

简单解决方案

# Sample model class
class TestClass(Base):
   # SQL Mappings   
   __tablename__ = 'test1'

   pid = Column("id", Integer, primary_key=True)
   name = Column('name', String)
  # ...

  def __init__(self):
    self.works_var = 0
    self.works_not_var = []
Run Code Online (Sandbox Code Playgroud)

经验法则:将默认参数__init__放在类级别上.

说明

可以按顺序进行更彻底的解释:为什么会出现问题?我不会详细介绍Python如何处理变量.在eevee的Python常见问题解答中有一个简洁的解释:传递文章.此外,这里有一个很好的解释:其他语言有"变量".

有了这些知识,并从上面的例子中我们现在知道语句何时works_not_var = []执行:导入它的那一刻(或者脚本被启动).我们也知道为什么这是一个问题:一个像是list可变的对象,当你改变它时,Python不会移动它的"标记":而是你创建了一个实例变量.更常见的是,这个问题在函数默认参数上被注意到(并且更容易解释).考虑这个问题:Python中的"最小惊讶":可变默认参数.它解释了它的来源非常好.在一个简洁的例子中:

def f(a=[]):
    if len(a) == 0:
        print "Oh no, list is empty"
        a.append(1)

f()
print "Function executed first time"
f()
print "Function executed second time"
Run Code Online (Sandbox Code Playgroud)

并输出:

Oh no, list is empty
Function executed first time
Function executed second time
Run Code Online (Sandbox Code Playgroud)

该列表是在解析它们时创建的,而不是执行时间.另一个如何失败并产生愚蠢问题的例子:

from datetime import datetime
from time import sleep

def f(time=datetime.now()):
    print time

f()
sleep(1)
f()
f(datetime.now())
Run Code Online (Sandbox Code Playgroud)

因此,您创建了一个函数,该函数获得默认为当前时间的时间.好吧,不是那么多.它默认为程序启动的时间,但不是当前时间.如果你运行它你会得到:

2013-08-20 16:14:29.037069
2013-08-20 16:14:29.037069
2013-08-20 16:14:30.038302
Run Code Online (Sandbox Code Playgroud)

但是你会期望第二次和第三次几乎相等而且在"第二"级别没有差别.问题再次出现:datetime.now()解析函数未执行时执行默认参数.

解决方案

为此,还有一个简单的解决方案(虽然我可能会说我觉得它不像我想的那样漂亮):

def f(now=None):
    if now is None:
        now = datetime.now()
Run Code Online (Sandbox Code Playgroud)

我希望这个解释有所帮助.

  • 哇,谢谢你对此的详细解答.就像您编写的那样,可以使用您的信息轻松修复我的代码.谢谢!关于SQLAlchemy的附加注释:如果在框架内创建了对象,则默认情况下不会调用__init __()方法.需要"@ orm.reconstructor"装饰器为此目的指定一个自己的"__init__"方法(这反过来可以调用标准__init __()) (4认同)