2009-06-02 22 views
13

Câu hỏi của tôi là một câu hỏi thiết kế. Trong Python, nếu mã trong "hàm tạo" của bạn không thành công, đối tượng sẽ không được định nghĩa. Do đó:Thực hành Xấu để chạy mã trong hàm tạo có khả năng bị lỗi?

someInstance = MyClass("test123") #lets say that constructor throws an exception 
someInstance.doSomething() # will fail, name someInstance not defined. 

Tôi có một trường hợp sao chép mã sẽ xảy ra nếu tôi xóa mã dễ bị lỗi từ hàm tạo của mình. Về cơ bản, hàm tạo của tôi điền vào một vài thuộc tính (thông qua IO, nơi có nhiều lỗi có thể đi sai) có thể được truy cập với các getters khác nhau. Nếu tôi xóa mã khỏi trình thu thập, tôi sẽ có 10 getters với mã dán sao chép giống như:

  1. là thuộc tính thực sự được đặt?
  2. làm một số hành động IO để điền vào các thuộc tính
  3. trở lại nội dung của các biến trong câu hỏi

Tôi không thích điều đó, bởi vì tất cả getters tôi sẽ chứa rất nhiều mã. Thay vào đó tôi thực hiện các hoạt động IO của tôi ở vị trí trung tâm, hàm tạo và điền vào tất cả các thuộc tính của tôi.

Làm cách nào để thực hiện điều này?

+0

ok, tôi sẽ nhớ rằng, tôi m vẫn còn mới ở đây và muốn cung cấp tín dụng cho những người dành thời gian quý báu của họ, cố gắng trả lời câu hỏi của tôi, tôi thực sự đánh giá cao nó! – Tom

+0

Câu hỏi này đến từ Python và thậm chí từ việc giải thích tất cả những gì về RAII hoặc tại sao chúng ta có thể/không thể thực hiện các hoạt động trong hàm tạo có thể tăng ngoại lệ? – lispmachine

+1

@Neil Butterworth: điều này không thực sự quan trọng vì bạn có thể chấp nhận một anwser tốt hơn sau này. –

Trả lời

5

Tôi không phải là nhà phát triển Python, nhưng nói chung, tốt nhất là tránh các hoạt động phức tạp/dễ bị lỗi trong hàm tạo của bạn. Một cách xung quanh điều này sẽ là đặt một phương thức "LoadFromFile" hoặc "Init" trong lớp của bạn để cư trú đối tượng từ một nguồn bên ngoài. Phương thức tải/init này sau đó phải được gọi riêng sau khi xây dựng đối tượng.

+0

Được rồi, tôi đoán bạn đã đúng.Tôi hy vọng sẽ có một sự chấp thuận tốt đẹp hơn, bởi vì tôi đoán sau đó tôi phải đi qua tất cả các kiểm tra này. đoán mã của tôi sẽ như thế nào mà sau đó: 1. constructor rỗng 2. một số phương pháp fileLoading 3. mỗi getter thực hiện cuộc gọi cho dù các tập tin đã được tải trước khi thực hiện công việc của mình
xấu xí, nhưng tôi đoán thats chỉ có thể cách – Tom

+3

Ý tưởng nhà máy được đề xuất bởi laalto có thể là một ý tưởng hay. Nhà máy sẽ ẩn cấu trúc 2 giai đoạn, vì vậy mã ứng dụng của bạn không cần phải biết về nó. (Tôi đã upvote câu trả lời của ông, nhưng tôi ra khỏi phiếu ngày hôm nay :). Tôi vẫn đứng theo tuyên bố của tôi rằng tốt hơn là không ném ngoại lệ từ một nhà xây dựng. –

3

Một mẫu phổ biến là xây dựng hai giai đoạn, cũng được Andy White đề xuất.

Giai đoạn đầu tiên: Trình tạo thông thường.

Giai đoạn thứ hai: Các thao tác có thể không thành công.

Tích hợp cả hai: Thêm phương thức nhà máy để thực hiện cả hai giai đoạn và làm cho hàm tạo được bảo vệ/riêng tư để ngăn chặn sự kiện bên ngoài phương thức nhà máy.

Ồ, và tôi không phải là nhà phát triển Python.

20

Trong C++ ít nhất, không có gì sai khi đặt mã dễ bị lỗi trong hàm tạo - bạn chỉ cần ném một ngoại lệ nếu xảy ra lỗi. Nếu mã này là cần thiết để xây dựng đúng đối tượng, thì không có sự thay thế nào (mặc dù bạn có thể trừu tượng mã thành các hàm con, hoặc tốt hơn vào các hàm tạo của các đối tượng con). Thực hành tồi tệ nhất là xây dựng một nửa đối tượng và sau đó mong đợi người dùng gọi các chức năng khác để hoàn thành việc xây dựng bằng cách nào đó.

+0

