2010-01-09 11 views
29

Dưới đây là một câu hỏi gấp đôi, với một phần lý thuyết và thực tiễn một:Phân lớp dict: nên dict .__ init __() được gọi?

Khi subclassing dict:

class ImageDB(dict): 
    def __init__(self, directory): 
     dict.__init__(self) # Necessary?? 
     ... 

nên dict.__init__(self) được gọi là, cũng giống như một "an toàn" đo (ví dụ, trong trường hợp có một số chi tiết triển khai không quan trọng nào quan trọng)? có nguy cơ mã ngắt với phiên bản Python trong tương lai không nếu dict.__init__()không phải là được gọi là? Tôi đang tìm kiếm một lý do cơ bản để làm một việc này hay thứ khác, ở đây (thực tế, gọi dict.__init__() là an toàn).

Tôi đoán là khi ImageDB.__init__(self, directory) được gọi, tự đã là một đối tượng dict trống mới, và do đó không cần gọi dict.__init__ (tôi muốn lệnh dict trống, lúc đầu). Điều này có đúng không?

Sửa:

Câu hỏi thực tế hơn đằng sau những câu hỏi cơ bản ở trên là như sau. Tôi đã nghĩ về subclassing dict bởi vì tôi sẽ sử dụng cú pháp db […] khá thường xuyên (thay vì làm db.contents […] mọi lúc); dữ liệu duy nhất của đối tượng (thuộc tính) thực sự là một dict. Tôi muốn thêm một vài phương thức vào cơ sở dữ liệu (chẳng hạn như get_image_by_name(), hoặc get_image_by_code(), ví dụ) và chỉ ghi đè __init__(), bởi vì cơ sở dữ liệu hình ảnh được xác định bởi thư mục chứa nó.

Nói tóm lại thì (thực tế) câu hỏi có thể là: là những gì thực hiện tốt cái gì đó hoạt động như một cuốn từ điển, ngoại trừ việc khởi tạo của nó là khác nhau (nó chỉ mất một tên thư mục), và rằng nó có thêm phương pháp?

"Nhà máy" được đề cập trong nhiều câu trả lời. Vì vậy, tôi đoán nó tất cả các boils xuống: làm bạn phân lớp dict, ghi đè lên __init__() và thêm phương pháp, hoặc bạn viết một (nhà máy) chức năng trả về một dict, mà bạn thêm phương pháp? Tôi có khuynh hướng thích giải pháp đầu tiên, bởi vì hàm factory trả về một đối tượng có kiểu không chỉ ra rằng nó có các ngữ nghĩa và phương thức bổ sung, nhưng bạn nghĩ gì?

Chỉnh sửa 2:

tôi thu thập từ các câu trả lời mọi người rằng nó không phải là một ý tưởng tốt để phân lớp dict khi lớp mới "không phải là một cuốn từ điển", và đặc biệt khi phương pháp __init__ của nó không thể đi cùng đối số là __init__ của dict (đây là trường hợp trong "câu hỏi thực tế" ở trên). Nói cách khác, nếu tôi hiểu đúng, sự đồng thuận dường như là: khi bạn phân lớp, tất cả các phương thức (bao gồm khởi tạo) phải có chữ ký giống như các phương thức lớp cơ sở. Điều này cho phép isinstance (subclass_instance, dict) đảm bảo rằng subclass_instance.__init__() có thể được sử dụng như dict.__init__(), ví dụ.

Một câu hỏi thực tế khác sau đó bật lên: làm thế nào một lớp học giống như dict, ngoại trừ phương pháp khởi tạo của nó, được thực hiện? mà không có phân lớp? điều này sẽ yêu cầu một số mã boilerplate khó chịu, không?

+0

Chức năng của nhà máy là cách thực hiện. Nếu bạn cần tùy chỉnh hành vi * instance *, thì bạn có thể muốn tạo một lớp con. Nếu bạn chỉ muốn ghi đè * khởi tạo *, bạn không cần phải phân lớp bất kỳ thứ gì, vì các cá thể của bạn không khác với các cá thể chuẩn. Hãy nhớ rằng __init__ không được coi là một phần của giao diện của cá thể, mà là của lớp. –

