2012-05-30 58 views
5

Vì vậy, có một cách dễ dàng để tính giao điểm của hai tập hợp thông qua set.intersection(). Tuy nhiên, tôi có vấn đề sau đây:.Giao diện tập hợp tùy chỉnh Python

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 là một cơ sở đẳng cấp được cung cấp bởi ORM của tôi mà thực hiện cơ bản __hash____eq__ chức năng, trong đó chủ yếu cho biết thêm mỗi thành viên của lớp để băm Nói cách khác, số __hash__ được trả lại sẽ là băm của mọi thành phần của lớp)

Ở giai đoạn này, tôi chỉ muốn chạy một giao lộ được thiết lập theo số .name, để tìm, giả sử, Person('Bar', -1).intersection(union_list) #= [Person("Bar", -1), Person("Bar", 22), Person("Bar", 24)]. (Điển hình .intersection() vào thời điểm này sẽ không cho tôi bất cứ điều gì, tôi không thể ghi đè __hash__ hoặc __eq__ trên lớp Person, vì điều này sẽ ghi đè lên tập đoàn gốc (I nghĩ)

là gì cách tốt nhất để làm này bằng Python 2.x

EDIT: Lưu ý rằng các giải pháp không phải dựa vào một set Tuy nhiên, tôi cần phải tìm các đoàn thể và sau đó nút giao thông, vì vậy nó cảm thấy như thế này là tuân theo một quy định. (nhưng tôi sẵn sàng chấp nhận các giải pháp sử dụng bất kỳ ma thuật nào bạn cho là xứng đáng, miễn là giải quyết được vấn đề của tôi!)

+0

Tôi không hiểu kết quả mong muốn của bạn. Bạn có thể vui lòng * giải thích * kết quả nên chứa? –

+0

Err crap, đó phải là .union, not .intectionection. Tôi đã cập nhật câu hỏi - hãy cho tôi biết nếu điều này rõ ràng hơn? –

+0

Tôi vẫn còn một chút bối rối vì mã ví dụ không có kết quả mà bạn yêu cầu. –

Trả lời

1

Tôi ghét trả lời các câu hỏi của riêng mình, vì vậy tôi sẽ ngừng đánh dấu điều này là 'câu trả lời' trong một thời gian ngắn.

Hóa ra cách để làm điều này là như sau:

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) 

Đó chắc chắn bẩn và nó dựa trên types.MethodType, nhưng nó ít chuyên sâu hơn so với giải pháp tốt nhất đề xuất cho đến nay (giải pháp glglgl của) như thực tế của tôi union_list có thể chứa tiềm năng theo thứ tự của hàng nghìn mục, do đó, điều này sẽ giúp tôi lưu lại các đối tượng mỗi khi tôi chạy quy trình giao cắt này.

+0

Điều này thực sự làm việc mặc dù? Tài liệu chỉ ra rằng các phương thức ma thuật như '__hash__' được tìm kiếm trên lớp, không phải là cá thể. https://docs.python.org/3/reference/datamodel.html#special-lookup –

+0

Thực ra, có vẻ như nó hoạt động đối với các lớp kiểu cũ, nhưng không phải cho các lớp kiểu mới: https://docs.python.org /2/reference/datamodel.html#special-method-lookup-for-old-style-classes –

0

Bạn sẽ phải ghi đè __hash__ và các phương pháp so sánh nếu bạn muốn sử dụng các bộ như thế này.

Nếu không, sau đó

Person("Foo", 21) == Person("Foo", 21) 

sẽ luôn luôn sai.

Nếu đối tượng của bạn được quản lý bằng ORM, thì bạn sẽ phải kiểm tra cách so sánh đối tượng. Thông thường nó chỉ nhìn vào các đối tượng id và so sánh chỉ hoạt động nếu cả hai đối tượng được quản lý. Nếu bạn cố gắng so sánh một đối tượng mà bạn nhận được từ ORM với một cá thể mà bạn đã tự tạo ra trước khi nó tồn tại trong db, thì chúng có thể khác. Dù sao, một ORM không nên có vấn đề với bạn cung cấp logic so sánh của riêng bạn.

Nhưng nếu vì một số lý do bạn không thể ghi đè __hash____eq__, thì bạn không thể sử dụng tập hợp giao lộ và liên kết với đối tượng gốc. Bạn có thể:

  • tính toán giao điểm/đoàn mình
  • tạo ra một lớp wrapper mà có thể so sánh:

    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) 
    
+1

Xem nhận xét của tôi (về câu hỏi gốc); ghi đè đã được xử lý bởi ORM. Tôi sẽ cập nhật câu hỏi để phản ánh điều này. –

0

Đây là phiền phức, nhưng ...

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)} 
5

Âm thanh như

>>> 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'] 

là những gì bạn muốn, vì name là khóa duy nhất của bạn?

+0

Ah, không, ORM tôi đang sử dụng đã cung cấp phương thức __eq__ và __hash__ (và, như vậy, set.union() đã tạo ra kết quả 'sane'). Tôi đang tìm cách để thực hiện một hoạt động giao lộ mà * chỉ * sử dụng một trong các thành viên của lớp làm khóa và vì vậy không thể ghi đè '__hash__' hoặc' __eq__'. –

+0

Tôi hiểu rồi, có lẽ giải pháp của glglgl sẽ phù hợp? –

1

Nếu bạn muốn age là không thích hợp đối với việc so sánh với, bạn nên ghi đè __hash__()__eq__() trong Person mặc dù bạn có nó trong Object của bạn.

Nếu bạn cần hành vi này chỉ có ở đây (và tương tự) bối cảnh, bạn có thể tạo một đối tượng wrapper chứa Person và hoạt động khác nhau, chẳng hạn như

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) 

và sau đó làm

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)] 

(chưa được kiểm tra)

+0

Vấn đề là tôi cần các phương thức '__hash__' và' __eq__' theo cách của chúng, nếu không '.union()' sẽ không hoạt động theo cách của nó. –

+0

Hmm, thú vị. Vì vậy, không có cách nào để làm điều này mà không cần xây dựng lại các đối tượng? Tôi biết C + + mang lại cho tôi tùy chọn để vượt qua một bộ so sánh tùy chỉnh; Python không có khả năng tương tự? –

+0

Có một cách để làm như vậy với các hàm như 'sắp xếp()', tại đây bạn có thể cung cấp hàm 'cmp' cũng như hàm' khóa', nhưng không phải với 'set's, than ôi ... – glglgl

1

Làm thế nào về:

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] 

Nó có thể là nhanh hơn để ném intersectnames tại ORM của bạn, trong trường hợp này bạn sẽ không xây dựng từ điển, chỉ cần thu thập tên trong danh sách.

Các vấn đề liên quan