因此,存在一种通过set.intersection()计算两个集合的交集的简单方法。但是,我有以下问题:
class Person(Object):
def __init__(self, name, age):
self.name = name
self.age = age
l1 = [Person("Foo", 21), Person("Bar", 22)]
l2 = [Person("Foo", 21), Person("Bar", 24)]
union_list = list(set(l1).union(l2))
# [Person("Foo", 21), Person("Bar", 22), Person("Bar", 24)]
(Object
是我的ORM提供的基类,它实现了基本的__hash__
和__eq__
功能,它基本上将类的每个成员添加到哈希。换句话说,返回的__hash__
将是该类的每个元素的散列)
在这个阶段,我想仅通过.name
运行一个集合交叉操作,以找到Person('Bar', -1).intersection(union_list) #= [Person("Bar", -1), Person("Bar", 22), Person("Bar", 24)]
。 (此时典型的.intersection()
不会给我任何内容,我无法覆盖__hash__
类上的__eq__
或Person
,因为这会覆盖原始的集合联盟(我想)
在Python 2.x中执行此操作的最佳方法是什么?
编辑:请注意,解决方案 不依赖于set
。但是,我需要找到工会然后交叉点,所以感觉这对于一套是合适的(但是我愿意接受使用你认为值得的任何魔法的解决方案,只要它能解决我的问题!)
答案 0 :(得分:6)
听起来像
>>> class Person:
... def __init__(self, name, age):
... self.name = name
... self.age = age
... def __eq__(self, other):
... return self.name == other.name
... def __hash__(self):
... return hash(self.name)
... def __str__(self):
... return self.name
...
>>> l1 = [Person("Foo", 21), Person("Bar", 22)]
>>> l2 = [Person("Foo", 21), Person("Bar", 24)]
>>> union_list = list(set(l1).union(l2))
>>> [str(l) for l in union_list]
['Foo', 'Bar']
是您想要的,因为name
是您唯一的密钥?
答案 1 :(得分:1)
如果您希望age
与比较无关,则应覆盖__hash__()
中的__eq__()
和Person
,尽管您已将Object
覆盖在Person
中}。
如果仅在此(和类似的)上下文中需要此行为,则可以创建包含class PersonWrapper(Object):
def __init__(self, person):
self.person = person
def __eq__(self, other):
if hasattr(other, 'person'):
return self.person.name == other.person.name
else:
return self.person.name == other.name
def __hash__(self):
return hash(self.person.name)
的包装器对象,并且行为方式不同,例如
union_list = list(set(PersonWrapper(i) for i in l1).union(PersonWrapper(i) for i in l2))
# [Person("Foo", 21), Person("Bar", 22), Person("Bar", 24)]
然后再做
{{1}}
(未测试的)
答案 2 :(得分:1)
我讨厌回答我自己的问题,所以我会暂时将此标记为“答案”。
原来这样做的方法如下:
import types
p = Person("Bar", -1)
new_hash_method = lambda obj: hash(obj.name)
p.__hash__ = types.MethodType(new_hash_method, p)
for i in xrange(0, len(union_list)):
union_list[i].__hash__ = types.MethodType(new_hash_method, union_list[i])
set(union_list).intersection(p)
它肯定是脏的,它依赖于types.MethodType
,但它比目前提出的最佳解决方案(glglgl的解决方案)密集度更低,因为我的实际union_list
可能包含数千个项目的顺序,因此,每次运行此交叉过程时,这都将使我无需重新创建对象。
答案 3 :(得分:1)
怎么样:
d1 = {p.name:p for p in l1}
d2 = {p.name:p for p in l2}
intersectnames = set(d1.keys()).intersection(d2.keys)
intersect = [d1[k] for k in intersectnames]
将intersectnames
扔到您的ORM可能会更快,在这种情况下您不会构建词典,只需在列表中收集名称。
答案 4 :(得分:0)
如果你想使用这样的集合,你必须覆盖__hash__
和比较方法。
如果你不这样做,那么
Person("Foo", 21) == Person("Foo", 21)
总是假的。
如果您的对象由ORM管理,那么您将必须检查它如何比较对象。 通常它只查看对象id和比较仅在两个对象都被管理时才有效。如果您尝试将从ORM获得的对象与您自己创建的实例进行比较,然后将其保存到数据库,那么它们可能会有所不同。无论如何,ORM应该没有问题,你提供自己的比较逻辑。
但是如果由于某些原因你无法覆盖__hash__
和__eq__
,那么你就不能使用集合来与原始对象进行交集和联合。你可以:
创建一个可比较的包装类:
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
l1 = [Person("Foo", 21), Person("Bar", 22)]
l2 = [Person("Foo", 21), Person("Bar", 24)]
class ComparablePerson:
def __init__(self, person):
self.person = person
def __hash__(self):
return hash(self.person.name) + 31*hash(self.person.age)
def __eq__(self, other):
return (self.person.name == other.person.name and
self.person.age == other.person.age)
def __repr__(self):
return "<%s - %d>" % (self.person.name, self.person.age)
c1 = set(ComparablePerson(p) for p in l1)
c2 = set(ComparablePerson(p) for p in l2)
print c1
print c2
print c1.union(c2)
print c2.intersection(c1)
答案 5 :(得分:0)
这很笨重但是......
set(p for p in union_list for q in l2 if p.name == q.name and p.age != q.age) | (set(p for p in l2 for q in union_list if p.name == q.name and p.age != q.age))
# {person(name='Bar', age=22), person(name='Bar', age=24)}