+0

Theo như tôi thấy nó cho vấn đề này nó sẽ là tốt nhất để thêm phương pháp '__getitem__' vào ImageDB của bạn thay vì subclassing một dict, bởi vì nó là _not_ một dict. Điều này cho phép bạn làm những gì bạn muốn, _without_ có tất cả các phương thức như 'pop()' có vẻ không phù hợp với lớp của bạn. –

+0

@gs: Tốt, về pop; nó thực sự, ít nhất là cho thời điểm này, không liên quan (nội dung cơ sở dữ liệu được xác định khi khởi tạo chỉ). Tôi nghĩ rằng nó thực sự là tốt nhất mà việc thực hiện chặt chẽ phù hợp với các tính năng cần thiết. – EOL

Trả lời

15

Bạn có thể gọi dict.__init__(self) khi phân lớp; trên thực tế, bạn không biết điều gì đang xảy ra chính xác trong dict (vì nó là nội trang) và điều đó có thể khác nhau giữa các phiên bản và triển khai. Không gọi nó có thể dẫn đến hành vi không đúng, vì bạn không thể biết được nơi dict đang giữ cấu trúc dữ liệu nội bộ của nó.

Nhân tiện, bạn không cho chúng tôi biết bạn muốn làm gì; nếu bạn muốn một lớp với hành vi dict (ánh xạ), và bạn không thực sự cần một dict (ví dụ như không có mã nào đang làm isinstance(x, dict) ở bất kỳ nơi nào trong phần mềm của bạn), bạn nên sử dụng UserDict.UserDict hoặc UserDict.DictMixin nếu bạn đang ở trên python < = 2.5 hoặc collections.MutableMapping nếu bạn đang ở trên python> = 2.6. Những người sẽ cung cấp cho lớp học của bạn với một hành vi dict tuyệt vời.

EDIT: Tôi đọc trong một nhận xét khác rằng bạn không ghi đè bất kỳ phương pháp nào của dict! Sau đó, không có điểm trong phân lớp ở tất cả, không làm điều đó.

def createImageDb(directory): 
    d = {} 
    # do something to fill in the dict 
    return d 

EDIT 2: bạn muốn kế thừa từ dict để thêm phương pháp mới, nhưng bạn không cần ghi đè lên bất kỳ phương pháp nào. Hơn một lựa chọn tốt có thể là:

class MyContainer(dict): 
    def newmethod1(self, args): 
     pass 

    def newmethod2(self, args2): 
     pass 


def createImageDb(directory): 
    d = MyContainer() 
    # fill the container 
    return d 

Nhân tiện: bạn đang thêm phương pháp nào? Bạn có chắc là bạn đang tạo ra một sự trừu tượng tốt? Có lẽ bạn nên sử dụng một lớp học xác định các phương pháp bạn cần và sử dụng một dict "bình thường" trong nội bộ để nó.

Factory func: http://en.wikipedia.org/wiki/Factory_method_pattern

Nó chỉ đơn giản là một cách ủy thác việc xây dựng một thể hiện một chức năng thay vì trọng/thay đổi nhà xây dựng của nó.

+2

+1: phân lớp khi không cần phân lớp là ý tưởng tồi, nhà máy tốt hơn nhiều. –

+0

Thậm chí nếu tôi không ghi đè phương pháp dict, lớp mới không có phương pháp bổ sung,… (Tôi đang nghiên cứu các nhà máy, cảm ơn bạn cho con trỏ!) – EOL

+0

Tôi không chắc chắn về UserDict: tài liệu đọc "Mô-đun này cũng Sự cần thiết cho lớp này đã được thay thế phần lớn bởi khả năng phân lớp trực tiếp từ dict (một tính năng có sẵn bắt đầu với Python phiên bản 2.2). " – EOL

2

PEP 372 đề với việc thêm lệnh được đặt hàng vào mô-đun bộ sưu tập.

