2011-11-02 32 views
22

Khi phân lớp các kiểu nội trang, tôi nhận thấy sự khác biệt khá quan trọng giữa Python 2 và Python 3 trong kiểu trả về của các phương thức của các kiểu dựng sẵn. Mã sau minh họa điều này cho các bộ:Phân lớp các kiểu nội trang trong Python 2 và Python 3

class MySet(set): 

    pass 

s1 = MySet([1, 2, 3, 4, 5]) 

s2 = MySet([1, 2, 3, 6, 7]) 

print(type(s1.union(s2))) 

print(type(s1.intersection(s2))) 

print(type(s1.difference(s2))) 

Với Python 2, tất cả các giá trị trả về là loại MySet. Với Python 3, kiểu trả về là set. Tôi không thể tìm thấy bất kỳ tài liệu nào về kết quả được cho là gì, cũng như bất kỳ tài liệu nào về sự thay đổi trong Python 3.

Dù sao, điều tôi thực sự quan tâm là: có cách đơn giản trong Python 3 để lấy hành vi được nhìn thấy trong Python 2, mà không định nghĩa lại mọi phương thức duy nhất của các kiểu dựng sẵn?

+0

Mở Python 2 chỉ loại của 's1' là có liên quan không phải là loại của' s2 '. – agf

+2

Nó tương tự như cách 'Sai + Sai' là' 0', không phải 'Sai' (' bool' là một phân lớp của 'int', nhân tiện). –

Trả lời

11

Đây không phải là thay đổi chung đối với các loại được cài sẵn khi di chuyển từ Python 2.x đến 3.x - listint, ví dụ, có cùng hành vi trong 2.x và 3.x. Chỉ loại thiết lập đã được thay đổi để làm cho nó phù hợp với các loại khác, như được thảo luận trong this bug tracker issue.

Tôi e rằng không có cách nào thực sự tốt đẹp để khiến ứng dụng hoạt động theo cách cũ. Dưới đây là một số mã tôi đã có thể đưa ra:

class MySet(set): 
    def copy(self): 
     return MySet(self) 
    def _make_binary_op(in_place_method): 
     def bin_op(self, other): 
      new = self.copy() 
      in_place_method(new, other) 
      return new 
     return bin_op 
    __rand__ = __and__ = _make_binary_op(set.__iand__) 
    intersection = _make_binary_op(set.intersection_update) 
    __ror__ = __or__ = _make_binary_op(set.__ior__) 
    union = _make_binary_op(set.update) 
    __sub__ = _make_binary_op(set.__isub__) 
    difference = _make_binary_op(set.difference_update) 
    __rxor__ = xor__ = _make_binary_op(set.__ixor__) 
    symmetric_difference = _make_binary_op(set.symmetric_difference_update) 
    del _make_binary_op 
    def __rsub__(self, other): 
     new = MySet(other) 
     new -= self 
     return new 

Điều này sẽ chỉ ghi đè lên tất cả các phương pháp có phiên bản trả về loại của riêng bạn. (Có rất nhiều phương pháp!)

Có thể cho ứng dụng của bạn, bạn có thể lấy đi bằng ghi đè copy() và dính vào các phương thức tại chỗ.

+0

Phải, Python 2 không nhất quán ở đây. Nếu bạn tạo một 'class MySet (set): pass' trong Python 2, thì' print type (MySet(). Copy()) 'cho' ', nhưng nếu bạn tạo một' class MyDict (dict): pass', sau đó 'loại in (MyDict(). Copy())' cho ''. – Cito

+0

Có cách để xử lý ít nhất các phương pháp không đặc biệt trong một thao tác đơn lẻ. Tôi sẽ trả lời câu hỏi của riêng mình để minh họa cách (Tôi không thể đưa mã vào nhận xét). Nhưng nó vẫn là cách mà tôi muốn, với tất cả các phương pháp đặc biệt để xử lý từng cái một. – khinsen

0

Có lẽ một metaclass để làm tất cả những gói buồn tẻ cho bạn sẽ làm cho nó dễ dàng hơn:

