混合属性

在 ORM 映射类上定义具有“混合”行为的属性。

“混合”是指属性在类级别和实例级别具有不同的行为定义。

hybrid 扩展提供了一种特殊的函数装饰器形式,并且对 SQLAlchemy 的其他部分依赖性很小。它的基本工作原理可以与任何基于描述符的表达式系统一起使用。

考虑一个映射 Interval,表示整数 startend 值。我们可以在映射类上定义更高级别的函数,这些函数在类级别生成 SQL 表达式,在实例级别生成 Python 表达式评估。下面,每个用 hybrid_methodhybrid_property 装饰的函数可能会接收 self 作为类的实例,或者可能会直接接收类,具体取决于上下文。

from __future__ import annotations

from sqlalchemy.ext.hybrid import hybrid_method
from sqlalchemy.ext.hybrid import hybrid_property
from sqlalchemy.orm import DeclarativeBase
from sqlalchemy.orm import Mapped
from sqlalchemy.orm import mapped_column


class Base(DeclarativeBase):
    pass

class Interval(Base):
    __tablename__ = 'interval'

    id: Mapped[int] = mapped_column(primary_key=True)
    start: Mapped[int]
    end: Mapped[int]

    def __init__(self, start: int, end: int):
        self.start = start
        self.end = end

    @hybrid_property
    def length(self) -> int:
        return self.end - self.start

    @hybrid_method
    def contains(self, point: int) -> bool:
        return (self.start <= point) & (point <= self.end)

    @hybrid_method
    def intersects(self, other: Interval) -> bool:
        return self.contains(other.start) | self.contains(other.end)

上面,length 属性返回 endstart 属性之间的差值。对于 Interval 的实例,这个减法是在 Python 中进行的,使用普通的 Python 描述符机制。

>>> i1 = Interval(5, 10)
>>> i1.length
5

在处理 Interval 类本身时,hybrid_property 描述符会使用 Interval 类作为参数来评估函数体,这在与 SQLAlchemy 表达式机制一起评估时会返回一个新的 SQL 表达式。

>>> from sqlalchemy import select
>>> print(select(Interval.length))
SELECT interval."end" - interval.start AS length FROM interval
>>> print(select(Interval).filter(Interval.length > 10))
SELECT interval.id, interval.start, interval."end" FROM interval WHERE interval."end" - interval.start > :param_1

过滤方法,例如 Select.filter_by() 也支持混合属性。

>>> print(select(Interval).filter_by(length=5))
SELECT interval.id, interval.start, interval."end" FROM interval WHERE interval."end" - interval.start = :param_1

Interval 类示例还说明了两个方法,contains()intersects(),用 hybrid_method 装饰。这个装饰器对方法应用了与 hybrid_property 对属性应用相同的思想。这些方法返回布尔值,并利用 Python 的 |& 位运算符来生成等效的实例级和 SQL 表达式级布尔行为。

>>> i1.contains(6)
True
>>> i1.contains(15)
False
>>> i1.intersects(Interval(7, 18))
True
>>> i1.intersects(Interval(25, 29))
False

>>> print(select(Interval).filter(Interval.contains(15)))
SELECT interval.id, interval.start, interval."end" FROM interval WHERE interval.start <= :start_1 AND interval."end" > :end_1
>>> ia = aliased(Interval) >>> print(select(Interval, ia).filter(Interval.intersects(ia)))
SELECT interval.id, interval.start, interval."end", interval_1.id AS interval_1_id, interval_1.start AS interval_1_start, interval_1."end" AS interval_1_end FROM interval, interval AS interval_1 WHERE interval.start <= interval_1.start AND interval."end" > interval_1.start OR interval.start <= interval_1."end" AND interval."end" > interval_1."end"

定义与属性行为不同的表达式行为

在上一节中,我们使用 &| 位运算符在 Interval.containsInterval.intersects 方法中是很幸运的,因为我们的函数处理两个布尔值以返回一个新的布尔值。在许多情况下,在 Python 中构建函数和 SQLAlchemy SQL 表达式之间的构造有足够的差异,以至于应该定义两个单独的 Python 表达式。 hybrid 装饰器为此目的定义了一个修饰符 hybrid_property.expression()。举个例子,我们将定义区间的半径,这需要使用绝对值函数。

from sqlalchemy import ColumnElement
from sqlalchemy import Float
from sqlalchemy import func
from sqlalchemy import type_coerce

