16

Tôi tương đối mới đối với Python và đấu tranh để hòa giải các tính năng của ngôn ngữ với các thói quen tôi đã chọn từ nền của mình trong C++ và Java.Chức năng không phải thành viên và thành viên trong Python

Vấn đề mới nhất Tôi đang gặp đã làm với đóng gói, đặc biệt là một ý tưởng tốt nhất tóm tắt lên bởi mục 23 của Meyer "Effective C++":

Prefer non-member non-friend functions to member functions.

Bỏ qua việc thiếu một cơ chế friend trong chốc lát, được các hàm không thành viên xem xét thích hợp hơn để chức năng thành viên bằng Python, quá?

Một bắt buộc, ví dụ ngu si:

class Vector(object): 
    def __init__(self, dX, dY): 
     self.dX = dX 
     self.dY = dY 

    def __str__(self): 
     return "->(" + str(self.dX) + ", " + str(self.dY) + ")" 

    def scale(self, scalar): 
     self.dX *= scalar 
     self.dY *= scalar 

def scale(vector, scalar): 
    vector.dX *= scalar 
    vector.dY *= scalar 

Với v = Vector(10, 20), chúng ta có thể bây giờ hoặc là gọi v.scale(2) hoặc scale(v, 2) tăng gấp đôi độ lớn của vector.

Xem xét thực tế rằng chúng tôi đang sử dụng các thuộc tính trong trường hợp này, lựa chọn nào trong số hai tùy chọn - nếu có bất kỳ - nào tốt hơn và tại sao?

+2

Tôi cảm thấy điều này đơn giản là không đúng trong Python. Các đối số không thực sự ngồi với Python, nơi bạn có thể sửa đổi các lớp học dễ dàng như vậy. Python cũng tập trung vào khả năng đọc và tôi cảm thấy rằng '' v.scale (2) '' rõ ràng hơn nhiều so với '' scale (v, 2) ''. Nếu bạn nhìn vào thư viện chuẩn, tất cả nhưng các chức năng chung nhất được giữ như các thành viên chứ không phải là nội trang. –

Trả lời

15

Câu hỏi thú vị.

Bạn đang bắt đầu từ một nơi khác so với hầu hết các câu hỏi đến từ các lập trình viên Java, có xu hướng giả định rằng bạn cần các lớp học khi bạn hầu như không. Nói chung, trong Python không có điểm trong việc có các lớp trừ khi bạn đang thực hiện cụ thể việc đóng gói dữ liệu.

Tất nhiên, ở đây trong ví dụ của bạn, bạn đang thực sự làm điều đó, vì vậy việc sử dụng các lớp học là hợp lý. Cá nhân, tôi muốn nói rằng vì bạn làm có một lớp, sau đó hàm thành viên là cách tốt nhất để đi: bạn đang thực hiện một thao tác cụ thể trên cá thể vector cụ thể đó, do đó, điều đó có ý nghĩa đối với hàm phương pháp trên Vector.

Nơi bạn có thể muốn làm cho nó trở thành một hàm độc lập (chúng tôi không thực sự sử dụng từ "thành viên" hoặc "không phải thành viên") là bạn cần làm cho nó hoạt động với nhiều lớp không nhất thiết phải kế thừa từ mỗi khác hoặc một cơ sở chung. Nhờ gõ vịt, thực tế khá phổ biến là làm điều này: chỉ định rằng hàm của bạn trông đợi một đối tượng với một tập hợp các thuộc tính hoặc phương thức cụ thể và làm một cái gì đó với chúng.

+3

Trong khi chúng ta ở đây, một lưu ý hơi liên quan - một sự khác biệt từ C++ là Python không có khái niệm 'public' hoặc' private'. Cú pháp '_underscore' là cách thông thường để cung cấp một gợi ý * rằng một cái gì đó là riêng tư, nhưng nó không * được thi hành *. –

2

Hãy xem ví dụ của riêng bạn - hàm không phải thành viên phải truy cập vào các thành viên dữ liệu của lớp Vector. Đây không phải là một chiến thắng cho đóng gói. Điều này đặc biệt vì nó thay đổi các thành viên dữ liệu của đối tượng được truyền vào. Trong trường hợp này, có thể tốt hơn là trả về một vectơ được chia tỷ lệ và để nguyên bản gốc không thay đổi.

Ngoài ra, bạn sẽ không nhận ra bất kỳ lợi ích nào của đa hình lớp bằng cách sử dụng hàm không phải thành viên. Ví dụ, trong trường hợp này, nó vẫn có thể chỉ đối phó với vectơ của hai thành phần. Sẽ tốt hơn nếu nó sử dụng khả năng nhân bản vectơ, hoặc sử dụng một phương thức để lặp qua các thành phần.

Nói tóm lại:

  1. chức năng thành viên sử dụng để vận hành trên các đối tượng của các lớp bạn kiểm soát;
  2. sử dụng các chức năng không phải thành viên để thực hiện các hoạt động thuần túy chung được triển khai theo phương pháp và toán tử đa hình.
  3. Có lẽ tốt hơn là giữ cho đối tượng đột biến theo phương pháp.
+0

