2009-04-28 10 views
8

Tôi đang triển khai một đối tượng gần giống với một tập hợp, nhưng yêu cầu một biến cá thể bổ sung, vì vậy tôi đang phân lớp đối tượng được cài đặt sẵn. Cách tốt nhất để đảm bảo rằng giá trị của biến này được sao chép khi một trong các đối tượng của tôi được sao chép là gì?Cách chính xác (hoặc tốt nhất) để phân lớp lớp Python là gì, thêm một biến cá thể mới?

Sử dụng mô-đun bộ cũ, đoạn code sau làm việc một cách hoàn hảo:

import sets 
class Fooset(sets.Set): 
    def __init__(self, s = []): 
     sets.Set.__init__(self, s) 
     if isinstance(s, Fooset): 
      self.foo = s.foo 
     else: 
      self.foo = 'default' 
f = Fooset([1,2,4]) 
f.foo = 'bar' 
assert((f | f).foo == 'bar') 

nhưng điều này không làm việc bằng cách sử dụng built-in module bộ.

Giải pháp duy nhất mà tôi có thể thấy là ghi đè mọi phương thức trả về đối tượng được sao chép ... trong trường hợp này tôi cũng có thể không làm phiền phân lớp đối tượng đã đặt. Chắc chắn có một cách tiêu chuẩn để làm điều này?

(Để làm rõ, các mã sau đây không không làm việc (sự khẳng định thất bại):

class Fooset(set): 
    def __init__(self, s = []): 
     set.__init__(self, s) 
     if isinstance(s, Fooset): 
      self.foo = s.foo 
     else: 
      self.foo = 'default' 

f = Fooset([1,2,4]) 
f.foo = 'bar' 
assert((f | f).foo == 'bar') 

)

Trả lời

14

cách ưa thích của tôi để bọc các phương pháp của một bộ sưu tập built-in:

class Fooset(set): 
    def __init__(self, s=(), foo=None): 
     super(Fooset,self).__init__(s) 
     if foo is None and hasattr(s, 'foo'): 
      foo = s.foo 
     self.foo = foo 



    @classmethod 
    def _wrap_methods(cls, names): 
     def wrap_method_closure(name): 
      def inner(self, *args): 
       result = getattr(super(cls, self), name)(*args) 
       if isinstance(result, set) and not hasattr(result, 'foo'): 
        result = cls(result, foo=self.foo) 
       return result 
      inner.fn_name = name 
      setattr(cls, name, inner) 
     for name in names: 
      wrap_method_closure(name) 

Fooset._wrap_methods(['__ror__', 'difference_update', '__isub__', 
    'symmetric_difference', '__rsub__', '__and__', '__rand__', 'intersection', 
    'difference', '__iand__', 'union', '__ixor__', 
    'symmetric_difference_update', '__or__', 'copy', '__rxor__', 
    'intersection_update', '__xor__', '__ior__', '__sub__', 
]) 

Về cơ bản điều tương tự bạn đang làm trong câu trả lời của riêng bạn, nhưng có ít loc.Nó cũng dễ dàng để đưa vào một metaclass nếu bạn muốn làm điều tương tự với danh sách và dicts là tốt.

+0

đó là một đóng góp hữu ích, cảm ơn. nó không giống như bạn đang đạt được nhiều bằng cách làm cho _wrap_methods một phương thức lớp chứ không phải là một hàm - đó là hoàn toàn cho mô đun mà nó mang lại? – rog

+0

Điều này thực sự tuyệt vời, cảm ơn sự đóng góp sâu sắc! – bjd2385

2

set1 | set2 là một hoạt động mà sẽ không sửa đổi hoặc hiện set, nhưng trả về một mới thay vào đó là set. set mới được tạo và trả lại. Không có cách nào để làm cho nó tự động sao chép thuộc tính arbritary từ một hoặc cả hai của set s đến mới được tạo ra set, mà không tự mình tùy chỉnh nhà cung cấp | bởi defining the __or__ method.

class MySet(set): 
    def __init__(self, *args, **kwds): 
     super(MySet, self).__init__(*args, **kwds) 
     self.foo = 'nothing' 
    def __or__(self, other): 
     result = super(MySet, self).__or__(other) 
     result.foo = self.foo + "|" + other.foo 
     return result 

r = MySet('abc') 
r.foo = 'bar' 
s = MySet('cde') 
s.foo = 'baz' 

t = r | s 

print r, s, t 
print r.foo, s.foo, t.foo 

Prints:

