正确自动生成 __str__() 实现也适用于 sqlalchemy 类?

问题描述 投票:0回答:2

我想干净整洁地显示/打印我的 sqlalchemy 类。

有没有办法在 python 中自动生成

__str__()
实现? 答案 您可以使用 vars、dir、...:... 来迭代实例属性 在简单类的情况下会有所帮助。

当我尝试将其应用到

Sqlalchemy
类时(例如 中的类) Python 的 SQLAlchemy 入门教程 - 见下文),除了成员变量之外,我还得到以下条目作为成员变量:

_sa_instance_state=<sqlalchemy.orm.state.InstanceState object at 0x000000004CEBCC0>

如何避免此条目出现在

__str__
表示中?

为了完整起见,我也将链接的 stackoverflow 问题的解决方案放在下面。

import os
import sys
from sqlalchemy import Column, ForeignKey, Integer, String
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import relationship
from sqlalchemy import create_engine

Base = declarative_base()

class Person(Base):
    __tablename__ = 'person'
    # Here we define columns for the table person
    # Notice that each column is also a normal Python instance attribute.
    id = Column(Integer, primary_key=True)
    name = Column(String(250), nullable=False)

如前所述,这是来自 Is there a way to auto generated a

__str__
() implementation in python?:

的解决方案
def auto_str(cls):
    def __str__(self):
        return '%s(%s)' % (
            type(self).__name__,
            ', '.join('%s=%s' % item for item in vars(self).items())
        )
    cls.__str__ = __str__
    return cls

@auto_str
class Foo(object):
    def __init__(self, value_1, value_2):
        self.attribute_1 = value_1
         self.attribute_2 = value_2

适用:

>>> str(Foo('bar', 'ping'))
'Foo(attribute_2=ping, attribute_1=bar)'
python sqlalchemy boilerplate
2个回答
18
投票

这就是我用的:

def todict(obj):
    """ Return the object's dict excluding private attributes, 
    sqlalchemy state and relationship attributes.
    """
    excl = ('_sa_adapter', '_sa_instance_state')
    return {k: v for k, v in vars(obj).items() if not k.startswith('_') and
            not any(hasattr(v, a) for a in excl)}

class Base:

    def __repr__(self):
        params = ', '.join(f'{k}={v}' for k, v in todict(self).items())
        return f"{self.__class__.__name__}({params})"

Base = declarative_base(cls=Base)

任何继承自

Base
的模型都将定义默认的
__repr__()
方法,如果我需要做一些不同的事情,我可以重写该特定类上的方法。

它排除用前导下划线表示的任何私有属性的值、SQLAlchemy 实例状态对象以及字符串中的任何关系属性。我排除了关系属性,因为我通常不希望 repr 导致延迟加载关系,并且关系是双向的,包括关系属性可能会导致无限递归。

结果看起来像:

ClassName(attr=val, ...)

--编辑--

我上面提到的

todict()
函数是一个帮助器,我经常调用它来从 SQLA 对象构造
dict
,主要用于序列化。我在这种情况下懒惰地使用它,但它不是很有效,因为它正在构建一个
dict
(在
todict()
中)来构建一个
dict
(在
__repr__()
中)。此后我修改了模式以调用生成器:

def keyvalgen(obj):
    """ Generate attr name/val pairs, filtering out SQLA attrs."""
    excl = ('_sa_adapter', '_sa_instance_state')
    for k, v in vars(obj).items():
        if not k.startswith('_') and not any(hasattr(v, a) for a in excl):
            yield k, v

然后底座Base看起来像这样:

class Base:

    def __repr__(self):
        params = ', '.join(f'{k}={v}' for k, v in keyvalgen(self))
        return f"{self.__class__.__name__}({params})"

todict()
函数也利用了
keyvalgen()
生成器,但不再需要构建 repr。


11
投票

我在我的基本模型上或 mixin 中定义了这个

__repr__
方法:

def __repr__(self):
    package = self.__class__.__module__
    class_ = self.__class__.__name__
    attrs = sorted((k, getattr(self, k)) for k in self.__mapper__.columns.keys())
    sattrs = ', '.join(f'{key}={value!r}' for key, value in attrs)
    return f'{package}.{class_}({sattrs})'

该方法按字母顺序显示表的所有列(但不包括关系)的名称及其值的

repr
。我通常不会定义
__str__
除非我需要特定的形式 - 也许
str(User(name='Alice'))
只是
Alice
- 所以
str(model_instance)
将调用
__repr__
方法。

示例代码

import datetime

import sqlalchemy as sa
from sqlalchemy import orm


class ReprMixin:
    """Provide a default __repr__ for ORM model classes."""

    def __repr__(self):
        package = self.__class__.__module__
        class_ = self.__class__.__name__
        attrs = sorted((k, getattr(self, k)) for k in self.__mapper__.columns.keys())
        sattrs = ', '.join(f'{key}={value!r}' for key, value in attrs)
        return f'{package}.{class_}({sattrs})'


Base = orm.declarative_base()


class MyModel(ReprMixin, Base):

    __tablename__ = 'mytable'

    foo = sa.Column(sa.String(32))
    bar = sa.Column('bar_id', sa.Integer, primary_key=True)
    baz = sa.Column(sa.DateTime)
>>> mm = models.MyModel(foo='Foo', bar=42, baz=datetime.datetime.now())
>>> mm
models.MyModel(bar=42, baz=datetime.datetime(2019, 1, 4, 7, 37, 59, 350432), foo='Foo')

此答案的原始版本使用模型的

__table__
属性来访问列名称。我已将其更改为使用模型的
__mapper__
,因为 SQLAlchemy 允许模型属性名称(存储在映射器中)与数据库中的列名称(存储在表中)不同。
MyModel.bar
证明了这一点。

© www.soinside.com 2019 - 2024. All rights reserved.