Mengapa metode 'pribadi' Python tidak benar-benar pribadi?

658

Python memberikan kita kemampuan untuk membuat metode 'pribadi' dan variabel dalam kelas dengan mengawali garis bawah ganda untuk nama, seperti ini: __myPrivateMethod(). Bagaimana, kemudian, seseorang dapat menjelaskan hal ini

>>> 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!!

Apa masalahnya ?!

Saya akan menjelaskan ini sedikit untuk mereka yang tidak mengerti.

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

Apa yang saya lakukan di sana adalah membuat kelas dengan metode publik dan metode pribadi dan instantiate.

Selanjutnya, saya sebut metode publiknya.

>>> obj.myPublicMethod()
public method

Selanjutnya, saya mencoba dan memanggil metode privatnya.

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

Semuanya terlihat bagus di sini; kami tidak dapat menyebutnya. Ini sebenarnya adalah 'pribadi'. Sebenarnya tidak. Menjalankan dir () pada objek tersebut mengungkapkan metode magis baru yang python buat secara ajaib untuk semua metode 'pribadi' Anda.

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

Nama metode baru ini selalu garis bawah, diikuti oleh nama kelas, diikuti oleh nama metode.

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

Begitu banyak untuk enkapsulasi, eh?

Bagaimanapun, saya selalu mendengar Python tidak mendukung enkapsulasi, jadi mengapa bahkan mencoba? Apa yang menyebabkannya?

Willurd
sumber
18
Hal yang sama berlaku untuk Java atau C # jika Anda menggunakan refleksi (yang entah bagaimana Anda lakukan di sana).
0x434D53
4
Itu dibangun untuk tujuan Pengujian Unit, sehingga Anda dapat menggunakan "retas" untuk menguji unit metode pribadi kelas Anda dari luar.
waas1919
16
Bukankah menguji metode pribadi merupakan anti-pola? Metode pribadi akan digunakan dalam beberapa metode publik untuk memastikan yang lain itu tidak digunakan selamanya. Dan cara yang tepat untuk menguji metode pribadi (berdasarkan pembelajaran saya sejauh ini dari ThoughtWorks) adalah Anda menulis tes untuk metode publik saja yang mencakup semua kasus. Jika itu berfungsi dengan baik, Anda tidak perlu menguji metode pribadi dari luar sama sekali.
Wisnu Narang
3
@ WisnuNarang: Ya, itulah yang sering diajarkan. Tetapi Seperti biasa, pendekatan yang hampir "religius" tentang " selalu melakukan ini, tidak pernah melakukan itu" adalah satu-satunya hal yang "tidak pernah" baik. Jika tes unit "hanya" digunakan untuk tes regresi atau menguji API publik, Anda tidak perlu menguji privat. Tetapi jika Anda melakukan pengembangan unit test driven, ada alasan bagus untuk menguji metode privat selama pengembangan (misalnya ketika sulit untuk mengejek parameter tidak biasa / ekstrim tertentu melalui antarmuka publik). Beberapa bahasa / lingkungan unit tes tidak membiarkan Anda melakukan ini, yang IMHO tidak baik.
Marco Freudenberger
5
@MarcoFreudenberger Saya mengerti maksud Anda. Saya memang memiliki pengalaman dalam pengembangan unit test driven. Seringkali ketika menjadi sulit untuk mengejek parameter, paling sering diselesaikan dengan mengubah dan meningkatkan desain. Saya belum menemukan skenario di mana desainnya sempurna dan pengujian unit masih sangat sulit untuk menghindari pengujian metode pribadi. Saya akan mencari kasus-kasus seperti itu. Terima kasih. Saya menghargai jika Anda dapat membagikan satu skenario di atas kepala Anda untuk membantu saya memahami.
Wisnu Narang

Jawaban:

592

Perebutan nama digunakan untuk memastikan bahwa subclass tidak sengaja menimpa metode pribadi dan atribut superclasses mereka. Itu tidak dirancang untuk mencegah akses yang disengaja dari luar.

Sebagai contoh:

>>> 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}

Tentu saja, itu rusak jika dua kelas berbeda memiliki nama yang sama.

