Tại sao các phương thức 'riêng tư' của Python không thực sự riêng tư?


657

Python cung cấp cho chúng ta khả năng tạo các phương thức và biến 'riêng tư' trong một lớp bằng cách thêm hai dấu gạch dưới vào tên, như thế này : __myPrivateMethod(). Làm thế nào, sau đó, người ta có thể giải thích điều này

>>> class MyClass:
...     def myPublicMethod(self):
...             print 'public method'
...     def __myPrivateMethod(self):
...             print 'this is private!!'
... 
>>> obj = MyClass()
>>> obj.myPublicMethod()
public method
>>> obj.__myPrivateMethod()
Traceback (most recent call last):
  File "", line 1, in 
AttributeError: MyClass instance has no attribute '__myPrivateMethod'
>>> dir(obj)
['_MyClass__myPrivateMethod', '__doc__', '__module__', 'myPublicMethod']
>>> obj._MyClass__myPrivateMethod()
this is private!!

Thỏa thuận là gì?!

Tôi sẽ giải thích điều này một chút cho những người không hiểu điều đó.

>>> class MyClass:
...     def myPublicMethod(self):
...             print 'public method'
...     def __myPrivateMethod(self):
...             print 'this is private!!'
... 
>>> obj = MyClass()

Những gì tôi đã làm là tạo ra một lớp với một phương thức công khai và một phương thức riêng tư và khởi tạo nó.

Tiếp theo, tôi gọi phương thức công khai của nó.

>>> obj.myPublicMethod()
public method

Tiếp theo, tôi thử và gọi phương thức riêng tư của nó.

>>> obj.__myPrivateMethod()
Traceback (most recent call last):
  File "", line 1, in 
AttributeError: MyClass instance has no attribute '__myPrivateMethod'

Mọi thứ có vẻ tốt ở đây; chúng tôi không thể gọi nó. Trên thực tế, đó là 'riêng tư'. Chà, thực ra thì không. Chạy dir () trên đối tượng cho thấy một phương thức ma thuật mới mà python tạo ra một cách kỳ diệu cho tất cả các phương thức 'riêng tư' của bạn.

>>> dir(obj)
['_MyClass__myPrivateMethod', '__doc__', '__module__', 'myPublicMethod']

Tên của phương thức mới này luôn là một dấu gạch dưới, theo sau là tên lớp, theo sau là tên phương thức.

>>> obj._MyClass__myPrivateMethod()
this is private!!

Quá nhiều cho việc đóng gói, nhỉ?

Trong mọi trường hợp, tôi luôn nghe thấy Python không hỗ trợ đóng gói, vậy tại sao lại thử? Đưa cái gì?


18
Điều tương tự cũng đúng với Java hoặc C # nếu bạn sử dụng sự phản chiếu (đó là cách bạn đang làm ở đó).
0x434D53

4
Nó được xây dựng cho mục đích Kiểm thử đơn vị, vì vậy bạn có thể sử dụng "hack" đó để kiểm tra đơn vị các phương thức riêng tư của lớp bạn từ bên ngoài.
waas1919

16
Không phải thử nghiệm các phương thức riêng tư là một mô hình chống sao? Các phương thức riêng tư sẽ được sử dụng trong một số phương thức công khai để chắc chắn rằng nó chỉ không được sử dụng mãi mãi. Và cách đúng đắn để kiểm tra các phương thức riêng tư (dựa trên việc tôi học từ Th thinkWorks) là bạn viết các bài kiểm tra cho các phương thức công khai chỉ bao gồm tất cả các trường hợp. Nếu nó hoạt động tốt, bạn không cần phải thử nghiệm các phương pháp riêng tư từ bên ngoài.
Vishnu Narang

3
@VishnuNarang: Vâng, đó là những gì thường được dạy. Nhưng như mọi khi, một cách tiếp cận gần như "tôn giáo" của " luôn luôn làm điều này, không bao giờ làm điều đó" là điều duy nhất "không bao giờ" là tốt. Nếu kiểm tra đơn vị là "chỉ" được sử dụng cho kiểm tra hồi quy hoặc kiểm tra API công khai, bạn không cần kiểm tra tư nhân. Nhưng nếu bạn thực hiện phát triển theo hướng kiểm tra đơn vị, có nhiều lý do chính đáng để kiểm tra các phương thức riêng tư trong quá trình phát triển (ví dụ: khi khó có thể chế nhạo một số tham số bất thường / cực đoan thông qua giao diện chung). Một số môi trường kiểm tra ngôn ngữ / đơn vị không cho phép bạn làm điều này, điều mà IMHO không tốt.
Marco Freudenberger