class Interval(Base):
    # ...

    @hybrid_property
    def radius(self) -> float:
        return abs(self.length) / 2

    @radius.inplace.expression
    @classmethod
    def _radius_expression(cls) -> ColumnElement[float]:
        return type_coerce(func.abs(cls.length) / 2, Float)

在上面的例子中,首先分配给名称 Interval.radiushybrid_property 通过一个后续方法 Interval._radius_expression 进行修改,使用装饰器 @radius.inplace.expression,它将两个修饰符 hybrid_property.inplacehybrid_property.expression 连接在一起。使用 hybrid_property.inplace 表示 hybrid_property.expression() 修饰符应该就地修改 Interval.radius 处的现有混合对象,而不会创建新对象。有关此修饰符及其原理的说明将在下一节 使用 inplace 创建符合 pep-484 规范的混合属性 中讨论。使用 @classmethod 是可选的,严格来说是为了向类型工具提示在这个例子中 cls 预计是 Interval 类,而不是 Interval 的实例。

注意

hybrid_property.inplace 以及用于正确类型支持的 @classmethod 从 SQLAlchemy 2.0.4 开始可用,在早期版本中不起作用。

现在 Interval.radius 包含一个表达式元素,当在类级别访问 Interval.radius 时会返回 SQL 函数 ABS()

>>> from sqlalchemy import select
>>> print(select(Interval).filter(Interval.radius > 5))
SELECT interval.id, interval.start, interval."end" FROM interval WHERE abs(interval."end" - interval.start) / :abs_1 > :param_1

使用 inplace 创建符合 pep-484 规范的混合属性

在上一节中,我们演示了一个hybrid_property 装饰器,它包含两个独立的被装饰的函数,最终生成一个被引用为 Interval.radius 的对象属性。 实际上,我们可以为hybrid_property 使用几种不同的修饰符,包括hybrid_property.expression()hybrid_property.setter()hybrid_property.update_expression()

SQLAlchemy 的hybrid_property 装饰器旨在以与 Python 的内置 @property 装饰器相同的方式添加这些方法,其中习惯用法是继续重复定义属性,每次使用**相同的属性名**,如下例所示,它说明了如何使用 hybrid_property.setter()hybrid_property.expression()Interval.radius 描述符。

# correct use, however is not accepted by pep-484 tooling

class Interval(Base):
    # ...

    @hybrid_property
    def radius(self):
        return abs(self.length) / 2

    @radius.setter
    def radius(self, value):
        self.length = value * 2

    @radius.expression
    def radius(cls):
        return type_coerce(func.abs(cls.length) / 2, Float)

上面有三个 Interval.radius 方法,但是由于每个方法都被装饰,首先是被hybrid_property 装饰器装饰,然后是被 @radius 本身装饰,最终效果是 Interval.radius 是一个具有三个不同函数的单个属性。 这种使用方法来自Python 文档中对 @property 的使用。 重要的是要注意,@propertyhybrid_property 的工作方式,**每次都会创建一个描述符的副本**。 也就是说,每次调用 @radius.expression@radius.setter 等等都会创建一个全新的对象。 这允许在子类中重新定义属性而不会出现问题(请参阅本节后面的跨子类重用混合属性,了解如何使用它)。

但是,上述方法与 mypy 和 pyright 等类型工具不兼容。 Python 自己的 @property 装饰器没有这个限制,仅仅是因为这些工具对 @property 的行为进行了硬编码,这意味着在PEP 484 兼容性下,SQLAlchemy 无法使用这种语法。

为了在保持类型合规性的同时生成合理的语法,hybrid_property.inplace 装饰器允许对同一装饰器使用不同的方法名称,同时仍然在同一个名称下生成单个装饰器。

# correct use which is also accepted by pep-484 tooling

class Interval(Base):
    # ...

    @hybrid_property
    def radius(self) -> float:
        return abs(self.length) / 2

    @radius.inplace.setter
    def _radius_setter(self, value: float) -> None:
        # for example only
        self.length = value * 2

    @radius.inplace.expression
    @classmethod
    def _radius_expression(cls) -> ColumnElement[float]:
        return type_coerce(func.abs(cls.length) / 2, Float)

使用 hybrid_property.inplace 进一步限定了装饰器的使用,即不应该创建新的副本,从而保留 Interval.radius 名称,同时允许额外的 Interval._radius_setterInterval._radius_expression 方法使用不同的名称。

