SQLAlchemy: неожиданные результаты при использовании "и" и "или"

У меня есть декларативный базовый класс News:

class News(Base):
    __tablename__ = "news"
    id = Column(Integer, primary_key = True)
    title = Column(String)
    author = Column(String)
    url = Column(String)
    comments = Column(Integer)
    points = Column(Integer)
    label = Column(String)

У меня также есть функция f(title), которая получает строку и возвращает один из 3 вариантов строк: "хорошо", "возможно" или "никогда". Я пытаюсь получить отфильтрованные строки:

rows = s.query(News).filter(News.label == None and f(News.title) == 'good').all()

Но программа завершается ошибкой, вызывая эту ошибку:

raise TypeError("Boolean value of this clause is not defined")

Как я могу решить это?

Ответ 1

Проблема заключается в следующем:

News.label == None and f(News.title) == 'good'
#                  ^^^ here

Python не позволяет переопределять поведение логических операций operations and и or. Вы можете повлиять на них в некоторой степени с помощью __bool__ в Python 3 и __nonzero__ в Python 2, но все, что он делает, это то, что он определяет истинное значение вашего объект.

Если рассматриваемые объекты не реализовали __bool__ и не выдавали ошибку, или реализация не выдавала, вы могли бы получить довольно загадочные ошибки из-за короткого замыкания and и or:

In [19]: (News.label == 'asdf') and True
Out[19]: <sqlalchemy.sql.elements.BinaryExpression object at 0x7f62c416fa58>

In [24]: (News.label == 'asdf') or True
Out[24]: True

потому

In [26]: bool(News.label == 'asdf')
Out[26]: False

Это может привести к потере волос в виде неправильных выражений SQL:

In [28]: print(News.label == 'asdf' or News.author == 'NOT WHAT YOU EXPECTED')
news.author = :author_1

Для создания логических выражений SQL используйте функции and_(), or_() и not_() sql или двоичные &, | и ~ перегрузки операторов:

# Parentheses required due to operator precedence
filter((News.label == None) & (f(News.title) == 'good'))

или

filter(and_(News.label == None, f(News.title) == 'good'))

или передайте множественный критерий для вызова Query.filter():

filter(News.label == None, f(News.title) == 'good')

или объедините несколько вызовов на filter():

filter(News.label == None).filter(f(News.title) == 'good')