5
@MarcoFreudenberger Tôi thấy quan điểm của bạn. Tôi có kinh nghiệm trong việc phát triển thử nghiệm đơn vị. Thông thường, khi việc giả định các tham số trở nên khó khăn, thường thì nó được giải quyết bằng cách thay đổi và cải tiến thiết kế. Tôi vẫn chưa bắt gặp một kịch bản trong đó thiết kế hoàn hảo và việc kiểm tra đơn vị vẫn cực kỳ khó khăn để tránh thử nghiệm các phương pháp riêng tư. Tôi sẽ xem xét các trường hợp như vậy. Cảm ơn. Tôi đánh giá cao nếu bạn có thể chia sẻ một kịch bản khỏi đỉnh đầu để giúp tôi hiểu.
Vishnu Narang

Câu trả lời:


592

Việc xáo trộn tên được sử dụng để đảm bảo rằng các lớp con không vô tình ghi đè lên các phương thức và thuộc tính riêng của các siêu lớp của chúng. Nó không được thiết kế để ngăn chặn sự truy cập có chủ ý từ bên ngoài.

Ví dụ:

>>> class Foo(object):
...     def __init__(self):
...         self.__baz = 42
...     def foo(self):
...         print self.__baz
...     
>>> class Bar(Foo):
...     def __init__(self):
...         super(Bar, self).__init__()
...         self.__baz = 21
...     def bar(self):
...         print self.__baz
...
>>> x = Bar()
>>> x.foo()
42
>>> x.bar()
21
>>> print x.__dict__
{'_Bar__baz': 21, '_Foo__baz': 42}

Tất nhiên, nó bị hỏng nếu hai lớp khác nhau có cùng tên.


12
docs.python.org/2/tutorial/groupes.html . Mục: 9.6 về các biến riêng và tham chiếu lớp-cục bộ.
gjain

72
Đối với những người trong chúng ta quá lười để cuộn / tìm kiếm: Phần 9.6 liên kết trực tiếp
cod3monk3y

3
Bạn nên đặt một dấu gạch dưới duy nhất để xác định rằng biến nên được coi là riêng tư. Một lần nữa, điều này không ngăn cản ai đó thực sự truy cập vào đó.
igon

14
Guido đã trả lời câu hỏi này - "lý do chính để tạo ra (gần như) mọi thứ có thể khám phá được là gỡ lỗi: khi gỡ lỗi bạn thường cần phải vượt qua sự trừu tượng" - Tôi đã thêm nó dưới dạng nhận xét vì quá muộn - quá nhiều câu trả lời.
Peter M. - là viết tắt của Monica

1
Nếu bạn tuân theo tiêu chí "ngăn chặn truy cập có chủ ý", hầu hết các ngôn ngữ OOP không hỗ trợ các thành viên thực sự riêng tư. Ví dụ: trong C ++, bạn có quyền truy cập thô vào bộ nhớ và trong mã tin cậy C # có thể sử dụng phản xạ riêng tư.
CodeInChaos

207

Ví dụ về hàm riêng

import re
import inspect

class MyClass :

    def __init__(self) :
        pass

    def private_function ( self ) :
        try :
            function_call = inspect.stack()[1][4][0].strip()

            # See if the function_call has "self." in the begining
            matched = re.match( '^self\.', function_call )
            if not matched :
                print 'This is Private Function, Go Away'
                return
        except :
            print 'This is Private Function, Go Away'
            return

        # This is the real Function, only accessible inside class #
        print 'Hey, Welcome in to function'

    def public_function ( self ) :
        # i can call private function from inside the class
        self.private_function()

### End ###

12
self = MyClass() self.private_function(). : D Tất nhiên là nó không hoạt động trong các lớp, nhưng bạn chỉ cần xác định một chức năng tùy chỉnh: def foo(self): self.private_function()
Casey Kuball

161
Chỉ trong trường hợp không rõ ràng: không bao giờ làm điều này bằng mã thực;)
Sudo Bash

10
@ThorSummoner Hoặc chỉ function_call.startswith('self.').
nyuszika7h

13
inspect.stack()[1][4][0].strip()<- các số ma thuật 1, 4 và 0 đó là gì?
akhy