版本 2.0.4 中的新功能: 添加了 hybrid_property.inplace,允许以更简洁的方式构建复合 hybrid_property 对象,而无需使用重复的方法名称。 此外,允许在 hybrid_property.expressionhybrid_property.update_expressionhybrid_property.comparator 中使用 @classmethod,以允许类型工具将 cls 识别为类而不是方法签名中的实例。

定义 Setter

hybrid_property.setter() 修饰符允许构建自定义 setter 方法,该方法可以修改对象上的值。

class Interval(Base):
    # ...

    @hybrid_property
    def length(self) -> int:
        return self.end - self.start

    @length.inplace.setter
    def _length_setter(self, value: int) -> None:
        self.end = self.start + value

现在,在设置时会调用 length(self, value) 方法。

>>> i1 = Interval(5, 10)
>>> i1.length
5
>>> i1.length = 12
>>> i1.end
17

允许批量 ORM 更新

混合属性可以定义一个自定义的“UPDATE”处理程序,用于在使用 ORM 启用的更新时,允许混合属性在更新的 SET 子句中使用。

通常,在使用混合属性与update() 一起时,SQL 表达式用作 SET 目标的列。 如果我们的 Interval 类有一个与 Interval.start 关联的混合属性 start_point,那么可以将其直接替换。

from sqlalchemy import update
stmt = update(Interval).values({Interval.start_point: 10})

但是,在使用像 Interval.length 这样的复合混合属性时,此混合属性表示不止一列。 我们可以设置一个处理程序来处理传递到 VALUES 表达式中的值,该值可以影响这一点,使用 hybrid_property.update_expression() 装饰器。 一个与我们的 setter 类似的处理程序将是

from typing import List, Tuple, Any

class Interval(Base):
    # ...

    @hybrid_property
    def length(self) -> int:
        return self.end - self.start

    @length.inplace.setter
    def _length_setter(self, value: int) -> None:
        self.end = self.start + value

    @length.inplace.update_expression
    def _length_update_expression(cls, value: Any) -> List[Tuple[Any, Any]]:
        return [
            (cls.end, cls.start + value)
        ]

上面,如果我们在 UPDATE 表达式中使用 Interval.length,我们将得到一个混合 SET 表达式。

>>> from sqlalchemy import update
>>> print(update(Interval).values({Interval.length: 25}))
UPDATE interval SET "end"=(interval.start + :start_1)

ORM 会自动处理此 SET 表达式。

另请参阅

ORM 启用的 INSERT、UPDATE 和 DELETE 语句 - 包括有关 ORM 启用的 UPDATE 语句的背景信息。

与关系一起使用

创建与相关对象一起使用的混合属性,与创建基于列的数据的混合属性相比,并没有本质上的区别。 对不同表达式的需求往往更大。 我们将演示的两个变体是“依赖连接”混合属性和“相关子查询”混合属性。

依赖连接的关系混合属性

考虑以下声明式映射,它将 UserSavingsAccount 关联起来。

from __future__ import annotations

from decimal import Decimal
from typing import cast
from typing import List
from typing import Optional

from sqlalchemy import ForeignKey
from sqlalchemy import Numeric
from sqlalchemy import String
from sqlalchemy import SQLColumnExpression
from sqlalchemy.ext.hybrid import hybrid_property
from sqlalchemy.orm import DeclarativeBase
from sqlalchemy.orm import Mapped
from sqlalchemy.orm import mapped_column
from sqlalchemy.orm import relationship


class Base(DeclarativeBase):
    pass


class SavingsAccount(Base):
    __tablename__ = 'account'
    id: Mapped[int] = mapped_column(primary_key=True)
    user_id: Mapped[int] = mapped_column(ForeignKey('user.id'))
    balance: Mapped[Decimal] = mapped_column(Numeric(15, 5))

    owner: Mapped[User] = relationship(back_populates="accounts")

class User(Base):
    __tablename__ = 'user'
    id: Mapped[int] = mapped_column(primary_key=True)
    name: Mapped[str] = mapped_column(String(100))

    accounts: Mapped[List[SavingsAccount]] = relationship(
        back_populates="owner", lazy="selectin"
    )

    @hybrid_property
    def balance(self) -> Optional[Decimal]:
        if self.accounts:
            return self.accounts[0].balance
        else:
            return None

    @balance.inplace.setter
    def _balance_setter(self, value: Optional[Decimal]) -> None:
        assert value is not None

        if not self.accounts:
            account = SavingsAccount(owner=self)
        else:
            account = self.accounts[0]
        account.balance = value

    @balance.inplace.expression
    @classmethod
    def _balance_expression(cls) -> SQLColumnExpression[Optional[Decimal]]:
        return cast("SQLColumnExpression[Optional[Decimal]]", SavingsAccount.balance)

