2012-11-07 38 views
13

Khi tìm kiếm cách làm việc với các từ điển lồng nhau, tôi đã tìm thấy mã sau đây được đăng bởi nosklo, mà tôi muốn được giải thích, vui lòng.Trong python, lớp AutoVivification sau hoạt động như thế nào?

class AutoVivification(dict): 
    """Implementation of perl's autovivification feature.""" 
    def __getitem__(self, item): 
     try: 
      return dict.__getitem__(self, item) 
     except KeyError: 
      value = self[item] = type(self)() 
      return value 

Thử nghiệm:

a = AutoVivification() 

a[1][2][3] = 4 
a[1][3][3] = 5 
a[1][2]['test'] = 6 

print a 

Output:

{1: {2: {'test': 6, 3: 4}, 3: {3: 5}}} 

Tôi là một lập trình viên người mới khá. Tôi đã học được hầu hết những gì tôi biết về thời gian của mình ở bên cạnh, với sự huấn luyện chính thức duy nhất của tôi là về Turbo Pascal ở trường trung học. Tôi hiểu và có thể sử dụng các lớp theo những cách đơn giản, chẳng hạn như sử dụng __init__, phương pháp lớp và lưu trữ dữ liệu trong các phiên bản của lớp học với foo.man = 'choo'.

Tôi không biết làm cách nào mà hàng loạt dấu ngoặc vuông được chỉ dẫn, chính xác, thông qua lớp (tôi đoán họ đang gọi __getitem__ bằng cách nào đó) và không hiểu cách chúng được xử lý một cách chính xác mà không cần phải gọi phương thức ba lần riêng lẻ.

Tôi đã có ấn tượng rằng (dict) trong tuyên bố lớp học sẽ được xử lý bởi __init__.

Tôi đã sử dụng try: except: trước đây, mặc dù một lần nữa, theo những cách khá đơn giản. Nó trông giống như tôi try, khi nó chạy, đang gọi một loạt các chức năng __getitem__. Tôi thu thập rằng nếu từ điển của cấp độ hiện tại tồn tại, thử sẽ vượt qua và đi đến từ điển tiếp theo. Các except, tôi thu thập, chạy khi có một KeyError nhưng tôi đã không nhìn thấy self được sử dụng như thế trước đây. Self đang được đối xử như một từ điển trong khi tôi nghĩ rằng self là một ví dụ của class AutoVivification ... là cả hai? Tôi chưa bao giờ được chỉ định hai lần liên tiếp như thế này foo = man = choo nhưng nghi ngờ rằng value trỏ đến self[item] trong khi self[item] trỏ đến kết quả của type(self). Nhưng type(self) sẽ trả về một cái gì đó như thế này: <class '__main__.AutoVivification'> phải không? Tôi không có ý kiến ​​gì về các dấu ngoặc tròn thêm ở cuối. Bởi vì tôi không biết làm thế nào chức năng được gọi là, tôi không hiểu nơi value đang được trả lại.

Xin lỗi vì tất cả các câu hỏi! Có rất nhiều điều trong điều này mà tôi không hiểu và tôi không biết nơi để tìm kiếm nó lên ngắn đọc qua tài liệu cho giờ mà tôi muốn giữ lại rất ít. Mã này có vẻ như nó sẽ phục vụ cho mục đích của tôi nhưng tôi muốn hiểu nó trước khi sử dụng nó.

Trong trường hợp bạn muốn biết những gì tôi đang cố gắng thực hiện trong chương trình của mình với các từ điển lồng nhau: Tôi đang cố giữ dữ liệu bản đồ theo thang đo thiên văn. Mặc dù tôi không thể tạo từ điển/danh sách 10^6 mục được lồng 4 lần (có thể là 10^24 mục!), Khoảng trống hầu như trống nên tôi có thể để trống các giá trị trống và chỉ gán khi có thứ gì đó ở đó. Điều khiến tôi bối rối là một cách hiệu quả để xử lý các từ điển.

Trả lời

18

từng dòng:

class AutoVivification(dict): 

Chúng tôi làm cho một lớp con của dict, vì vậy AutoVivification là một loại dict, với một số thay đổi địa phương.

def __getitem__(self, item): 

Các __getitem()__ hook được gọi bất cứ khi nào ai đó cố gắng truy cập vào một mục trên ví dụ thông qua [...] tra cứu chỉ số. Vì vậy, bất cứ khi nào ai đó thực hiện object[somekey], type(object).__getitem__(object, somekey) được gọi.