Alya
sumber
12
docs.python.org/2/tutorial/classes.html . Bagian: 9.6 tentang variabel pribadi dan referensi kelas-lokal.
gjain
72
Bagi kita yang terlalu malas untuk menggulir / mencari: Bagian 9.6 tautan langsung
cod3monk3y
3
Anda harus meletakkan satu garis bawah untuk menentukan bahwa variabel harus dianggap sebagai pribadi. Sekali lagi, ini tidak menghalangi seseorang untuk benar-benar mengaksesnya.
igon
14
Guido menjawab pertanyaan ini - "alasan utama untuk membuat (hampir) semua yang dapat ditemukan adalah debugging: ketika debugging Anda sering perlu menerobos abstraksi" - Saya menambahkannya sebagai komentar karena sudah terlambat - terlalu banyak jawaban.
Peter M. - singkatan dari Monica
1
Jika Anda menggunakan kriteria "cegah akses yang disengaja", sebagian besar bahasa OOP tidak mendukung anggota yang benar-benar pribadi. Misalnya di C ++ Anda memiliki akses mentah ke memori dan dalam kode tepercaya C # dapat menggunakan refleksi pribadi.
CodesInChaos
207

Contoh fungsi pribadi

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 ###
arun
sumber
12
self = MyClass() self.private_function(). : D Tentu saja itu tidak berfungsi di kelas, tetapi Anda hanya perlu mendefinisikan fungsi khusus: def foo(self): self.private_function()
Casey Kuball
161
Kalau-kalau tidak jelas: jangan pernah melakukan ini dalam kode nyata;)
Sudo Bash
10
@ThorSummoner Atau hanya function_call.startswith('self.').
nyuszika7h
13
inspect.stack()[1][4][0].strip()<- apa angka ajaib 1, 4, dan 0 itu?
akhy
5
Ini bisa dikalahkan dengan mudah dengan melakukan self = MyClass(); self.private_function()dan gagal ketika dipanggil menggunakan x = self.private_function()di dalam metode.
Will
171

Ketika saya pertama kali datang dari Jawa ke Python saya benci ini. Itu membuatku takut sampai mati.

Hari ini mungkin hanya satu hal yang paling saya sukai tentang Python.

Saya suka berada di platform, di mana orang saling mempercayai dan tidak merasa perlu membangun tembok yang tidak bisa ditembus di sekitar kode mereka. Dalam bahasa yang dienkapsulasi dengan kuat, jika API memiliki bug, dan Anda telah menemukan apa yang salah, Anda mungkin masih tidak dapat mengatasinya karena metode yang diperlukan bersifat pribadi. Dalam Python sikapnya adalah: "yakin". Jika Anda pikir Anda memahami situasinya, mungkin Anda bahkan sudah membacanya, maka yang bisa kita katakan adalah "semoga sukses!".

Ingat, enkapsulasi bahkan tidak berhubungan lemah dengan "keamanan", atau menjauhkan anak-anak dari halaman. Ini hanyalah pola lain yang harus digunakan untuk membuat basis kode lebih mudah dimengerti.

Thomas Ahle
sumber
36
@CamJackson Javascript adalah contoh Anda ?? Satu-satunya bahasa yang banyak digunakan yang memiliki warisan berbasis prototybe dan bahasa yang mendukung pemrograman fungsional? Saya pikir JS jauh lebih sulit untuk dipelajari daripada sebagian besar bahasa lain, karena dibutuhkan beberapa langkah ortogonal dari OOP tradisional. Bukannya ini mencegah idiot dari menulis JS, mereka hanya tidak tahu;)
K.Steff
23
API sebenarnya adalah contoh yang sangat bagus mengapa enkapsulasi penting dan kapan metode pribadi lebih disukai. Metode yang dimaksudkan untuk bersifat pribadi dapat hilang, mengubah tanda tangan, atau yang terburuk dari semua perilaku perubahan - semua tanpa peringatan - pada versi baru berikutnya. Akankah anggota tim Anda yang cerdas dan dewasa benar-benar ingat bahwa ia mengakses metode yang dimaksudkan untuk pribadi satu tahun dari sekarang ketika Anda memperbarui? Apakah dia akan bekerja di sana lagi?
einnocent
2
Saya tidak setuju dengan argumen itu. Dalam kode produksi, saya kemungkinan besar tidak akan pernah menggunakan API yang memiliki bug yang membuat saya mengubah anggota publik untuk membuatnya "berfungsi". API harus bekerja. Jika tidak, saya akan mengajukan laporan bug atau membuat API yang sama sendiri. Saya tidak suka filosofi dan saya tidak terlalu menyukai Python, meskipun sintaksnya membuatnya menyenangkan untuk menulis skrip yang lebih kecil di ...
Yngve Sneen Lindal
4
Java memiliki Method.setAccessible dan Field.setAccessible. Juga menakutkan?
Tony
15
Penegakan di Java dan C ++ bukan karena Java tidak mempercayai pengguna sementara Python melakukannya. Ini karena kompiler dan / atau vm dapat membuat berbagai asumsi ketika berhadapan dengan bagaimana bisnisnya jika mengetahui informasi ini, misalnya C ++ dapat melewati seluruh lapisan tipuan dengan menggunakan panggilan C lama biasa alih-alih panggilan virtual, dan itu penting ketika Anda bekerja pada kinerja tinggi atau hal-hal presisi tinggi. Python pada dasarnya tidak dapat memanfaatkan informasi dengan baik tanpa mengorbankan dinamismenya. Kedua bahasa itu bertujuan untuk hal-hal yang berbeda, jadi tidak ada yang "salah"
Shayne
144