上面的混合属性 balance 与该用户帐户列表中的第一个 SavingsAccount 条目一起使用。 Python 中的 getter/setter 方法可以将 accounts 视为 Python 列表,该列表在 self 上可用。

提示

上面的示例中,User.balance 的 getter 访问 self.acccounts 集合,该集合通常会通过在 User.balanceselectinload() 加载器策略上配置的relationship() 加载。 当在 relationship() 上没有明确指定时,默认的加载器策略是lazyload(),它会按需发出 SQL。 在使用 asyncio 时,不支持按需加载器,例如lazyload(),因此应注意确保在使用 asyncio 时,self.accounts 集合可供此混合属性访问器访问。

在表达式级别,预计 User 类将用于适当的上下文中,以便存在对 SavingsAccount 的适当连接。

>>> from sqlalchemy import select
>>> print(select(User, User.balance).
...       join(User.accounts).filter(User.balance > 5000))
SELECT "user".id AS user_id, "user".name AS user_name, account.balance AS account_balance FROM "user" JOIN account ON "user".id = account.user_id WHERE account.balance > :balance_1

但是,需要注意的是,虽然实例级访问器需要担心 self.accounts 是否存在,但在 SQL 表达式级别,这个问题表现形式有所不同,我们基本上会使用外连接。

>>> from sqlalchemy import select
>>> from sqlalchemy import or_
>>> print (select(User, User.balance).outerjoin(User.accounts).
...         filter(or_(User.balance < 5000, User.balance == None)))
SELECT "user".id AS user_id, "user".name AS user_name, account.balance AS account_balance FROM "user" LEFT OUTER JOIN account ON "user".id = account.user_id WHERE account.balance < :balance_1 OR account.balance IS NULL

相关子查询关系混合

当然,我们可以放弃对包含查询中使用的连接的依赖,而选择相关子查询,它可以移植地打包到一个单列表达式中。相关子查询更具可移植性,但通常在 SQL 级别上的性能更差。使用与 使用 column_property 中说明的技术相同,我们可以调整我们的 SavingsAccount 示例以聚合所有帐户的余额,并使用相关子查询作为列表达式。

from __future__ import annotations

from decimal import Decimal
from typing import List

from sqlalchemy import ForeignKey
from sqlalchemy import func
from sqlalchemy import Numeric
from sqlalchemy import select
from sqlalchemy import SQLColumnExpression
from sqlalchemy import String
from sqlalchemy.ext.hybrid import hybrid_property
from sqlalchemy.orm import DeclarativeBase
from sqlalchemy.orm import Mapped
from sqlalchemy.orm import mapped_column
from sqlalchemy.orm import relationship


class Base(DeclarativeBase):
    pass


class SavingsAccount(Base):
    __tablename__ = 'account'
    id: Mapped[int] = mapped_column(primary_key=True)
    user_id: Mapped[int] = mapped_column(ForeignKey('user.id'))
    balance: Mapped[Decimal] = mapped_column(Numeric(15, 5))

    owner: Mapped[User] = relationship(back_populates="accounts")

class User(Base):
    __tablename__ = 'user'
    id: Mapped[int] = mapped_column(primary_key=True)
    name: Mapped[str] = mapped_column(String(100))

    accounts: Mapped[List[SavingsAccount]] = relationship(
        back_populates="owner", lazy="selectin"
    )

    @hybrid_property
    def balance(self) -> Decimal:
        return sum((acc.balance for acc in self.accounts), start=Decimal("0"))

    @balance.inplace.expression
    @classmethod
    def _balance_expression(cls) -> SQLColumnExpression[Decimal]:
        return (
            select(func.sum(SavingsAccount.balance))
            .where(SavingsAccount.user_id == cls.id)
            .label("total_balance")
        )

上面的配方将为我们提供 balance 列,它渲染了一个相关的 SELECT。

>>> from sqlalchemy import select
>>> print(select(User).filter(User.balance > 400))
SELECT "user".id, "user".name FROM "user" WHERE ( SELECT sum(account.balance) AS sum_1 FROM account WHERE account.user_id = "user".id ) > :param_1