5
Điều này có thể bị đánh bại khá dễ dàng bằng cách thực hiện self = MyClass(); self.private_function()và nó thất bại khi được gọi bằng cách sử dụng x = self.private_function()bên trong một phương thức.
Sẽ

171

Khi tôi lần đầu tiên chuyển từ Java sang Python, tôi ghét điều này. Nó làm tôi sợ đến chết.

Hôm nay nó có thể là điều tôi thích nhất ở Python.

Tôi thích được ở trên một nền tảng, nơi mọi người tin tưởng lẫn nhau và không cảm thấy như họ cần xây dựng những bức tường không thể xuyên thủng xung quanh mã của họ. Trong các ngôn ngữ được đóng gói mạnh mẽ, nếu API có lỗi và bạn đã tìm ra lỗi sai, bạn vẫn có thể không thể làm việc xung quanh nó vì phương thức cần thiết là riêng tư. Trong Python thái độ là: "chắc chắn". Nếu bạn nghĩ rằng bạn hiểu tình hình, có lẽ bạn thậm chí đã đọc nó, thì tất cả những gì chúng ta có thể nói là "chúc may mắn!".

Hãy nhớ rằng, đóng gói thậm chí không liên quan yếu đến "an ninh", hoặc giữ trẻ em ngoài bãi cỏ. Nó chỉ là một mẫu khác nên được sử dụng để làm cho một cơ sở mã dễ hiểu hơn.


36
@CamJackson Javascript là ví dụ của bạn ?? Ngôn ngữ duy nhất được sử dụng rộng rãi có sự kế thừa dựa trên nguyên mẫu và ngôn ngữ ủng hộ lập trình chức năng? Tôi nghĩ rằng JS khó học hơn rất nhiều so với hầu hết các ngôn ngữ khác, vì phải thực hiện một số bước trực giao từ OOP truyền thống. Không phải điều này ngăn cản những kẻ ngốc viết JS, họ chỉ không biết điều đó;)
K.Steff

23
API thực sự là một ví dụ thực sự tốt về lý do tại sao đóng gói lại quan trọng và khi nào các phương thức riêng tư sẽ được ưa thích. Một phương thức dự định là riêng tư có thể biến mất, thay đổi chữ ký hoặc tệ nhất trong tất cả các hành vi thay đổi - tất cả mà không có cảnh báo - trên bất kỳ phiên bản mới tiếp theo nào. Thành viên nhóm trưởng thành, thông minh của bạn có thực sự nhớ rằng cô ấy đã truy cập một phương pháp dự định riêng tư một năm kể từ bây giờ khi bạn cập nhật không? Cô ấy thậm chí sẽ làm việc ở đó nữa?
vô tư

2
Tôi không đồng ý với lập luận. Trong mã sản xuất, rất có thể tôi sẽ không bao giờ sử dụng API có lỗi khiến tôi thay đổi thành viên công cộng để làm cho nó "hoạt động". Một API nên hoạt động. Nếu không, tôi sẽ tự báo cáo lỗi hoặc tự tạo API tương tự. Tôi không thích triết lý này và tôi không thích Python lắm, mặc dù cú pháp của nó rất thú vị khi viết các tập lệnh nhỏ hơn trong ...
Yngve Sneen Lindal

4
Java có Method.setAccessible và Field.setAccessible. Còn đáng sợ?
Tony

15
Việc thực thi trong Java và C ++ không phải vì Java làm mất lòng người dùng trong khi Python thực hiện. Bởi vì trình biên dịch và / hoặc vm có thể đưa ra các giả định khác nhau khi xử lý cách thức hoạt động của doanh nghiệp nếu biết thông tin này, ví dụ C ++ có thể bỏ qua toàn bộ một lớp gián tiếp bằng cách sử dụng các cuộc gọi C cũ thông thường thay vì các cuộc gọi ảo, và đó vấn đề khi bạn làm việc trên hiệu suất cao hoặc công cụ chính xác cao. Python về bản chất không thể thực sự sử dụng tốt thông tin mà không ảnh hưởng đến tính năng động của nó. Cả hai ngôn ngữ đều nhắm đến những thứ khác nhau, vì vậy cả hai đều không "sai"
Shayne

144

Từ http://www.faqs.org/docs/diveintopython/fileinfo_private.html