Dari http://www.faqs.org/docs/diveintopython/fileinfo_private.html

Sebenarnya, metode pribadi dapat diakses di luar kelas mereka, hanya saja tidak mudah diakses. Tidak ada dalam Python yang benar-benar pribadi; secara internal, nama-nama metode dan atribut pribadi yang hancur dan tidak berubah dengan cepat untuk membuat mereka tampak tidak dapat diakses oleh nama yang diberikan. Anda dapat mengakses metode __parse dari kelas MP3FileInfo dengan nama _MP3FileInfo__parse. Mengakui bahwa ini menarik, lalu berjanji untuk tidak pernah melakukannya dalam kode nyata. Metode pribadi bersifat pribadi karena suatu alasan, tetapi seperti banyak hal lain di Python, privasi mereka pada akhirnya adalah masalah konvensi, bukan paksaan.

xsl
sumber
202
atau seperti yang dikatakan Guido van Rossum: "kita semua adalah orang dewasa."
35
-1: ini salah. Menggarisbawahi ganda tidak pernah dimaksudkan untuk digunakan sebagai pribadi di tempat pertama. Jawaban dari Alya di bawah ini menunjukkan maksud sebenarnya dari sintaksis nama mangling. Konvensi yang sebenarnya adalah garis bawah tunggal.
nosklo
2
Coba dengan satu garis bawah saja dan Anda akan melihat hasil yang Anda dapatkan. @nosklo
Billal Begueradj
93

Ungkapan yang umum digunakan adalah "kita semua menyetujui orang dewasa di sini". Dengan menambahkan satu garis bawah (jangan tampilkan) atau garis bawah dua (sembunyikan), Anda memberi tahu pengguna kelas Anda bahwa Anda bermaksud anggota untuk 'pribadi' dengan cara tertentu. Namun, Anda memercayai orang lain untuk berperilaku secara bertanggung jawab dan menghargai itu, kecuali mereka memiliki alasan kuat untuk tidak melakukannya (mis. Debugger, penyelesaian kode).

Jika Anda benar-benar harus memiliki sesuatu yang bersifat pribadi, maka Anda dapat mengimplementasikannya dalam ekstensi (misalnya dalam C untuk CPython). Namun, dalam kebanyakan kasus, Anda hanya mempelajari cara Pythonic dalam melakukan sesuatu.

Tony Meyer
sumber
jadi apakah ada semacam protokol pembungkus yang seharusnya saya gunakan untuk mengakses variabel yang dilindungi?
intuited
3
Tidak ada variabel "terlindungi" selain dari "pribadi". Jika Anda ingin mengakses atribut yang dimulai dengan garis bawah, Anda bisa melakukannya (tetapi perhatikan bahwa penulis tidak menyarankan ini). Jika Anda harus mengakses atribut yang dimulai dengan garis bawah ganda, Anda dapat melakukan sendiri nama mangling, tetapi Anda hampir pasti tidak ingin melakukan ini.
Tony Meyer
33

Ini tidak seperti Anda absolutly dan tidak dapat menyiasati privasi anggota dalam bahasa apa pun (aritmatika pointer di C ++, Refleksi dalam .NET / Java).

Intinya adalah Anda mendapatkan kesalahan jika Anda mencoba memanggil metode pribadi secara tidak sengaja. Tetapi jika Anda ingin menembak diri sendiri, lanjutkan dan lakukan.