构建自定义比较器

混合属性还包含一个助手,允许构建自定义比较器。比较器对象允许对每个 SQLAlchemy 表达式运算符的行为进行单独定制。它们在创建具有 SQL 方面高度特异性行为的自定义类型时很有用。

注意

在本节中介绍的 hybrid_property.comparator() 装饰器 **取代** 了使用 hybrid_property.expression() 装饰器。它们不能一起使用。

以下示例类允许对名为 word_insensitive 的属性进行不区分大小写的比较。

from __future__ import annotations

from typing import Any

from sqlalchemy import ColumnElement
from sqlalchemy import func
from sqlalchemy.ext.hybrid import Comparator
from sqlalchemy.ext.hybrid import hybrid_property
from sqlalchemy.orm import DeclarativeBase
from sqlalchemy.orm import Mapped
from sqlalchemy.orm import mapped_column

class Base(DeclarativeBase):
    pass


class CaseInsensitiveComparator(Comparator[str]):
    def __eq__(self, other: Any) -> ColumnElement[bool]:  # type: ignore[override]  # noqa: E501
        return func.lower(self.__clause_element__()) == func.lower(other)

class SearchWord(Base):
    __tablename__ = 'searchword'

    id: Mapped[int] = mapped_column(primary_key=True)
    word: Mapped[str]

    @hybrid_property
    def word_insensitive(self) -> str:
        return self.word.lower()

    @word_insensitive.inplace.comparator
    @classmethod
    def _word_insensitive_comparator(cls) -> CaseInsensitiveComparator:
        return CaseInsensitiveComparator(cls.word)

在上面,针对 word_insensitive 的 SQL 表达式将对两边都应用 LOWER() SQL 函数。

>>> from sqlalchemy import select
>>> print(select(SearchWord).filter_by(word_insensitive="Trucks"))
SELECT searchword.id, searchword.word FROM searchword WHERE lower(searchword.word) = lower(:lower_1)

上面的 CaseInsensitiveComparator 实施了 ColumnOperators 接口的一部分。可以使用 Operators.operate() 将像小写转换这样的“强制转换”操作应用于所有比较操作(即 eqltgt 等)。

class CaseInsensitiveComparator(Comparator):
    def operate(self, op, other, **kwargs):
        return op(
            func.lower(self.__clause_element__()),
            func.lower(other),
            **kwargs,
        )

在子类之间重复使用混合属性

可以从超类引用混合,以允许修改方法,例如 hybrid_property.getter()hybrid_property.setter(),以便在子类上重新定义这些方法。这类似于标准 Python @property 对象的工作方式。

class FirstNameOnly(Base):
    # ...

    first_name: Mapped[str]

    @hybrid_property
    def name(self) -> str:
        return self.first_name

    @name.inplace.setter
    def _name_setter(self, value: str) -> None:
        self.first_name = value

class FirstNameLastName(FirstNameOnly):
    # ...

    last_name: Mapped[str]

    # 'inplace' is not used here; calling getter creates a copy
    # of FirstNameOnly.name that is local to FirstNameLastName
    @FirstNameOnly.name.getter
    def name(self) -> str:
        return self.first_name + ' ' + self.last_name

    @name.inplace.setter
    def _name_setter(self, value: str) -> None:
        self.first_name, self.last_name = value.split(' ', 1)

在上面,FirstNameLastName 类引用来自 FirstNameOnly.name 的混合,以便重新利用其 getter 和 setter 用于子类。

当重写 hybrid_property.expression()hybrid_property.comparator() 作为对超类的第一个引用时,这些名称与类级别上同名访问器冲突,该访问器是 QueryableAttribute 对象在类级别返回的。要直接引用父类描述符时重写这些方法,请添加特殊限定符 hybrid_property.overrides,它将对受控属性进行反引用,使其回到混合对象。

class FirstNameLastName(FirstNameOnly):
    # ...

    last_name: Mapped[str]

    @FirstNameOnly.name.overrides.expression
    @classmethod
    def name(cls):
        return func.concat(cls.first_name, ' ', cls.last_name)

混合值对象

请注意,在我们之前的示例中,如果我们要将 SearchWord 实例的 word_insensitive 属性与一个普通的 Python 字符串进行比较,那么普通的 Python 字符串将不会被强制转换为小写 - 我们构建的 CaseInsensitiveComparator,由 @word_insensitive.comparator 返回,只适用于 SQL 方面。