MySet(['a', 'c', 'b']) MySet(['c', 'e', 'd']) MySet(['a', 'c', 'b', 'e', 'd']) 
bar baz bar|baz 
+0

Đây là những gì tôi nghi ngờ. Trong trường hợp này, tôi sẽ phải ghi đè __and__, __or__, __rand__, __ror__, __rsub__, __rxor__, __sub__, __xor__, thêm, sao chép, sự khác biệt, giao lộ, đối xứng_difference và union. Tôi đã bỏ lỡ bất kỳ? Thành thật mà nói, tôi đã tìm kiếm một cái gì đó với tính tổng quát đơn giản của giải pháp 2.5 tôi liệt kê ở trên ... nhưng một câu trả lời tiêu cực là tốt quá. Nó có vẻ giống như một lỗi đối với tôi. – rog

-2

Đối với tôi công trình này một cách hoàn hảo bằng Python 2.5.2 trên Win32. Sử dụng bạn định nghĩa lớp và kiểm tra sau:

f = Fooset([1,2,4]) 
s = sets.Set((5,6,7)) 
print f, f.foo 
f.foo = 'bar' 
print f, f.foo 
g = f | s 
print g, g.foo 
assert((f | f).foo == 'bar') 

tôi nhận được kết quả này, đó là những gì tôi mong đợi:

Fooset([1, 2, 4]) default 
Fooset([1, 2, 4]) bar 
Fooset([1, 2, 4, 5, 6, 7]) bar 
+0

có, điều này làm việc với 2.5.2, nhưng bạn có thể làm cho nó hoạt động với các loại thiết lập được xây dựng trong python 2.6? – rog

+0

vì bạn có 'bộ nhập' trong mã của mình và không đề cập đến 2.6, tôi giả sử bạn sẽ sử dụng mô-đun sets.py. Nếu điều này không còn khả dụng trong phiên bản 2.6, có khả năng bạn sẽ không gặp may mắn nữa – Ber

2

Dường như bộ bỏ qua __init__ trong c code. Tuy nhiên, bạn sẽ kết thúc một phiên bản của Fooset, nó sẽ không có cơ hội sao chép trường.

Ngoài việc ghi đè các phương thức trả lại bộ mới, tôi không chắc bạn có thể làm quá nhiều trong trường hợp này. Bộ được xây dựng rõ ràng cho một số lượng nhất định của tốc độ, do đó, không có nhiều công việc trong c.

+0

* sigh *. cảm ơn. đó là đọc của tôi về mã C quá, nhưng tôi là một newbie python vì vậy nghĩ rằng nó là giá trị yêu cầu. tôi đã quên lý do của tôi để không thích phân lớp nói chung - lớp con "bên ngoài" trở nên phụ thuộc vào các chi tiết triển khai nội bộ chưa được xuất bản của lớp cha của nó. – rog

0

Giả sử các câu trả lời khác là chính xác và ghi đè tất cả các phương pháp là cách duy nhất để thực hiện việc này, đây là nỗ lực của tôi theo cách làm vừa phải. Nếu thêm các biến mẫu được thêm vào, chỉ một đoạn mã cần thay đổi. Thật không may nếu một toán tử nhị phân mới được thêm vào đối tượng đã đặt, mã này sẽ bị ngắt, nhưng tôi không nghĩ có cách nào để tránh điều đó. Bình luận chào mừng!

def foocopy(f): 
    def cf(self, new): 
     r = f(self, new) 
     r.foo = self.foo 
     return r 
    return cf 

class Fooset(set): 
    def __init__(self, s = []): 
     set.__init__(self, s) 
     if isinstance(s, Fooset): 
      self.foo = s.foo 
     else: 
      self.foo = 'default' 

    def copy(self): 
     x = set.copy(self) 
     x.foo = self.foo 
     return x 

    @foocopy 
    def __and__(self, x): 
     return set.__and__(self, x) 

    @foocopy 
    def __or__(self, x): 
     return set.__or__(self, x) 

    @foocopy 
    def __rand__(self, x): 
     return set.__rand__(self, x) 

    @foocopy 
    def __ror__(self, x): 
     return set.__ror__(self, x) 

    @foocopy 
    def __rsub__(self, x): 
     return set.__rsub__(self, x) 

    @foocopy 
    def __rxor__(self, x): 
     return set.__rxor__(self, x) 

    @foocopy 
    def __sub__(self, x): 
     return set.__sub__(self, x) 

    @foocopy 
    def __xor__(self, x): 
     return set.__xor__(self, x) 

    @foocopy 
    def difference(self, x): 
     return set.difference(self, x) 

    @foocopy 
    def intersection(self, x): 
     return set.intersection(self, x) 

    @foocopy 
    def symmetric_difference(self, x): 
     return set.symmetric_difference(self, x) 

    @foocopy 
    def union(self, x): 
     return set.union(self, x) 