Nói đúng ra, các phương thức riêng tư có thể truy cập bên ngoài lớp của chúng, chỉ là không dễ truy cập. Không có gì trong Python là thực sự riêng tư; Trong nội bộ, tên của các phương thức và thuộc tính riêng tư được xáo trộn và không bị xáo trộn khi đang bay khiến chúng dường như không thể truy cập được bằng tên đã cho của chúng. Bạn có thể truy cập phương thức __parse của lớp MP3FileInfo bằng tên _MP3FileInfo__parse. Thừa nhận rằng điều này là thú vị, sau đó hứa sẽ không bao giờ, thực hiện nó trong mã thực. Các phương thức riêng tư là riêng tư vì một lý do, nhưng giống như nhiều thứ khác trong Python, tính riêng tư của chúng cuối cùng là vấn đề quy ước, không phải là lực lượng.


201
hoặc như Guido van Rossum nói: "tất cả chúng ta đều là người trưởng thành."

35
-1: điều này là sai. Double undererscores không bao giờ có nghĩa là được sử dụng như là riêng tư ở nơi đầu tiên. Câu trả lời từ Alya dưới đây cho biết ý định thực sự của cú pháp xáo trộn tên. Các quy ước thực sự là một dấu gạch dưới duy nhất.
nosklo

2
Hãy thử chỉ với một dấu gạch dưới và bạn sẽ thấy kết quả bạn nhận được. @nosklo
Billal Begueradj

93

Cụm từ thường được sử dụng là "tất cả chúng ta đều đồng ý ở đây". Bằng cách chuẩn bị một dấu gạch dưới đơn (không phơi bày) hoặc nhân đôi dấu gạch dưới (ẩn), bạn đang nói với người dùng của lớp bạn rằng bạn dự định thành viên sẽ 'riêng tư' theo một cách nào đó. Tuy nhiên, bạn tin tưởng mọi người khác cư xử có trách nhiệm và tôn trọng điều đó, trừ khi họ có lý do thuyết phục không (ví dụ: trình gỡ lỗi, hoàn tất mã).

Nếu bạn thực sự phải có một cái gì đó là riêng tư, thì bạn có thể triển khai nó trong một phần mở rộng (ví dụ: trong C cho CPython). Tuy nhiên, trong hầu hết các trường hợp, bạn chỉ cần học cách làm Pythonic.


Vì vậy, có một số loại giao thức trình bao bọc mà tôi phải sử dụng để truy cập vào một biến được bảo vệ?
trực giác

3
Không có các biến "được bảo vệ" nào nhiều hơn các biến "riêng tư". Nếu bạn muốn truy cập một thuộc tính bắt đầu bằng dấu gạch dưới, bạn có thể thực hiện nó (nhưng lưu ý rằng tác giả không khuyến khích điều này). Nếu bạn phải truy cập vào một thuộc tính bắt đầu bằng dấu gạch dưới kép, bạn có thể tự thực hiện tên đó, nhưng bạn gần như chắc chắn không muốn làm điều này.
Tony Meyer

33

Không giống như bạn hoàn toàn không thể có được sự riêng tư của các thành viên trong bất kỳ ngôn ngữ nào (mỹ phẩm con trỏ trong C ++, Reflection in .NET / Java).

Vấn đề là bạn gặp lỗi nếu bạn cố gắng gọi phương thức riêng một cách tình cờ. Nhưng nếu bạn muốn tự bắn vào chân mình, hãy tiếp tục và làm điều đó.

Chỉnh sửa: Bạn không cố gắng bảo mật công cụ của mình bằng cách đóng gói OO, phải không?


2
Không có gì. Tôi chỉ đơn giản đưa ra quan điểm rằng thật kỳ quặc để giúp nhà phát triển dễ dàng, và theo ý kiến ​​của tôi đối với phép thuật, cách truy cập các thuộc tính 'riêng tư'.
trả lời

2
Vâng, tôi chỉ cố gắng để minh họa điểm. Làm cho nó riêng tư chỉ nói "bạn không nên truy cập trực tiếp vào điều này" bằng cách khiến trình biên dịch phàn nàn. Nhưng một người muốn thực sự thực sự làm điều đó anh ta có thể. Nhưng vâng, nó dễ dàng hơn trong Python so với hầu hết các ngôn ngữ khác.
Maximilian

7
Trong Java, bạn thực sự có thể bảo mật mọi thứ thông qua việc đóng gói, nhưng điều đó đòi hỏi bạn phải thông minh và chạy mã không tin cậy trong SecurityManager và rất cẩn thận. Ngay cả Oracle đôi khi cũng hiểu sai.
Antimon