自定义比较器的更全面的形式是构建一个混合值对象。此技术将目标值或表达式应用于一个值对象,然后由访问器在所有情况下返回该值对象。值对象允许控制对值的 所有操作,以及如何处理比较值,包括 SQL 表达式方面和 Python 值方面。用新的 CaseInsensitiveWord 类替换之前的 CaseInsensitiveComparator 类。

class CaseInsensitiveWord(Comparator):
    "Hybrid value representing a lower case representation of a word."

    def __init__(self, word):
        if isinstance(word, basestring):
            self.word = word.lower()
        elif isinstance(word, CaseInsensitiveWord):
            self.word = word.word
        else:
            self.word = func.lower(word)

    def operate(self, op, other, **kwargs):
        if not isinstance(other, CaseInsensitiveWord):
            other = CaseInsensitiveWord(other)
        return op(self.word, other.word, **kwargs)

    def __clause_element__(self):
        return self.word

    def __str__(self):
        return self.word

    key = 'word'
    "Label to apply to Query tuple results"

在上面,CaseInsensitiveWord 对象表示 self.word,它可能是一个 SQL 函数,也可能是一个 Python 本机。通过重写 operate()__clause_element__() 以使用 self.word 进行工作,所有比较操作都将针对 word 的“转换”形式工作,无论它是在 SQL 方面还是 Python 方面。我们的 SearchWord 类现在可以无条件地从单个混合调用中提供 CaseInsensitiveWord 对象。

class SearchWord(Base):
    __tablename__ = 'searchword'
    id: Mapped[int] = mapped_column(primary_key=True)
    word: Mapped[str]

    @hybrid_property
    def word_insensitive(self) -> CaseInsensitiveWord:
        return CaseInsensitiveWord(self.word)

现在,word_insensitive 属性具有普遍的不区分大小写的比较行为,包括 SQL 表达式与 Python 表达式(请注意,Python 值在此处在 Python 方面被转换为小写)。

>>> print(select(SearchWord).filter_by(word_insensitive="Trucks"))
SELECT searchword.id AS searchword_id, searchword.word AS searchword_word FROM searchword WHERE lower(searchword.word) = :lower_1

SQL 表达式与 SQL 表达式

>>> from sqlalchemy.orm import aliased
>>> sw1 = aliased(SearchWord)
>>> sw2 = aliased(SearchWord)
>>> print(
...     select(sw1.word_insensitive, sw2.word_insensitive).filter(
...         sw1.word_insensitive > sw2.word_insensitive
...     )
... )
SELECT lower(searchword_1.word) AS lower_1, lower(searchword_2.word) AS lower_2 FROM searchword AS searchword_1, searchword AS searchword_2 WHERE lower(searchword_1.word) > lower(searchword_2.word)

仅 Python 表达式

>>> ws1 = SearchWord(word="SomeWord")
>>> ws1.word_insensitive == "sOmEwOrD"
True
>>> ws1.word_insensitive == "XOmEwOrX"
False
>>> print(ws1.word_insensitive)
someword

混合值模式对于可能有多种表示形式的任何类型的值非常有用,例如时间戳、时间增量、度量单位、货币和加密密码。

另请参阅

混合和值不可知类型 - 在 techspot.zzzeek.org 博客上

值不可知类型,第二部分 - 在 techspot.zzzeek.org 博客上

API 参考

对象名称 描述

比较器

一个辅助类,允许轻松构建自定义 PropComparator 类,用于与混合一起使用。

hybrid_method

一个装饰器,允许定义具有实例级和类级行为的 Python 对象方法。

hybrid_property

一个装饰器,允许定义具有实例级和类级行为的 Python 描述符。

HybridExtensionType

一个枚举。

class sqlalchemy.ext.hybrid.hybrid_method

一个装饰器,允许定义具有实例级和类级行为的 Python 对象方法。

方法 sqlalchemy.ext.hybrid.hybrid_method.__init__(func: Callable[[Concatenate[Any, _P]], _R], expr: Callable[[Concatenate[Any, _P]], SQLCoreOperations[_R]] | None = None)

创建一个新的 hybrid_method

通常使用装饰器。

from sqlalchemy.ext.hybrid import hybrid_method

class SomeClass:
    @hybrid_method
    def value(self, x, y):
        return self._value + x + y

    @value.expression
    @classmethod
    def value(cls, x, y):
        return func.some_function(cls._value, x, y)