Chúng tôi sẽ bỏ qua try trong chốc lát, dòng tiếp theo là:

return dict.__getitem__(self, item) 

này gọi cởi phương pháp __getitem__(), và vượt qua trong trường hợp của chúng ta với nó, cùng với chìa khóa. Nói cách khác, chúng tôi gọi số gốc__getitem__ như được xác định bởi lớp cha mẹ của chúng tôi dict.

Bây giờ, tất cả chúng ta đều biết điều gì sẽ xảy ra nếu không có phím item trong từ điển, số KeyError được nâng lên. Đây là nơi các try:, except KeyError kết hợp đi kèm trong:

try: 
     return dict.__getitem__(self, item) 
    except KeyError: 
     value = self[item] = type(self)() 
     return value 

Vì vậy, nếu trường hợp hiện tại (mà là một tiểu loại dict) không có một phím nào đó, nó sẽ bắt KeyError ngoại lệ phương thức gốc dict.__getitem__() ném và thay vào đó chúng tôi tạo một giá trị mới, lưu trữ giá trị đó trong self[item] và trả về giá trị đó.

Bây giờ, hãy nhớ rằng self là (phân lớp) là dict, vì vậy, đó là từ điển. Do đó, nó có thể gán các giá trị mới (mà nó sẽ sử dụng __setitem__ hook, một cách tình cờ), và trong trường hợp này nó tạo ra một thể hiện mới cùng loại với self. Đó là một lớp con khác là dict.

Vậy điều gì sẽ xảy ra chi tiết khi chúng tôi gọi a[1][2][3] = 4? Python thực hiện từng bước này:

  1. a[1] dẫn đến type(a).__getitem__(a, 1).Phương thức tùy chỉnh __getitem__ của AutoVivification bắt các số KeyError, tạo ra một ví dụ mới của AutoVivification, lưu trữ dưới khóa 1 và trả lại.

  2. a[1] trả lại phiên bản trống AutoVivification. Mục tiếp theo truy cập [2] được gọi trên đối tượng đó và chúng ta lặp lại những gì đã xảy ra ở bước 1; có KeyError, một phiên bản mới của AutoVivification được tạo, được lưu trữ dưới khóa 2 và phiên bản mới đó được trả lại cho người gọi.

  3. a[1][2] trả lại phiên bản trống AutoVivification. Mục tiếp theo truy cập [3] được gọi trên đối tượng đó và chúng tôi lặp lại những gì đã xảy ra ở bước 1 (và trong bước 2). Có một KeyError, một phiên bản mới của AutoVivification được tạo, được lưu trữ dưới khóa 3 và phiên bản mới đó được trả lại cho người gọi.

  4. a[1][2][3] trả về trường hợp rỗng AutoVivification. Bây giờ chúng tôi lưu trữ một giá trị mới trong trường hợp đó, 4.

Khi bạn đi đến dòng tiếp theo của bạn mã, a[1][3][3] = 5, các trường hợp cấp cao nhất AutoVivification đã có một chìa khóa 1, và dòng return dict.__getitem__(self, item) sẽ trả về giá trị tương ứng, mà sẽ xảy ra là AutoVivification dụ tạo ra trong bước một ở trên.

Từ đó, cuộc gọi truy cập [3] mục sẽ tạo ra một AutoVivification dụ mới một lần nữa (vì đối tượng tại a[1] chỉ có một chìa khóa 2), và chúng tôi đi qua tất cả các bước tương tự một lần nữa.

2

Xem tài liệu object.__getitem__, để bắt đầu.

Việc kê khai class AutoVivification(dict) làm AutoVivification một lớp con của dict, vì vậy nó cư xử như nhau là như dict sẽ trừ khi nó quan trọng hơn một cách rõ ràng một số hành vi - như lớp này thực hiện khi nó quan trọng hơn __getitem__.

Các cuộc gọi đến dict.__getitem__(self, item) thường sẽ được viết thay vì như:

super(AutoVivification, self).__getitem__(item) 

(. Ít nhất bằng Python 2.x; Python 3 có cú pháp tốt hơn) Dù bằng cách nào, điều này làm là cố gắng để cho mặc định dict hành vi chạy, nhưng thực hiện dự phòng trong trường hợp không hoạt động.

type(self)() đầu tiên tra cứu đối tượng lớp tương ứng với cá thể self và sau đó gọi đối tượng lớp - trong trường hợp này giống như viết AutoVivification(), trông quen thuộc hơn nhiều.

Hy vọng sẽ xóa nó cho bạn!

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