2011-02-01 35 views
93

Câu hỏi tôi sắp hỏi có vẻ trùng lặp với Python's use of __new__ and __init__?, nhưng không phân biệt, chính xác sự khác biệt thực tế giữa __new____init__ là gì.Python (và Python C API): __new__ so với __init__

Trước khi bạn vội vàng nói với tôi rằng __new__ là để tạo đối tượng và __init__ là để khởi tạo đối tượng, hãy để tôi rõ ràng: Tôi hiểu điều đó. Trong thực tế, sự khác biệt đó là khá tự nhiên đối với tôi, vì tôi có kinh nghiệm trong C++, nơi chúng tôi có placement new, mà tương tự tách phân bổ đối tượng từ khởi tạo.

Các Python C API tutorial giải thích nó như thế này:

Các thành viên mới có trách nhiệm tạo (như trái ngược với khởi tạo) đối tượng của các loại. Nó được hiển thị trong Python làm phương thức __new__(). ... Một lý do để thực hiện một phương pháp mới là đảm bảo các giá trị ban đầu của biến thể hiện.

Vì vậy, yeah - Tôi được__new__ có, nhưng bất chấp điều này, tôi vẫn không hiểu tại sao nó rất hữu ích trong Python. Ví dụ cho biết rằng __new__ có thể hữu ích nếu bạn muốn "đảm bảo các giá trị ban đầu của các biến mẫu". Vâng, không phải là chính xác những gì __init__ sẽ làm gì?

Trong hướng dẫn API C, một ví dụ được hiển thị khi một Loại mới (được gọi là "Noddy") được tạo và chức năng __new__ của Type được xác định. Loại Noddy chứa một thành viên chuỗi gọi first, và là thành viên chuỗi này được khởi tạo một chuỗi rỗng như vậy:

static PyObject * Noddy_new(PyTypeObject *type, PyObject *args, PyObject *kwds) 
{ 
    ..... 

    self->first = PyString_FromString(""); 
    if (self->first == NULL) 
    { 
     Py_DECREF(self); 
     return NULL; 
    } 

    ..... 
} 

Lưu ý rằng nếu không có __new__ phương pháp định nghĩa ở đây, chúng ta sẽ phải sử dụng PyType_GenericNew, mà chỉ đơn giản khởi tất cả các thành viên biến đối tượng thành NULL. Vì vậy, lợi ích duy nhất của phương thức __new__ là biến cá thể sẽ bắt đầu như một chuỗi rỗng, trái ngược với NULL. Nhưng tại sao điều này lại hữu ích, vì nếu chúng ta quan tâm đến việc đảm bảo các biến mẫu của chúng ta được khởi tạo thành một giá trị mặc định, chúng ta có thể thực hiện điều đó trong phương thức __init__?

Trả lời

105

Sự khác biệt chủ yếu phát sinh với các loại có thể thay đổi so với bất biến.

__new__ chấp nhận loại làm đối số đầu tiên và (thường) trả về phiên bản mới của loại đó. Vì vậy, nó phù hợp để sử dụng với cả hai loại có thể thay đổi và bất biến.

__init__ chấp nhận một đối số làm đối số đầu tiên và sửa đổi các thuộc tính của cá thể đó.Điều này là không thích hợp cho một loại bất biến, vì nó sẽ cho phép chúng được sửa đổi sau khi tạo bằng cách gọi obj.__init__(*args).

Hãy so sánh hành vi của tuplelist:

>>> x = (1, 2) 
>>> x 
(1, 2) 
>>> x.__init__([3, 4]) 
>>> x # tuple.__init__ does nothing 
(1, 2) 
>>> y = [1, 2] 
>>> y 
[1, 2] 
>>> y.__init__([3, 4]) 
>>> y # list.__init__ reinitialises the object 
[3, 4] 

Là tại sao họ riêng biệt (ngoài lý do lịch sử đơn giản): __new__ phương pháp đòi hỏi một loạt các soạn sẵn để có được quyền (việc tạo ra đối tượng ban đầu, và sau đó nhớ trả lại đối tượng ở cuối). Ngược lại, phương pháp __init__ chết đơn giản vì bạn chỉ cần đặt bất kỳ thuộc tính nào bạn cần đặt.

