带有Q对象和`and_`(vs`and`)的Django奇怪行为

时间:2019-04-28 17:21:04

标签: python django

我一直在尝试根据某些函数参数动态构造过滤器列表。 我使用Q对象构建了一个谓词列表,最后我使用了以下条件来构建一个谓词:

filters = []
... some code appending Q objects to filters ...
combined_filter = reduce(and_, filters)

然后我用以下查询我的数据库对象:

MyModel.objects.filter(combined_filter)

当我尝试将Q对象与operator.and_组合在一起时,我注意到了一些奇怪的行为。

例如比较以下输出:

items = Item.objects.filter(and_(is_metal, ~is_wood))
print([i.name for i in items])

items = Item.objects.filter(is_metal and ~is_wood)
print([i.name for i in items])

items = Item.objects.filter(is_metal, ~is_wood)
print([i.name for i in items])

我得到:

['Table', 'Container']
['Container']
['Table', 'Container']

and_and之间行为不同的原因是什么?

我的预期输出是['Container'](请参见下面的完整示例,“容器”是唯一只包含“金属”作为材料的东西,应该排除“表”,因为它也具有“木头” )。

后续问题将是:使用and时如何获得reduce的行为?

我的Django版本是2.0.7 我在https://repl.it/repls/AgonizingBossyEfficiency

上重现了这个确切的问题

如果上面的链接消失了,我修改过的所有代码都在下面:

models.py

from django.db import models

class Material(models.Model):
  name = models.CharField(max_length=50)

class Item(models.Model):
  name = models.CharField(max_length=50)
  materials = models.ManyToManyField(Material)

views.py

from django.shortcuts import render
from django.db import transaction
from django.db.models import Q
from operator import and_
from .models import Material, Item

# Create your views here.
def home(request):
    with transaction.atomic():
      metal = Material(name="metal")
      metal.save()
      wood = Material(name="wood")
      wood.save()

      table = Item(name="Table")
      table.save()
      table.materials.add(metal, wood)
      table.save()

      chair = Item(name="Chair")
      chair.save()
      chair.materials.add(wood)
      chair.save()

      container = Item(name="Container")
      container.save()
      container.materials.add(metal)
      container.save()

      is_metal = Q(materials__name__in = ('metal',))
      is_wood = Q(materials__name__in = ('wood',))

      items = Item.objects.filter(and_(is_metal, ~is_wood))
      print([i.name for i in items])

      items = Item.objects.filter(is_metal and ~is_wood)
      print([i.name for i in items])

      items = Item.objects.filter(is_metal, ~is_wood)
      print([i.name for i in items])

      raise Exception('nope')
    return render(request, 'main/index.html')

1 个答案:

答案 0 :(得分:0)

Ilja指出,我的问题实际上是重复的。

我的主要问题是认为operator.and_适用于逻辑and。 但是事实并非如此,operator.and_是按位运算符。

如果我比较operator.and_(is_metal, is_wood)is_metal & is_wood的行为(与is_metal && is_wood == is_metal and is_wood相反),我的确会得到相同的结果。

所以我的问题是我正在使用按位运算,并且我希望在逻辑和运算符上使用。