class Perpetuate(type): 
    def __new__(metacls, cls_name, cls_bases, cls_dict): 
     if len(cls_bases) > 1: 
      raise TypeError("multiple bases not allowed") 
     result_class = type.__new__(metacls, cls_name, cls_bases, cls_dict) 
     base_class = cls_bases[0] 
     known_attr = set() 
     for attr in cls_dict.keys(): 
      known_attr.add(attr) 
     for attr in base_class.__dict__.keys(): 
      if attr in ('__new__'): 
       continue 
      code = getattr(base_class, attr) 
      if callable(code) and attr not in known_attr: 
       setattr(result_class, attr, metacls._wrap(base_class, code)) 
      elif attr not in known_attr: 
       setattr(result_class, attr, code) 
     return result_class 
    @staticmethod 
    def _wrap(base, code): 
     def wrapper(*args, **kwargs): 
      if args: 
       cls = args[0] 
      result = code(*args, **kwargs) 
      if type(result) == base: 
       return cls.__class__(result) 
      elif isinstance(result, (tuple, list, set)): 
       new_result = [] 
       for partial in result: 
        if type(partial) == base: 
         new_result.append(cls.__class__(partial)) 
        else: 
         new_result.append(partial) 
       result = result.__class__(new_result) 
      elif isinstance(result, dict): 
       for key in result: 
        value = result[key] 
        if type(value) == base: 
         result[key] = cls.__class__(value) 
      return result 
     wrapper.__name__ = code.__name__ 
     wrapper.__doc__ = code.__doc__ 
     return wrapper 

class MySet(set, metaclass=Perpetuate): 
    pass 

s1 = MySet([1, 2, 3, 4, 5]) 

s2 = MySet([1, 2, 3, 6, 7]) 

print(s1.union(s2)) 
print(type(s1.union(s2))) 

print(s1.intersection(s2)) 
print(type(s1.intersection(s2))) 

print(s1.difference(s2)) 
print(type(s1.difference(s2))) 
+0

Một vài ý kiến: 1. Điều này sẽ không bọc một phương thức được gọi là 'e()', nhưng nó bọc '__getattribute __()', ngăn chặn để lưu trữ các đối tượng của kiểu cơ sở trong các thuộc tính. 2. Điều này sẽ có một hit hiệu suất nghiêm trọng, đặc biệt là để lấy các thuộc tính.Nếu bạn lưu trữ một danh sách trong một thuộc tính, nó sẽ được lặp lại trên mọi truy cập. Có nhiều vấn đề hiệu suất hơn, có thể quá nhiều để chỉ ra. –

+0

@SvenMarnach: Tại sao nó không bọc 'e()'? –

+0

Vì tên 'e', điều kiện' attr in ('__new __') 'sẽ giữ. Phải thừa nhận rằng, đó là một cái giá rẻ, nhưng có nhiều lỗi mơ hồ hơn trong mã này. –

0

Là một theo dõi câu trả lời của Sven, đây là một giải pháp gói phổ quát mà sẽ chăm sóc của tất cả các phi đặc biệt phương pháp. Ý tưởng là để nắm bắt tra cứu đầu tiên đến từ một cuộc gọi phương thức, và cài đặt một phương thức trình bao mà thực hiện chuyển đổi kiểu. Tại lần tra cứu tiếp theo, trình bao bọc được trả về trực tiếp.

Hãy cẩn thận:

1) Đây là phép thuật phức tạp hơn tôi muốn có trong mã của mình.

2) Tôi vẫn sẽ cần phải quấn phương pháp đặc biệt (__and__ vv) bằng tay vì tra cứu của họ bỏ qua __getattribute__

import types 

class MySet(set): 

    def __getattribute__(self, name): 
     attr = super(MySet, self).__getattribute__(name) 
     if isinstance(attr, types.BuiltinMethodType): 
      def wrapper(self, *args, **kwargs): 
       result = attr(self, *args, **kwargs) 
       if isinstance(result, set): 
        return MySet(result) 
       else: 
        return result 
      setattr(MySet, name, wrapper) 
      return wrapper 
     return attr 
Các vấn đề liên quan