Mục tiêu-C sử dụng mô hình xây dựng 2 pha này khá nhiều ở khắp mọi nơi để tạo các đối tượng, vì vậy nó phải có một số công đức. –

+5

Việc ném một ngoại lệ trong hàm dựng C++ không gọi hàm hủy (vì đối tượng chưa được xây dựng). Bạn sẽ phải chắc chắn rằng bạn làm sạch bất cứ điều gì bạn đã quản lý để xây dựng trước khi ném. auto_ptrs tiện dụng. – laalto

+0

@Andy Tôi không thể nói cho Objective C, nhưng cách tiếp cận này thường có nghĩa là lưu trữ cờ của một số loại để chỉ ra tình trạng xây dựng - đây là lỗi dễ bị và (trong C++ ít nhất) không cần thiết. –

0

Nếu mã để khởi tạo các giá trị khác nhau thực sự đủ rộng để sao chép nó là không mong muốn (có vẻ như nó nằm trong trường hợp của bạn), cá nhân tôi sẽ chọn đưa khởi tạo bắt buộc vào phương thức riêng, thêm cờ vào cho biết liệu việc khởi tạo đã diễn ra chưa và làm cho tất cả các trình truy cập gọi phương thức khởi tạo nếu nó chưa được khởi tạo.

Trong trường hợp luồng, bạn có thể phải thêm bảo vệ bổ sung trong trường hợp khởi tạo chỉ được phép xảy ra một lần cho ngữ nghĩa hợp lệ (có thể hoặc có thể không phải do bạn đang xử lý tệp).

0

Một lần nữa, tôi có ít kinh nghiệm với Python, tuy nhiên trong C# thì tốt hơn nên thử và tránh việc có một hàm tạo ném một ngoại lệ.Một ví dụ về lý do tại sao mà lò xo trong tâm trí là nếu bạn muốn đặt constructor của bạn tại một điểm mà nó không thể bao quanh nó với một khối try {} catch {}, ví dụ khởi tạo một trường trong một lớp:

class MyClass 
{ 
    MySecondClass = new MySecondClass(); 
    // Rest of class 
} 

Nếu nhà xây dựng của MySecondClass ném một ngoại lệ mà bạn muốn xử lý bên trong MyClass thì bạn cần phải cấu trúc lại ở trên - chắc chắn nó không phải là kết thúc của thế giới, nhưng là một điều tốt đẹp để có.

Trong trường hợp này, cách tiếp cận của tôi có thể là di chuyển logic khởi tạo bị lỗi thành phương thức khởi tạo, và có getters gọi phương thức khởi tạo đó trước khi trả về bất kỳ giá trị nào.

Là một tối ưu hóa, bạn nên có getter (hoặc phương pháp khởi tạo) đặt một số loại "IsInitialised" boolean thành true, để chỉ ra rằng (khả năng tốn kém) initialisation không cần phải được thực hiện một lần nữa.