方法 sqlalchemy.ext.hybrid.hybrid_method.expression(expr: Callable[[Concatenate[Any, _P]], SQLCoreOperations[_R]]) hybrid_method[_P, _R]

提供一个修饰器,用于定义生成 SQL 表达式的函数。

属性 sqlalchemy.ext.hybrid.hybrid_method.extension_type: InspectionAttrExtensionType = 'HYBRID_METHOD'

扩展类型,如果没有则为 NotExtension.NOT_EXTENSION

属性 sqlalchemy.ext.hybrid.hybrid_method.inplace

返回此 hybrid_method 的就地修改器。

当调用 hybrid_method.expression() 装饰器时,hybrid_method 类已经执行了“就地”修改,因此此属性返回 Self。

2.0.4 版新增。

属性 sqlalchemy.ext.hybrid.hybrid_method.is_attribute = True

如果此对象是 Python 描述符,则为 True。

这可以指代多种类型。通常是一个 QueryableAttribute,它代表 MapperProperty 处理属性事件。但也可以是扩展类型,例如 AssociationProxyhybrid_propertyInspectionAttr.extension_type 将引用一个常量,该常量标识特定子类型。

sqlalchemy.ext.hybrid.hybrid_property

一个装饰器,允许定义具有实例级和类级行为的 Python 描述符。

类签名

class sqlalchemy.ext.hybrid.hybrid_property (sqlalchemy.orm.base.InspectionAttrInfo, sqlalchemy.orm.base.ORMDescriptor)

方法 sqlalchemy.ext.hybrid.hybrid_property.__init__(fget: _HybridGetterType[_T], fset: _HybridSetterType[_T] | None = None, fdel: _HybridDeleterType[_T] | None = None, expr: _HybridExprCallableType[_T] | None = None, custom_comparator: Comparator[_T] | None = None, update_expr: _HybridUpdaterType[_T] | None = None)

创建一个新的 hybrid_property

通常使用装饰器。

from sqlalchemy.ext.hybrid import hybrid_property

class SomeClass:
    @hybrid_property
    def value(self):
        return self._value

    @value.setter
    def value(self, value):
        self._value = value
方法 sqlalchemy.ext.hybrid.hybrid_property.comparator(comparator: _HybridComparatorCallableType[_T]) hybrid_property[_T]

提供一个修饰器,用于定义自定义的比较器生成方法。

装饰方法的返回值应为 Comparator 的实例。

注意

hybrid_property.comparator() 修饰器 **替换** 了 hybrid_property.expression() 修饰器的使用。它们不能一起使用。

当在类级别调用混合属性时,此处提供的 Comparator 对象会被包装在一个专门的 QueryableAttribute 中,该对象与 ORM 用于表示其他映射属性的对象类型相同。这样做的原因是为了让其他类级别属性(如文档字符串和对混合属性本身的引用)能够在返回的结构中保留,而无需对传入的原始比较器对象进行任何修改。

注意

当从拥有类引用混合属性(例如 SomeClass.some_hybrid)时,会返回 QueryableAttribute 的实例,表示该混合属性的表达式或比较器对象。但是,该对象本身具有名为 expressioncomparator 的访问器;因此,当尝试在子类上覆盖这些修饰器时,可能需要首先使用 hybrid_property.overrides 修饰符对其进行限定。有关详细信息,请参阅该修饰符。

方法 sqlalchemy.ext.hybrid.hybrid_property.deleter(fdel: _HybridDeleterType[_T]) hybrid_property[_T]

提供一个修饰器,用于定义删除方法。

方法 sqlalchemy.ext.hybrid.hybrid_property.expression(expr: _HybridExprCallableType[_T]) hybrid_property[_T]

提供一个修饰器,用于定义生成 SQL 表达式的函数。

当在类级别调用混合属性时,此处提供的 SQL 表达式会被包装在一个专门的 QueryableAttribute 中,该对象与 ORM 用于表示其他映射属性的对象类型相同。这样做的原因是为了让其他类级别属性(如文档字符串和对混合属性本身的引用)能够在返回的结构中保留,而无需对传入的原始 SQL 表达式进行任何修改。

注意

当从拥有类引用混合属性(例如 SomeClass.some_hybrid)时,会返回 QueryableAttribute 的实例,表示该混合属性的表达式或比较器对象,以及该混合属性本身。但是,该对象本身具有名为 expressioncomparator 的访问器;因此,当尝试在子类上覆盖这些修饰器时,可能需要首先使用 hybrid_property.overrides 修饰符对其进行限定。有关详细信息,请参阅该修饰符。