Nó cảnh báo rằng "phân lớp dict là một nhiệm vụ không tầm thường và nhiều triển khai không ghi đè tất cả các phương thức đúng cách có thể dẫn đến kết quả không mong muốn."

đề xuất (và được chấp nhận) patch để python3.1 sử dụng một __init__ trông như thế này:

+class OrderedDict(dict, MutableMapping): 
+ def __init__(self, *args, **kwds): 
+  if len(args) > 1: 
+   raise TypeError('expected at most 1 arguments, got %d' % len(args)) 
+  if not hasattr(self, '_keys'): 
+   self._keys = [] 
+  self.update(*args, **kwds) 

Dựa trên điều này, có vẻ như dict.__init__() không cần phải được gọi

. Chỉnh sửa: Nếu bạn không ghi đè hoặc mở rộng bất kỳ phương thức nào của dict, thì tôi đồng ý với Alan Franzoni: sử dụng nhà máy sản xuất dict thay vì phân lớp:

def makeImageDB(*args,**kwargs): 
    d = {} 
    # modify d 
    return d 
+0

Điều này thật thú vị. Bây giờ, không gọi 'dict .__ init __()' với Python 3.1 là an toàn, nhưng còn tương lai thì sao? Vì tôi không ghi đè lên bất kỳ phương thức nào, trong ImageDB, phân lớp phụ rất an toàn; chỉ khởi tạo là đặc biệt (nó xây dựng dict). – EOL

+0

Xin lỗi EOL, tôi không theo dõi bạn. Trong tâm trí của tôi, Python 3.1 là tương lai ... :) – unutbu

+0

Hãy xem xét những gì init thực sự đang làm. Nó cập nhật dict với tất cả các arg và từ khóa. Đó là điều mà lớp học của bạn sẽ phải làm, vì vậy hãy gọi dict. \ _ \ _ Init __ (self, * args, ** kwds) có thể đảm nhiệm điều đó cho bạn, hoặc bạn sẽ phải gọi self.update, như OrderedDict làm. –

10

Bạn thường nên gọi lớp cơ sở '__init__ tại sao lại tạo ngoại lệ tại đây?

Dù không ghi đè __init__ hoặc nếu bạn cần phải ghi đè __init__ gọi cơ sở lớp __init__, Nếu bạn lo lắng về việc tranh cãi chỉ cần vượt qua args *, ** kwargs hoặc không có gì nếu bạn muốn có sản phẩm nào dict ví dụ

class MyDict(dict): 
    def __init__(self, *args, **kwargs): 
     myparam = kwargs.pop('myparam', '') 
     dict.__init__(self, *args, **kwargs) 

Chúng ta không nên giả định gì baseclass đang làm hay không làm, đó là sai lầm không để gọi lớp cơ sở __init__

+0

Gọi dict '__init__' thực sự là những gì Tôi hiện đang làm. Vì có vẻ như gọi nó là không có đối số không làm gì cả, tôi chỉ tò mò về các sự kiện cơ bản về Python mà sẽ cho phép nó không được gọi! – EOL

+0

@EOL, IMO nó chỉ là đồng bằng sai không gọi baseclass __init__, cho đến khi có một lý do rất rất mạnh mẽ để làm khác –

+0

@Anurag: Tôi thấy quan điểm của bạn. Tôi đang cố gắng đẩy kiến ​​thức của mình về Python thêm một chút nữa, và tự hỏi liệu một "lý do rất mạnh" như vậy không gọi là 'dict .__ init __ (tự)' (không có đối số khác) tồn tại (như "nó sẽ không bao giờ làm bất cứ điều gì "). – EOL

3

Cẩn thận với tẩy khi subclassing dict; ví dụ này cần __getnewargs__ trong 2,7, và có thể __getstate__ __setstate__ trong các phiên bản cũ hơn. (Tôi không biết tại sao.)

class Dotdict(dict): 
    """ d.key == d["key"] """ 

    def __init__(self, *args, **kwargs): 
     dict.__init__(self, *args, **kwargs) 
     self.__dict__ = self 

    def __getnewargs__(self): # for cPickle.dump(d, file, protocol=-1) 
     return tuple(self)