2009-05-15 45 views
36

Tôi vừa mới chiến đấu với một lỗi trong Python. Đó là một trong những lỗi newbie ngớ ngẩn, nhưng nó đã cho tôi suy nghĩ về các cơ chế của Python (Tôi là một lập trình viên C++ thời gian dài, mới với Python). Tôi sẽ đặt ra mã lỗi và giải thích những gì tôi đã làm để sửa chữa nó, và sau đó tôi có một vài câu hỏi ...Thành viên lớp học Python Khởi tạo

Kịch bản: Tôi có một lớp, có thành viên dữ liệu từ điển, sau đây là mã của nó (đây là đơn giản hóa tất nhiên):

class A: 
    dict1={} 

    def add_stuff_to_1(self, k, v): 
     self.dict1[k]=v 

    def print_stuff(self): 
     print(self.dict1) 

lớp sử dụng mã này là lớp B:

class B: 

    def do_something_with_a1(self): 
     a_instance = A() 
     a_instance.print_stuff()   
     a_instance.add_stuff_to_1('a', 1) 
     a_instance.add_stuff_to_1('b', 2)  
     a_instance.print_stuff() 

    def do_something_with_a2(self): 
     a_instance = A()  
     a_instance.print_stuff()    
     a_instance.add_stuff_to_1('c', 1) 
     a_instance.add_stuff_to_1('d', 2)  
     a_instance.print_stuff() 

    def do_something_with_a3(self): 
     a_instance = A()  
     a_instance.print_stuff()    
     a_instance.add_stuff_to_1('e', 1) 
     a_instance.add_stuff_to_1('f', 2)  
     a_instance.print_stuff() 

    def __init__(self): 
     self.do_something_with_a1() 
     print("---") 
     self.do_something_with_a2() 
     print("---") 
     self.do_something_with_a3() 

Chú ý rằng tất cả các cuộc gọi đến do_something_with_aX() khởi một "sạch" mới thể hiện của lớp A, và in từ điển trước và sau khi thêm vào.

Các lỗi (nếu bạn chưa hình dung nó ra chưa):

>>> b_instance = B() 
{} 
{'a': 1, 'b': 2} 
--- 
{'a': 1, 'b': 2} 
{'a': 1, 'c': 1, 'b': 2, 'd': 2} 
--- 
{'a': 1, 'c': 1, 'b': 2, 'd': 2} 
{'a': 1, 'c': 1, 'b': 2, 'e': 1, 'd': 2, 'f': 2} 

Trong khởi thứ hai của lớp A, từ điển không có sản phẩm nào, nhưng bắt đầu với những nội dung của việc khởi tạo trước, và kể từ đó trở đi. Tôi mong họ bắt đầu "tươi".

gì giải quyết này "lỗi" được rõ ràng thêm:

self.dict1 = {} 

Trong __init__ constructor của lớp A. Tuy nhiên, điều đó khiến tôi ngạc nhiên:

  1. ý nghĩa của "dict1 là gì = {} "khởi tạo tại điểm khai báo của dict1 (dòng đầu tiên trong lớp A)? Nó là vô nghĩa?
  2. Cơ chế khởi tạo là nguyên nhân sao chép tham chiếu từ lần khởi chạy cuối cùng?
  3. Nếu tôi thêm "self.dict1 = {}" vào hàm tạo (hoặc bất kỳ thành viên dữ liệu nào khác), nó không ảnh hưởng như thế nào đến thành viên từ điển của các phiên bản khởi tạo trước đó?

EDIT: Sau câu trả lời bây giờ tôi hiểu rằng bằng cách tuyên bố một thành viên dữ liệu và không đề cập đến nó trong __init__ hoặc ở một nơi khác như self.dict1, tôi hầu như xác định những gì được gọi là trong C++/Java một thành viên dữ liệu tĩnh. Bằng cách gọi nó self.dict1 tôi đang làm cho nó "dụ ràng buộc".

+0

Bạn nên sử dụng các lớp kiểu mới, lấy được từ đối tượng. – nikow

Trả lời

49

Điều bạn liên tục đề cập đến dưới dạng lỗi là documented, hành vi tiêu chuẩn của các lớp Python.