属性 sqlalchemy.ext.hybrid.hybrid_property.extension_type: InspectionAttrExtensionType = 'HYBRID_PROPERTY'

扩展类型,如果没有则为 NotExtension.NOT_EXTENSION

方法 sqlalchemy.ext.hybrid.hybrid_property.getter(fget: _HybridGetterType[_T]) hybrid_property[_T]

提供一个修饰器,用于定义 getter 方法。

新版 1.2 中新增。

属性 sqlalchemy.ext.hybrid.hybrid_property.inplace

返回此 hybrid_property 的就地变异器。

这样允许对混合属性进行就地变异,允许重用具有特定名称的第一个混合属性方法,以便添加更多方法而不必给这些方法命名相同,例如:

class Interval(Base):
    # ...

    @hybrid_property
    def radius(self) -> float:
        return abs(self.length) / 2

    @radius.inplace.setter
    def _radius_setter(self, value: float) -> None:
        self.length = value * 2

    @radius.inplace.expression
    def _radius_expression(cls) -> ColumnElement[float]:
        return type_coerce(func.abs(cls.length) / 2, Float)

2.0.4 版新增。

属性 sqlalchemy.ext.hybrid.hybrid_property.is_attribute = True

如果此对象是 Python 描述符,则为 True。

这可以指代多种类型。通常是一个 QueryableAttribute,它代表 MapperProperty 处理属性事件。但也可以是扩展类型,例如 AssociationProxyhybrid_propertyInspectionAttr.extension_type 将引用一个常量,该常量标识特定子类型。

属性 sqlalchemy.ext.hybrid.hybrid_property.overrides

用于覆盖现有属性的方法的前缀。

hybrid_property.overrides 访问器仅返回该混合属性对象,当从父类在类级别调用它时,将取消引用在此级别返回的“仪表化属性”,并允许修改修饰器,例如 hybrid_property.expression()hybrid_property.comparator(),以便使用它们而不与通常存在于 QueryableAttribute 上的同名属性冲突。

class SuperClass:
    # ...

    @hybrid_property
    def foobar(self):
        return self._foobar

class SubClass(SuperClass):
    # ...

    @SuperClass.foobar.overrides.expression
    def foobar(cls):
        return func.subfoobar(self._foobar)

新版 1.2 中新增。

方法 sqlalchemy.ext.hybrid.hybrid_property.setter(fset: _HybridSetterType[_T]) hybrid_property[_T]

提供一个修饰器,用于定义 setter 方法。

方法 sqlalchemy.ext.hybrid.hybrid_property.update_expression(meth: _HybridUpdaterType[_T]) hybrid_property[_T]

提供一个修饰器,用于定义生成 UPDATE 元组的方法。

该方法接受一个值,该值将被渲染到 UPDATE 语句的 SET 子句中。该方法应该将该值处理成适合最终 SET 子句的单个列表达式,并将它们作为 2 元组的序列返回。每个元组包含一个列表达式作为键,以及要渲染的值。

例如:

class Person(Base):
    # ...

    first_name = Column(String)
    last_name = Column(String)

    @hybrid_property
    def fullname(self):
        return first_name + " " + last_name

    @fullname.update_expression
    def fullname(cls, value):
        fname, lname = value.split(" ", 1)
        return [
            (cls.first_name, fname),
            (cls.last_name, lname)
        ]

新版 1.2 中新增。

sqlalchemy.ext.hybrid.Comparator

一个辅助类,允许轻松构建自定义 PropComparator 类,用于与混合一起使用。

sqlalchemy.ext.hybrid.HybridExtensionType

一个枚举。

属性 sqlalchemy.ext.hybrid.HybridExtensionType.HYBRID_METHOD = 'HYBRID_METHOD'

表示类型为 hybrid_methodInspectionAttr 的符号。

分配给 InspectionAttr.extension_type 属性。

另请参阅

Mapper.all_orm_attributes

属性 sqlalchemy.ext.hybrid.HybridExtensionType.HYBRID_PROPERTY = 'HYBRID_PROPERTY'
表示类型为 hybrid_methodInspectionAttr

符号。

分配给 InspectionAttr.extension_type 属性。

另请参阅

Mapper.all_orm_attributes