12

Quy class.__stuffước đặt tên cho phép lập trình viên biết anh ta không có ý định truy cập __stufftừ bên ngoài. Cái tên xáo trộn khiến không ai có thể làm điều đó một cách tình cờ.

Đúng, bạn vẫn có thể giải quyết vấn đề này, nó thậm chí còn dễ hơn các ngôn ngữ khác (mà BTW cũng cho phép bạn làm điều này), nhưng không có lập trình viên Python nào làm điều này nếu anh ta quan tâm đến việc đóng gói.


12

Hành vi tương tự tồn tại khi tên thuộc tính mô-đun bắt đầu bằng một dấu gạch dưới (ví dụ _foo).

Các thuộc tính mô-đun có tên như vậy sẽ không được sao chép vào mô-đun nhập khi sử dụng from*phương thức, ví dụ:

from bar import *

Tuy nhiên, đây là một quy ước và không phải là một hạn chế ngôn ngữ. Đây không phải là thuộc tính riêng tư; chúng có thể được tham chiếu và thao tác bởi bất kỳ nhà nhập khẩu nào. Một số ý kiến ​​cho rằng vì điều này, Python không thể thực hiện đóng gói thực sự.


12

Đó chỉ là một trong những lựa chọn thiết kế ngôn ngữ. Ở một mức độ nào đó họ là hợp lý. Họ làm cho nó vì vậy bạn cần phải đi khá xa để thử và gọi phương thức, và nếu bạn thực sự cần nó quá tệ, bạn phải có một lý do khá chính đáng!

Móc gỡ lỗi và kiểm tra đến với các ứng dụng có thể, tất nhiên được sử dụng có trách nhiệm.


4

Với Python 3.4 đây là hành vi:

>>> class Foo:
        def __init__(self):
                pass
        def __privateMethod(self):
                return 3
        def invoke(self):
                return self.__privateMethod()


>>> help(Foo)
Help on class Foo in module __main__:

class Foo(builtins.object)
 |  Methods defined here:
 |
 |  __init__(self)
 |
 |  invoke(self)
 |
 |  ----------------------------------------------------------------------
 |  Data descriptors defined here:
 |
 |  __dict__
 |      dictionary for instance variables (if defined)
 |
 |  __weakref__
 |      list of weak references to the object (if defined)

 >>> f = Foo()
 >>> f.invoke()
 3
 >>> f.__privateMethod()
 Traceback (most recent call last):
   File "<pyshell#47>", line 1, in <module>
     f.__privateMethod()
 AttributeError: 'Foo' object has no attribute '__privateMethod'

https://docs.python.org/3/tutorial/groupes.html#tut-private

Lưu ý rằng các quy tắc xáo trộn được thiết kế chủ yếu để tránh tai nạn; vẫn có thể truy cập hoặc sửa đổi một biến được coi là riêng tư. Điều này thậm chí có thể hữu ích trong các trường hợp đặc biệt, chẳng hạn như trong trình gỡ lỗi.

Ngay cả khi câu hỏi đã cũ, tôi hy vọng đoạn trích của tôi có thể hữu ích.


2

Mối quan tâm quan trọng nhất về các phương thức và thuộc tính riêng là bảo các nhà phát triển không gọi nó bên ngoài lớp và đây là đóng gói. người ta có thể hiểu nhầm bảo mật từ đóng gói. khi một người cố tình sử dụng cú pháp như thế (dưới đây) mà bạn đã đề cập, bạn không muốn đóng gói.

obj._MyClass__myPrivateMethod()

Tôi đã di chuyển từ C # và lúc đầu, điều đó thật kỳ lạ đối với tôi nhưng sau một thời gian tôi đã nảy ra ý tưởng rằng chỉ có cách các nhà thiết kế mã Python nghĩ về OOP là khác.


1

Tại sao các phương thức 'riêng tư' của Python không thực sự riêng tư?

Theo tôi hiểu, họ không thể riêng tư. Làm thế nào riêng tư có thể được thực thi?

Câu trả lời rõ ràng là "các thành viên riêng tư chỉ có thể được truy cập thông qua self", nhưng điều đó sẽ không hoạt động - selfkhông phải là đặc biệt trong Python, nó không có gì khác hơn là một tên thường được sử dụng cho tham số đầu tiên của hàm.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.