Trong pseudo-code (C# bởi vì tôi sẽ chỉ mess lên cú pháp của Python):

class MyClass 
{ 
    private bool IsInitialised = false; 

    private string myString; 

    public void Init() 
    { 
     // Put initialisation code here 
     this.IsInitialised = true; 
    } 

    public string MyString 
    { 
     get 
     { 
      if (!this.IsInitialised) 
      { 
       this.Init(); 
      } 

      return myString; 
     } 
    } 
} 

Đây là khóa học không thread-safe, nhưng tôi không nghĩ rằng đa luồng được sử dụng mà thường trong python vì vậy đây có thể là một vấn đề không phải dành cho bạn.

+0

Cá nhân tôi không thích có mỗi và mọi phương pháp trong một lớp học có một bản sao dán kiểm tra khởi tạo. Đó là lỗi dễ bị nếu ai đó làm việc trên mã và quên nó, và trong đôi mắt của tôi nó cũng không phải là đẹp, nhưng có lẽ đó chỉ là một vấn đề chủ quan của hương vị. Tôi thực sự hy vọng để có thể tránh được một cái gì đó như thế:/ – Tom

4

Thực tế không phải là điều xấu.

Nhưng tôi nghĩ bạn có thể ở sau một thứ gì đó khác ở đây. Trong ví dụ của bạn, phương thức doSomething() sẽ không được gọi khi hàm tạo MyClass không thành công. Hãy thử đoạn mã sau:

class MyClass: 
def __init__(self, s): 
    print s 
    raise Exception("Exception") 

def doSomething(self): 
    print "doSomething" 

try: 
    someInstance = MyClass("test123") 
    someInstance.doSomething() 
except: 
    print "except" 

Nó nên in:

test123 
except 

Đối với thiết kế phần mềm của bạn, bạn có thể hỏi những câu hỏi sau đây:

  • gì phạm vi của biến someInstance nên ? Ai là người dùng? Yêu cầu của họ là gì?

  • Lỗi ở đâu và như thế nào trong trường hợp một trong 10 giá trị của bạn không khả dụng?

  • Tất cả 10 giá trị nên được lưu vào bộ nhớ cache vào thời gian xây dựng hoặc được lưu vào bộ nhớ cache từng lần một khi chúng cần thiết lần đầu tiên?

  • Mã I/O có thể được cấu trúc lại thành phương thức trợ giúp hay không, do đó, làm điều gì đó tương tự 10 lần không dẫn đến lặp lại mã?

  • ...

30

Có sự khác biệt giữa một constructor trong C++ và một __init__ phương pháp bằng Python. Trong C++, nhiệm vụ của một hàm tạo là xây dựng một đối tượng. Nếu nó không thành công, không có destructor được gọi. Do đó nếu bất kỳ tài nguyên nào được mua trước khi một ngoại lệ bị ném, việc dọn dẹp phải được thực hiện trước khi thoát khỏi hàm tạo. Vì vậy, một số thích xây dựng hai giai đoạn với hầu hết các công trình được thực hiện bên ngoài nhà xây dựng (ugh).

Python có cấu trúc hai pha sạch hơn nhiều (xây dựng, sau đó khởi tạo). Tuy nhiên, nhiều người nhầm lẫn một phương pháp __init__ (trình khởi tạo) với một hàm tạo. Phương thức khởi tạo thực tế trong Python được gọi là __new__. Không giống như trong C++, nó không lấy một thể hiện, nhưng trả về một. Nhiệm vụ của __init__ là khởi tạo phiên bản đã tạo. Nếu một ngoại lệ được nâng lên trong __init__, destructor __del__ (nếu có) sẽ được gọi như mong đợi, vì đối tượng đã được tạo (mặc dù nó không được khởi tạo đúng) vào lúc __init__ được gọi.

Trả lời câu hỏi của bạn:

Trong Python, nếu mã trong "nhà xây dựng" của bạn thất bại, đối tượng kết thúc lên không được xác định.

Điều đó không đúng. Nếu __init__ đặt ra một ngoại lệ, đối tượng là được tạo nhưng không được khởi tạo đúng cách (ví dụ: một số thuộc tính không được gán ). Nhưng tại thời điểm nó được nâng lên, bạn có thể không có bất kỳ tham chiếu đến đối tượng này, vì vậy thực tế là các thuộc tính không được chỉ định không quan trọng. Chỉ có destructor (nếu có) cần kiểm tra xem các thuộc tính có thực sự tồn tại hay không.

Làm cách nào để thực hiện điều này?

Trong Python, khởi tạo đối tượng trong __init__ và đừng lo lắng về ngoại lệ. Trong C++, hãy sử dụng RAII. Cập nhật [quản lý về tài nguyên]


:

Trong rác thu ngôn ngữ, nếu bạn đang đối phó với các nguồn lực, đặc biệt là hạn chế như các kết nối cơ sở dữ liệu, nó tốt hơn không để phát hành chúng trong destructor. Điều này là do các đối tượng bị phá hủy theo cách không xác định và nếu bạn xảy ra để có một vòng tham chiếu (không phải lúc nào cũng dễ dàng biết) và ít nhất một trong các đối tượng trong vòng lặp có một destructor được xác định, họ sẽ không bao giờ bị phá hủy. Các ngôn ngữ được thu gom rác thải có các phương tiện khác để xử lý các tài nguyên. Trong Python, nó là with statement.

+1

Ahhh, cuối cùng, một anwser từ ai đó thực sự mã trong Python. Thks, tôi sẽ nói điều đó. Hơn nữa, nó hoàn toàn OK để bao quanh đối tượng của bạn tạo ra với một thử bắt ... –

+0

'với tuyên bố' ... Các bạn đề cập đến aaaaall thời gian này. Hãy xem xét một chương trình với GUI. Bạn tạo đối tượng trong hộp thoại '__init__' và muốn phát hành nó trong hộp thoại gần. Đối tượng này giữ một số tài nguyên không nhớ. Cái gì bây giờ? ... – cubuspl42

+0

@ cubuspl42 Trong trường hợp chương trình dựa trên sự kiện, tôi sẽ phát hành rõ ràng. Ví dụ trong GTK có tín hiệu "hủy" để nghe https://developer.gnome.org/pygtk/stable/class-gtkobject.html#signal-gtkobject--destroy – lispmachine

0

dường như Neil có một điểm tốt: bạn tôi chỉ chỉ cho tôi như thế này:

http://en.wikipedia.org/wiki/Resource_Acquisition_Is_Initialization

mà về cơ bản là những gì Neil nói ...

+1

Nhưng đó là C++/Ada không liên quan đến Python. Ngoài ra trong quản lý/thu thập tài nguyên ngôn ngữ được quản lý (thu gom rác) không được thực hiện bởi các nhà xây dựng/destructors. – lispmachine

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