Ngoài __init__ phương pháp được dễ dàng hơn để viết, và có thể thay đổi so với phân biệt bất biến đã nói ở trên, sự tách biệt cũng có thể được khai thác để làm cho cách gọi tầng lớp phụ huynh __init__ trong lớp con tùy chọn bằng cách thiết lập bất kỳ bất biến dụ hoàn toàn cần thiết trong __new__. Điều này thường là một thực tế đáng ngờ mặc dù - nó thường là rõ ràng hơn để chỉ cần gọi các lớp cha mẹ __init__ phương pháp khi cần thiết.

+1

mã bạn gọi là "boilerplate" trong '__new__' không phải là bản mẫu, bởi vì boilerplate không bao giờ thay đổi. Đôi khi bạn cần thay thế mã cụ thể đó bằng một thứ khác. –

+10

Tạo, hoặc bằng cách khác thu được, cá thể (thường là với một cuộc gọi 'super') và trả về cá thể là các phần cần thiết của bất kỳ việc thực hiện' __new__' nào và "bản mẫu" mà tôi đang đề cập đến. Ngược lại, 'pass' là một thực thi hợp lệ cho' __init__' - không có hành vi bắt buộc nào. – ncoghlan

24

__new__() có thể trả về các đối tượng thuộc loại khác với loại được liên kết. __init__() chỉ khởi tạo phiên bản hiện tại của lớp học.

>>> class C(object): 
... def __new__(cls): 
...  return 5 
... 
>>> c = C() 
>>> print type(c) 
<type 'int'> 
>>> print c 
5 
+0

Đây là lời giải thích gọn gàng nhất cho đến nay. – Tarik

+0

Không hoàn toàn đúng. Tôi có '__init__' phương pháp có chứa mã trông giống như' self .__ class__ = type (...) '. Điều đó khiến đối tượng trở thành một lớp khác với lớp bạn nghĩ bạn đang tạo ra. Tôi thực sự không thể thay đổi nó thành một 'int' như bạn đã làm ... Tôi nhận được một lỗi về các loại đống hoặc một cái gì đó ... nhưng ví dụ của tôi về việc gán nó vào một lớp được tạo động được hoạt động. – ArtOfWarfare

31

Có thể có các cách sử dụng khác cho __new__ nhưng có một cách thực sự rõ ràng: Bạn không thể phân loại một loại không thay đổi được mà không sử dụng __new__. Ví dụ: giả sử bạn muốn tạo một lớp con của bộ dữ liệu có thể chỉ chứa các giá trị tích phân giữa 0 và size.

class ModularTuple(tuple): 
    def __new__(cls, tup, size=100): 
     tup = (int(x) % size for x in tup) 
     return super(ModularTuple, cls).__new__(cls, tup) 

Bạn chỉ đơn giản là không thể làm điều này với __init__ - nếu bạn cố gắng sửa đổi self trong __init__, người phiên dịch sẽ phàn nàn rằng bạn đang cố gắng sửa đổi một đối tượng không thay đổi.

+1

Tôi không hiểu tại sao chúng ta nên sử dụng siêu? Tôi có nghĩa là tại sao __new__ trả lại một thể hiện của lớp cha? Hơn nữa, như bạn đặt nó, tại sao chúng ta nên vượt qua cls một cách rõ ràng để __new__? siêu (ModularTuple, cls) không trả về một phương thức ràng buộc? – Alcott

+1