Các thành viên được truy cập (thành phần x và y) phải được công khai để đối tượng hữu ích. Xem xét vectơ với kích thước khác nhau, bạn có một điểm về đa hình mặc dù. – delnan

+1

@ delnan Nó không phải là tôi xem xét các chức năng độc lập rất nhiều vi phạm đóng gói, thay vì nó rất rõ ràng là không cải thiện đóng gói trong bất kỳ cách nào. Tôi không chấp nhận sự thiếu tính hào phóng của nó - nó chỉ có thể đối phó với vectơ của hai yếu tố. Tôi cũng không chấp nhận thực tế là nó thay đổi dữ liệu của vectơ. – Marcin

+1

@Marcin - Tên của các đối tượng trong ví dụ này là không liên quan, và thực sự, trong phòng thủ của tôi, nó * được * dán nhãn là asinine. Câu hỏi đặt ra ý kiến ​​về các thành tích tương đối của các hàm thành viên/không phải thành viên và * không * cho các đề xuất về cách làm cho các ví dụ tùy ý trở nên chung chung hơn. – JimmidyJoo

4

Chức năng miễn phí cho phép bạn linh hoạt sử dụng tính năng nhập vịt cho tham số đầu tiên đó.

Chức năng thành viên mang đến cho bạn tính biểu cảm của việc kết hợp chức năng với lớp học.

Chọn phù hợp. Nói chung, các hàm được tạo bằng nhau, vì vậy tất cả chúng đều có cùng các giả định về giao diện của một lớp. Khi bạn xuất bản chức năng miễn phí scale, bạn đang quảng cáo hiệu quả là .dX.dY là một phần của giao diện công khai của Vector. Đó có lẽ không phải là điều bạn muốn. Bạn đang thực hiện việc này để đổi lấy khả năng sử dụng lại cùng một chức năng với các đối tượng khác có số .dX.dY. Điều đó có lẽ sẽ không có giá trị đối với bạn. Vì vậy, trong trường hợp này tôi chắc chắn sẽ thích chức năng thành viên.

Để có ví dụ điển hình về việc sử dụng chức năng miễn phí, chúng tôi không cần xem thêm thư viện chuẩn: sorted là một chức năng miễn phí chứ không phải chức năng thành viên list, vì khái niệm bạn nên tạo danh sách kết quả từ sắp xếp bất kỳ chuỗi lặp nào.

3

thích chức năng phi bạn không thành viên vào các chức năng thành viên

Đây là một triết lý thiết kế và có thể và nên được mở rộng cho tất cả các ngôn ngữ lập trình OOP Paradigm. Nếu bạn hiểu bản chất của khái niệm này, khái niệm rõ ràng là

Nếu bạn có thể làm mà không yêu cầu quyền truy cập riêng tư/được bảo vệ cho các thành viên của Nhóm, thiết kế của bạn không có lý do để bao gồm hàm, thành viên của Nhóm . Để suy nghĩ điều này theo cách khác, khi thiết kế một Class, sau khi bạn đã liệt kê tất cả các thuộc tính, bạn cần phải xác định tập hợp tối thiểu các hành vi đủ để tạo Lớp. Bất kỳ chức năng thành viên nào mà bạn có thể viết bằng bất kỳ phương thức công cộng/chức năng thành viên nào có sẵn đều phải được công khai.

Bao nhiêu là này áp dụng trong Python

Để một mức độ nào nếu bạn đang cẩn thận. Python hỗ trợ đóng gói yếu hơn so với các ngôn ngữ OOP khác (như Java/C++) đáng chú ý vì không có thành viên riêng. (Có một cái gì đó gọi là biến tư nhân mà một lập trình viên có thể dễ dàng viết bằng tiền tố một '_' trước tên biến. Điều này trở thành lớp riêng thông qua một tính năng mangling tên.). Vì vậy, nếu chúng ta theo nghĩa đen chấp nhận từ của Scott Meyer hoàn toàn xem xét có một mỏng như thế nào giữa những gì cần được truy cập từ Class và những gì nên được từ bên ngoài. Nó nên được tốt nhất còn lại để các nhà thiết kế/lập trình để quyết định xem một chức năng nên là một phần không thể thiếu của Class hay không. Một nguyên tắc thiết kế chúng ta có thể dễ dàng áp dụng, "Unless your function required to access any of the properties of the class you can make it a non-member function".

1

Như scale dựa vào nhân viên khôn ngoan của một vector, tôi sẽ xem xét thực hiện phép nhân như một phương pháp và quy định scale là tổng quát hơn:

class Vector(object): 
    def __init__(self, dX, dY): 
     self._dX = dX 
     self._dY = dY 

    def __str__(self): 
     return "->(" + str(self._dX) + ", " + str(self._dY) + ")" 

    def __imul__(self, other): 
     if other is Vector: 
      self._dX *= other._dX 
      self._dY *= other._dY 
     else: 
      self._dX *= other 
      self._dY *= other 

     return self 

def scale(vector, scalar): 
    vector *= scalar 

Do đó, giao diện lớp học phong phú và hợp lý trong khi đóng gói được duy trì.

+0

Tôi nghĩ đây chắc chắn là một cách tiếp cận thú vị cho ví dụ cụ thể của tôi. – JimmidyJoo

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