为啥 SQLAlchemy Postgres ORM 需要 __init__(self) 作为声明性基础?

Posted

技术标签:

【中文标题】为啥 SQLAlchemy Postgres ORM 需要 __init__(self) 作为声明性基础?【英文标题】:Why is SQLAlchemy Postgres ORM requiring __init__(self) for a Declarative Base?为什么 SQLAlchemy Postgres ORM 需要 __init__(self) 作为声明性基础? 【发布时间】:2021-12-18 08:29:46 【问题描述】:

设置:Postgres 13、Python 3.7、SQLAlchemy 1.4

当前结构使用base.py 创建引擎、Scoped Session 和 Augmented Base。所有这些都被调用 在models.py 中,我们定义了一个表类,并在inserts.py 中再次调用,我们测试将新值插入到 使用 ORM 的数据库。所有这些都运行良好。

我的问题是关于models.py 表类中的def __init__(self) 函数。如果没有这个函数,代码会报错TypeError: __init__() takes 1 positional argument but 5 were given

只要我包含__init__ 函数,代码就会正常工作。

鉴于所有模型都是通过声明性系统定义的,我很困惑为什么会产生此错误 这意味着我们的 Class 应该得到一个 __init__() 方法构造函数,它自动接受关键字名称 匹配我们映射的列。

我怀疑我在编写 db_session = scoped_sessionBase = declarative_base(cls=Base, metadata=metadata_obj)Base 的方式在 class NumLimit(Base) 中传递。

我无法完全解决这个问题,如果被引导到我创建此错误的位置,我将不胜感激。谢谢!

base.py

from sqlalchemy import Column, create_engine, Integer, MetaData
from sqlalchemy.orm import declared_attr, declarative_base, scoped_session, sessionmaker

engine = create_engine('postgresql://user:pass@localhost:5432/dev', echo=True)

db_session = scoped_session(
    sessionmaker(
        bind=engine,
        autocommit=False,
        autoflush=False
    )
)


# Augment the base class by using the cls argument of the declarative_base() function so all classes derived
# from Base will have a table name derived from the class name and an id primary key column.
class Base:
    @declared_attr
    def __tablename__(cls):
        return cls.__name__.lower()

    id = Column(Integer, primary_key=True)


# Write all tables to schema 'collect'
metadata_obj = MetaData(schema='collect')
# Instantiate a Base class for our classes definitions
Base = declarative_base(cls=Base, metadata=metadata_obj)

models.py

from base import Base
from sqlalchemy import Column, DateTime, Integer, Text
from sqlalchemy.dialects.postgresql import UUID
import uuid


class NumLimit(Base):

    org = Column(UUID(as_uuid=True), default=uuid.uuid4, unique=True)
    limits = Column(Integer)
    limits_rate = Column(Integer)
    rate_use = Column(Integer)

    def __init__(self, org, limits, allowance_rate, usage, last_usage):
        super().__init__()
        self.org = org
        self.limits = limits
        self.limits_rate = limits_rate
        self.rate_use = rate_use

    def __repr__(self):
        return f'<NumLimit(org=self.org, limits=self.limits, limits_rate=self.limits_rate,' \
               f' rate_use=self.rate_use)>'

insert.py

def insert_num_limit():
    # Generate database schema based on definitions in models.py
    Base.metadata.create_all(bind=engine)

    # Create instances of the NumLimit class
    a_num_limit = NumLimit('123e4567-e89b-12d3-a456-426614174000', 20, 4, 8)
    another_limit = NumLimit('123e4567-e89b-12d3-a456-426614174660', 7, 2, 99)

    # Use the current session to persist data
    db_session.add_all([a_num_limit, another_limit])

    # Commit current session to database and close session
    db_session.commit()
    db_session.close()

    return

【问题讨论】:

【参考方案1】:

sqlalchemy 生成的__init__() 的所有参数都是关键字。好像定义是:

def __init__(self, *, id=None, org=None, limits=None, limits_rate=None, rate_use=None):
    self.id = id
    self.org = org
    # etc...

因此,当您尝试按位置提供参数时:NumLimit('123e4567-e89b-12d3-a456-426614174000', 20, 4, 8),您将收到错误 TypeError: __init__() takes 1 positional argument but 5 were given,因为 __init__() 确实只接受一个位置参数。但是,如果您将它们作为关键字参数提供:NumLimit(id='123e4567-e89b-12d3-a456-426614174000', org=20, limits=4, limits_rate=8) 一切正常。

【讨论】:

感谢您解释清楚,这就是解决方案!

以上是关于为啥 SQLAlchemy Postgres ORM 需要 __init__(self) 作为声明性基础?的主要内容,如果未能解决你的问题,请参考以下文章

Python 操作Redis

python爬虫入门----- 阿里巴巴供应商爬虫

Python词典设置默认值小技巧

《python学习手册(第4版)》pdf

Django settings.py 的media路径设置

Python中的赋值,浅拷贝和深拷贝的区别