f = Fooset([1,2,4]) 
f.foo = 'bar' 
assert((f | f).foo == 'bar') 
+0

Bạn đã có một số đệ quy vô hạn trong phương thức sao chép. x = self.copy() nên x = super (Fooset, self) .copy() –

+0

vâng, bạn nói đúng. đang sử dụng super() tốt hơn là đề cập một cách rõ ràng lớp cha? – rog

4

Tôi nghĩ rằng cách được khuyến nghị để làm điều này không phải là phân lớp trực tiếp từ tích hợp set, mà là sử dụng số Abstract Base Class Set có sẵn trong collections.

Sử dụng ABC Set cung cấp cho bạn một số phương pháp miễn phí dưới dạng kết hợp để bạn có thể có một lớp Set tối thiểu bằng cách chỉ định __contains__(), __len__()__iter__(). Nếu bạn muốn một số phương pháp được thiết lập đẹp hơn như intersection()difference(), bạn có thể phải bọc chúng lại.

Đây là nỗ lực của tôi (một điều này xảy ra là một frozenset giống như, nhưng bạn có thể kế thừa từ MutableSet để có được một phiên bản có thể thay đổi):

from collections import Set, Hashable 

class CustomSet(Set, Hashable): 
    """An example of a custom frozenset-like object using 
    Abstract Base Classes. 
    """ 
    ___hash__ = Set._hash 

    wrapped_methods = ('difference', 
         'intersection', 
         'symetric_difference', 
         'union', 
         'copy') 

    def __repr__(self): 
     return "CustomSet({0})".format(list(self._set)) 

    def __new__(cls, iterable): 
     selfobj = super(CustomSet, cls).__new__(CustomSet) 
     selfobj._set = frozenset(iterable) 
     for method_name in cls.wrapped_methods: 
      setattr(selfobj, method_name, cls._wrap_method(method_name, selfobj)) 
     return selfobj 

    @classmethod 
    def _wrap_method(cls, method_name, obj): 
     def method(*args, **kwargs): 
      result = getattr(obj._set, method_name)(*args, **kwargs) 
      return CustomSet(result) 
     return method 

    def __getattr__(self, attr): 
     """Make sure that we get things like issuperset() that aren't provided 
     by the mix-in, but don't need to return a new set.""" 
     return getattr(self._set, attr) 

    def __contains__(self, item): 
     return item in self._set 

    def __len__(self): 
     return len(self._set) 

    def __iter__(self): 
     return iter(self._set) 
3

Đáng buồn thay, bộ không theo các quy tắc và __new__ không phải là được gọi là tạo đối tượng set mới, mặc dù chúng giữ nguyên loại. Đây rõ ràng là một lỗi trong Python (vấn đề # 1721812, sẽ không được sửa trong chuỗi 2.x). Bạn sẽ không bao giờ có thể nhận được một đối tượng kiểu X mà không cần gọi đối tượng type tạo đối tượng X! Nếu set.__or__ sẽ không gọi __new__, chính thức bắt buộc phải trả lại set đối tượng thay vì đối tượng lớp con.

Nhưng thực tế, lưu ý bài đăng bằng nosklo ở trên, hành vi ban đầu của bạn không có ý nghĩa gì. Nhà điều hành Set.__or__ không nên sử dụng lại một trong hai đối tượng nguồn để xây dựng kết quả của nó, cần phải tạo một kết quả mới, trong trường hợp này, foo phải là "default"! Vì vậy, thực tế, bất cứ ai làm điều này nên phải quá tải các nhà khai thác để họ biết được bản sao của foo được sử dụng. Nếu nó không phụ thuộc vào các Foosets đang được kết hợp, bạn có thể làm cho nó là một lớp mặc định, trong trường hợp đó nó sẽ được vinh danh, bởi vì đối tượng mới cho rằng nó thuộc kiểu lớp con.

Những gì tôi có nghĩa là, ví dụ bạn có thể làm việc, loại, nếu bạn đã làm điều này:

class Fooset(set): 
    foo = 'default' 
    def __init__(self, s = []): 
    if isinstance(s, Fooset): 
     self.foo = s.foo 

f = Fooset([1,2,5]) 
assert (f|f).foo == 'default'