@Alcott, tôi nghĩ bạn hiểu lầm hành vi của '__new__'. Chúng tôi chuyển 'cls' một cách rõ ràng đến' __new__' bởi vì, vì bạn có thể đọc [ở đây] (http://docs.python.org/reference/datamodel.html#object.__new__) '__new__' _always_ yêu cầu một loại làm đầu tiên tranh luận. Sau đó nó trả về một thể hiện của kiểu đó. Vì vậy, chúng ta không trả về một thể hiện của lớp cha - chúng ta đang trả về một thể hiện của 'cls'. Trong trường hợp này, nó cũng giống như chúng ta đã nói 'tuple .__ new __ (ModularTuple, tup)'. – senderle

11

Không phải là câu trả lời hoàn chỉnh nhưng có thể là điều gì đó minh họa sự khác biệt.

__new__ sẽ luôn được gọi khi tạo đối tượng. Có một số tình huống mà __init__ sẽ không được gọi. Một ví dụ là khi bạn tháo các đối tượng ra khỏi một tập tin dưa, chúng sẽ được cấp phát (__new__) nhưng không được khởi tạo (__init__).

+0

Tôi có thể gọi __init__ từ __new__ nếu tôi muốn bộ nhớ được cấp phát và dữ liệu được khởi tạo không? Tại sao nếu __new__ không tồn tại khi tạo cá thể __init__ được gọi? –

+1

Công việc của phương thức '__new__' là * tạo * (điều này ngụ ý cấp phát bộ nhớ) một thể hiện của lớp và trả về nó. Việc khởi tạo là một bước riêng biệt và đó là những gì thường được hiển thị cho người dùng. Vui lòng đặt câu hỏi riêng nếu có vấn đề cụ thể mà bạn đang gặp phải. –

0

Chỉ muốn thêm một từ về mục đích (trái ngược với hành vi) của việc xác định __new__ so với __init__.

Tôi đã xem qua câu hỏi này (trong số những câu hỏi khác) khi tôi cố gắng hiểu cách tốt nhất để xác định nhà máy lớp học. Tôi nhận ra rằng một trong những cách thức mà __new__ là khái niệm khác nhau từ __init__ là một thực tế rằng các lợi ích của __new__ là chính xác những gì được nêu trong câu hỏi:

Vì vậy, lợi ích duy nhất của phương pháp mới là biến cá thể sẽ bắt đầu như một chuỗi rỗng, trái ngược với NULL. Nhưng tại sao điều này lại hữu ích, vì nếu chúng ta quan tâm đến việc đảm bảo các biến cá thể của chúng ta được khởi tạo thành một giá trị mặc định, chúng ta có thể thực hiện điều đó trong phương thức init?

Xem xét kịch bản đã nêu, chúng tôi quan tâm đến giá trị ban đầu của các biến mẫu khi ví dụ thực tế là một lớp.Vì vậy, nếu chúng ta đang tạo động một đối tượng lớp khi chạy và chúng ta cần xác định/điều khiển một cái gì đó đặc biệt về các cá thể tiếp theo của lớp này đang được tạo, chúng ta sẽ định nghĩa các điều kiện/thuộc tính này trong phương thức metaclass __new__.

Tôi đã nhầm lẫn về điều này cho đến khi tôi thực sự nghĩ về ứng dụng của khái niệm chứ không chỉ là ý nghĩa của nó. Dưới đây là ví dụ hy vọng tạo sự khác biệt rõ ràng:

a = Shape(sides=3, base=2, height=12) 
b = Shape(sides=4, length=2) 
print(a.area()) 
print(b.area()) 

# I want `a` and `b` to be an instances of either of 'Square' or 'Triangle' 
# depending on number of sides and also the `.area()` method to do the right 
# thing. How do I do that without creating a Shape class with all the 
# methods having a bunch of `if`s ? Here is one possibility 

class Shape: 
    def __new__(cls, sides, *args, **kwargs): 
     if sides == 3: 
      return Triangle(*args, **kwargs) 
     else: 
      return Square(*args, **kwargs) 

class Triangle: 
    def __init__(self, base, height): 
     self.base = base 
     self.height = height 

    def area(self): 
     return (self.base * self.height)/2 

class Square: 
    def __init__(self, length): 
     self.length = length 

    def area(self): 
     return self.length*self.length 

Lưu ý đây chỉ là một ví dụ điển hình. Có nhiều cách để có được một giải pháp mà không cần đến cách tiếp cận nhà máy của lớp như trên và thậm chí nếu chúng ta chọn để implelent giải pháp theo cách này, có một chút thận trọng bỏ ra vì lợi ích ngắn gọn (ví dụ, tuyên bố metaclass một cách rõ ràng)

Nếu bạn đang tạo một lớp thông thường (hay không phải là metaclass), thì __new__ không thực sự hợp lý trừ khi trường hợp đặc biệt như trường hợp có thể thay đổi hoặc không thay đổi trong câu trả lời ncoghlan's answer (về cơ bản là ví dụ cụ thể hơn) của khái niệm định nghĩa các giá trị/thuộc tính ban đầu của lớp/kiểu được tạo thông qua __new__ sau đó được khởi tạo thông qua __init__).

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