Khai báo một dict bên ngoài __init__ như ban đầu bạn đã làm là khai báo biến cấp lớp. Nó chỉ được tạo ra một lần lúc đầu, bất cứ khi nào bạn tạo ra các đối tượng mới nó sẽ sử dụng lại cùng một dict này. Để tạo các biến mẫu, bạn khai báo chúng với self trong __init__; Nó đơn giản như vậy.

+12

Tôi không nói đó là lỗi của Python, tôi đã nói đó là lỗi của tôi ... Khi một lập trình viên làm điều gì đó chống lại tài liệu, nó vẫn là một lỗi (một điều ngớ ngẩn như tôi đã đề cập). Câu hỏi của tôi là về các cơ chế kích hoạt "hành vi chuẩn mực, được ghi lại" - tôi muốn hiểu những gì dưới mui xe. Cảm ơn. Và đặc biệt là câu hỏi đầu tiên của tôi, tôi muốn hiểu nếu việc khai báo là vô dụng. –

+3

Nó không vô ích; nếu bạn muốn một biến tồn tại thông qua các trường hợp bạn sẽ sử dụng nó. Vấn đề của bạn là khi bạn làm x = A() chỉ có mã __init__ được thực thi. Biến lớp vẫn tồn tại. –

+0

Ok miễn là một khai báo không có trong __init__, nó giống như một thành viên dữ liệu "tĩnh" trong C++, và khai báo trong "__init__" thay đổi nó từ tĩnh thành thể hiện ràng buộc? Bây giờ nó rõ ràng, cảm ơn. –

1

Khi bạn truy cập thuộc tính dụ, ví dụ: self.foo, trăn đầu tiên sẽ tìm 'foo' trong self.__dict__. Nếu không tìm thấy, python sẽ tìm thấy 'foo' trong TheClass.__dict__

Trong trường hợp của bạn, dict1 thuộc loại A, không phải.

+1

Điều này thực sự đã giúp mô hình trực quan của tôi về cách hoạt động của nó. Vì vậy, một khi bạn ràng buộc self.x với một cái gì đó trong đối tượng của bạn (hoặc trong __init__ hoặc một nơi nào đó khác) tra cứu self.x trong tương lai sẽ tham chiếu đến biến mới trong phạm vi cá thể của bạn. Nếu họ không tìm thấy nó trong phạm vi cá thể họ nhìn vào phạm vi lớp. –

0

Tuyên bố lớp Pythons được thực thi dưới dạng khối mã và bất kỳ định nghĩa biến cục bộ nào (trong đó định nghĩa hàm là loại đặc biệt) được lưu trữ trong cá thể lớp được xây dựng. Do cách thức thuộc tính tra cứu các công trình trong Python, nếu một thuộc tính không được tìm thấy trên cá thể giá trị trên lớp được sử dụng.

Đây là an interesting article about the class syntax về lịch sử của blog Python.

0

Nếu đây là mã của bạn:

class ClassA: 
    dict1 = {} 
a = ClassA() 

Sau đó, bạn có thể mong đợi điều này xảy ra bên trong Python:

class ClassA: 
    __defaults__['dict1'] = {} 

a = instance(ClassA) 
# a bit of pseudo-code here: 
for name, value in ClassA.__defaults__: 
    a.<name> = value 

Theo như tôi có thể nói, đó gì xảy ra, ngoại trừ việc a dict có con trỏ được sao chép, thay vì giá trị, là hành vi mặc định ở mọi nơi trong Python. Hãy xem mã này:

a = {} 
b = a 
a['foo'] = 'bar' 
print b 
2

@Matthew: Vui lòng xem xét sự khác biệt giữa thành viên lớp học và thành viên đối tượng trong Lập trình hướng đối tượng. Vấn đề này xảy ra vì khai báo của dict gốc làm cho nó trở thành một thành viên của lớp, và không phải là một thành viên đối tượng (như là ý định của poster gốc.) Do đó, nó tồn tại một lần cho (được chia sẻ) tất cả các cá thể của lớp (tức là một lần cho chính lớp đó, là một thành viên của chính đối tượng lớp) vì vậy hành vi là hoàn toàn chính xác.

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