在Class內部,可以有屬性和方法,而外部代碼可以通過直接調用實例變量的方法來操作數據,這樣,就隱藏了內部的復雜邏輯。
但是,從前面Student類的定義來看,外部代碼還是可以自由地修改一個實例的`name`、`score`屬性:
~~~
>>> bart = Student('Bart Simpson', 98)
>>> bart.score
98
>>> bart.score = 59
>>> bart.score
59
~~~
如果要讓內部屬性不被外部訪問,可以把屬性的名稱前加上兩個下劃線`__`,在Python中,實例的變量名如果以`__`開頭,就變成了一個私有變量(private),只有內部可以訪問,外部不能訪問,所以,我們把Student類改一改:
~~~
class Student(object):
def __init__(self, name, score):
self.__name = name
self.__score = score
def print_score(self):
print('%s: %s' % (self.__name, self.__score))
~~~
改完后,對于外部代碼來說,沒什么變動,但是已經無法從外部訪問`實例變量.__name`和`實例變量.__score`了:
~~~
>>> bart = Student('Bart Simpson', 98)
>>> bart.__name
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'Student' object has no attribute '__name'
~~~
這樣就確保了外部代碼不能隨意修改對象內部的狀態,這樣通過訪問限制的保護,代碼更加健壯。
但是如果外部代碼要獲取name和score怎么辦?可以給Student類增加`get_name`和`get_score`這樣的方法:
~~~
class Student(object):
...
def get_name(self):
return self.__name
def get_score(self):
return self.__score
~~~
如果又要允許外部代碼修改score怎么辦?可以再給Student類增加`set_score`方法:
~~~
class Student(object):
...
def set_score(self, score):
self.__score = score
~~~
你也許會問,原先那種直接通過`bart.score = 59`也可以修改啊,為什么要定義一個方法大費周折?因為在方法中,可以對參數做檢查,避免傳入無效的參數:
~~~
class Student(object):
...
def set_score(self, score):
if 0 <= score <= 100:
self.__score = score
else:
raise ValueError('bad score')
~~~
需要注意的是,在Python中,變量名類似`__xxx__`的,也就是以雙下劃線開頭,并且以雙下劃線結尾的,是特殊變量,特殊變量是可以直接訪問的,不是private變量,所以,不能用`__name__`、`__score__`這樣的變量名。
有些時候,你會看到以一個下劃線開頭的實例變量名,比如`_name`,這樣的實例變量外部是可以訪問的,但是,按照約定俗成的規定,當你看到這樣的變量時,意思就是,“雖然我可以被訪問,但是,請把我視為私有變量,不要隨意訪問”。
雙下劃線開頭的實例變量是不是一定不能從外部訪問呢?其實也不是。不能直接訪問`__name`是因為Python解釋器對外把`__name`變量改成了`_Student__name`,所以,仍然可以通過`_Student__name`來訪問`__name`變量:
~~~
>>> bart._Student__name
'Bart Simpson'
~~~
但是強烈建議你不要這么干,因為不同版本的Python解釋器可能會把`__name`改成不同的變量名。
總的來說就是,Python本身沒有任何機制阻止你干壞事,一切全靠自覺。
### 參考源碼
[protected_student.py](https://github.com/michaelliao/learn-python3/blob/master/samples/oop_basic/protected_student.py)
- 關于
- Python簡介
- 安裝Python
- Python解釋器
- 第一個Python程序
- 使用文本編輯器
- Python代碼運行助手
- 輸入和輸出
- Python基礎
- 數據類型和變量
- 字符串和編碼
- 使用list和tuple
- 條件判斷
- 循環
- 使用dict和set
- 函數
- 調用函數
- 定義函數
- 函數的參數
- 遞歸函數
- 高級特性
- 切片
- 迭代
- 列表生成式
- 生成器
- 迭代器
- 函數式編程
- 高階函數
- 返回函數
- 匿名函數
- 裝飾器
- 偏函數
- 模塊
- 使用模塊
- 安裝第三方模塊
- 面向對象編程
- 類和實例
- 訪問限制
- 繼承和多態
- 獲取對象信息
- 實例屬性和類屬性
- 面向對象高級編程
- 使用slots
- 使用@property
- 多重繼承
- 定制類
- 使用枚舉類
- 使用元類
- 錯誤、調試和測試
- 錯誤處理
- 調試
- 單元測試
- 文檔測試
- IO編程
- 文件讀寫
- StringIO和BytesIO
- 操作文件和目錄
- 序列化
- 進程和線程
- 多進程
- 多線程
- ThreadLocal
- 進程 vs. 線程
- 分布式進程
- 正則表達式
- 常用內建模塊
- datetime
- collections
- base64
- struct
- hashlib
- itertools
- XML
- HTMLParser
- urllib
- 常用第三方模塊
- PIL
- virtualenv
- 圖形界面
- 網絡編程
- TCP/IP簡介
- TCP編程
- UDP編程
- 電子郵件
- SMTP發送郵件
- POP3收取郵件
- 訪問數據庫
- 使用SQLite
- 使用MySQL
- 使用SQLAlchemy
- Web開發
- HTTP協議簡介
- HTML簡介
- WSGI接口
- 使用Web框架
- 使用模板
- 異步IO
- 協程
- asyncio
- aiohttp
- 實戰
- Day 1 - 搭建開發環境
- Day 2 - 編寫Web App骨架
- Day 3 - 編寫ORM
- Day 4 - 編寫Model
- Day 5 - 編寫Web框架
- Day 6 - 編寫配置文件
- Day 7 - 編寫MVC
- Day 8 - 構建前端
- Day 9 - 編寫API
- Day 10 - 用戶注冊和登錄
- Day 11 - 編寫日志創建頁
- Day 12 - 編寫日志列表頁
- Day 13 - 提升開發效率
- Day 14 - 完成Web App
- Day 15 - 部署Web App
- Day 16 - 編寫移動App
- FAQ
- 期末總結