Sunting: Anda tidak mencoba mengamankan barang-barang Anda dengan OO-enkapsulasi, bukan?

Maximilian
sumber
2
Tidak semuanya. Saya hanya menyatakan bahwa aneh untuk memberi pengembang cara yang mudah, dan menurut saya cara magis, cara mengakses properti 'pribadi'.
willurd
2
Ya, saya hanya mencoba mengilustrasikan intinya. Menjadikannya pribadi hanya mengatakan "Anda tidak boleh mengakses ini secara langsung" dengan membuat kompiler mengeluh. Tetapi orang ingin benar-benar melakukannya, dia bisa. Tapi ya, itu lebih mudah di Python daripada di kebanyakan bahasa lain.
Maximilian
7
Di Jawa, Anda sebenarnya dapat mengamankan barang melalui enkapsulasi, tetapi itu mengharuskan Anda untuk menjadi pintar dan menjalankan kode yang tidak tepercaya di SecurityManager, dan sangat berhati-hati. Bahkan Oracle terkadang salah.
Antimony
12

The class.__stuffkonvensi penamaan memungkinkan programmer tahu dia tidak dimaksudkan untuk akses __stuffdari luar. Nama mangling membuatnya tidak mungkin ada orang yang melakukannya secara tidak sengaja.

Benar, Anda masih bisa mengatasi ini, bahkan lebih mudah daripada dalam bahasa lain (yang BTW juga membiarkan Anda melakukan ini), tetapi tidak ada programmer Python yang akan melakukan ini jika dia peduli tentang enkapsulasi.

Nickolay
sumber
12

Perilaku serupa ada ketika nama atribut modul dimulai dengan garis bawah tunggal (misalnya _foo).

Atribut modul yang dinamai demikian tidak akan disalin ke modul impor saat menggunakan from*metode ini, misalnya:

from bar import *

Namun, ini adalah konvensi dan bukan kendala bahasa. Ini bukan atribut pribadi; mereka dapat dirujuk dan dimanipulasi oleh importir mana pun. Beberapa berpendapat bahwa karena ini, Python tidak dapat mengimplementasikan enkapsulasi yang benar.

Ross
sumber
12

Itu hanya salah satu dari pilihan desain bahasa itu. Pada tingkat tertentu mereka dibenarkan. Mereka membuatnya sehingga Anda harus pergi jauh-jauh dari cara Anda untuk mencoba dan memanggil metode, dan jika Anda benar-benar membutuhkannya, Anda harus memiliki alasan yang cukup bagus!

Kait debugging dan pengujian muncul dalam pikiran sebagai aplikasi yang mungkin, tentu saja digunakan secara bertanggung jawab.

ctcherry
sumber
4

Dengan Python 3.4 ini adalah perilaku:

>>> 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/classes.html#tut-private

Perhatikan bahwa aturan mangling sebagian besar dirancang untuk menghindari kecelakaan; masih dimungkinkan untuk mengakses atau memodifikasi variabel yang dianggap pribadi. Ini bahkan dapat berguna dalam keadaan khusus, seperti di debugger.

Meskipun pertanyaannya sudah tua, saya harap cuplikan saya dapat membantu.

Alberto
sumber
2

Kekhawatiran yang paling penting tentang metode dan atribut pribadi adalah memberi tahu pengembang untuk tidak menyebutnya di luar kelas dan ini enkapsulasi. orang mungkin salah memahami keamanan dari enkapsulasi. ketika seseorang dengan sengaja menggunakan sintaks seperti itu (di bawah) yang Anda sebutkan, Anda tidak ingin enkapsulasi.

obj._MyClass__myPrivateMethod()

Saya telah bermigrasi dari C # dan pada awalnya itu aneh bagi saya juga, tetapi setelah beberapa saat saya sampai pada gagasan bahwa hanya cara para perancang kode Python berpikir tentang OOP berbeda.

Afshin Amiri
sumber
1

Mengapa metode 'pribadi' Python tidak benar-benar pribadi?

Seperti yang saya pahami, mereka tidak bisa bersifat pribadi. Bagaimana privasi bisa ditegakkan?

Jawaban yang jelas adalah "anggota pribadi hanya dapat diakses melalui self", tetapi itu tidak akan berhasil - selftidak istimewa dalam Python, itu tidak lebih dari nama yang biasa digunakan untuk parameter pertama dari suatu fungsi.

pengguna200783
sumber