背景需求
在物件導向的設計中,典型如Java語言,為了控制物件屬性的修改入口,我們常用的做法是把屬性設定為private,然後通過getter和setter方法訪問、修改該屬性。
但是在Python語言中,並沒有Java的訪問控制符,物件的屬性可以直接訪問、修改。
為了良好的設計規範,我們可以規定,在Python類中,所有的物件屬性均以下劃線"_"字首開頭,同時編寫該屬性的getter和setter方法,在其他地方引用的時候,禁止出現直接引用。
在IDEA等IDE中,可以對Java的物件屬性直接生成getter和setter方法,但是針對Python沒有這樣的功能。大量的getter和setter方法,很耗費精力,所以需要一款外掛來輔助自動化生成Python物件屬性的getter和setter方法。
搭建環境
編寫IDEA系列的外掛開發環境,可以看我之前的一篇文章:《IntelliJ IDEA/Android Studio外掛開發指南》
官方開發文件:IntelliJ Platform SDK
過程拆解
Python檔案例子:
class Test(object):
def __init__(self):
self._var1 = ""
self._var2 = 0
明確了需求、輸入(python物件屬性定義程式碼)、輸出(PyCharm外掛自動生成getter和setter)後,我們針對這個外掛的流程進行拆解:
- 首先,使用者選中了對應行的文字內容,外掛獲取到該內容文字
- 在內容文字中過濾出變數,在本例中,就是過濾出
_var1
,_var2
- 拼裝變數的getter和setter方法
- 計算出要插入的位置
- 回寫到編輯器中
1. 獲取文字
在PyCharm外掛中,Editor物件是編輯器的總覽,其中包含很多Model,比如
CaretModel caretModel=editor.getCaretModel(); // 用於描述插入游標
SelectionModel selectionModel = editor.getSelectionModel(); // 用於描述選中的文字
FoldingModel foldingModel = editor.getFoldingModel(); // 用於描述程式碼摺疊區域
IndentsModel indentModel = editor.getIndentsModel(); // 用於描述縮排
……
在這裡,我們只需要SelectionModel。
// 獲取游標選中文字段物件
SelectionModel selectionModel = editor.getSelectionModel();
// 拿到選中部分字串
String selectedText = selectionModel.getSelectedText();
2. 正則匹配
拿到選中文字後,有可能選擇了多行,裡面包含多個變數,所以我們需要獲取到變數列表。
觀察到所有的變數都是self.abc=xxx
的模式,我們可以考慮用正則匹配把其中的abc
獲取到。
Java中負責正則匹配並獲取匹配字串的類是Pattern
和Matcher
。
/**
* 獲取選中文字中所有的self.value中的value <br>
* e.g. self.value = xxx,or self._value = xxx, <br>
* 可以獲取到其中的value
*
* @param selectedText 選中文字
* @return 變數字串列表
*/
public ArrayList<String> getFieldList(String selectedText) {
ArrayList<String> list = new ArrayList<>();
// 刪除所有空格
selectedText = selectedText.replaceAll(" ", "");
// 正則匹配獲得變數字串
String reg = "self.(.*?)=";
Pattern pattern = Pattern.compile(reg);
Matcher matcher = pattern.matcher(selectedText);
while (matcher.find()) {
list.add(matcher.group(1));
}
return list;
}
3. 拼裝方法
Python中的getter和setter方法都非常簡單,我們可以先創造一個模板:
// 定義Getter和Setter的模板
String getterTemplate = " def get_word(self):\n return self.field\n ";
String setterTemplate = " def set_word(self, word):\n self.field = word\n ";
之所以存在空格,是為了匹配PyCharm的縮排,我這裡使用的4個空格做縮排,如果你使用兩個空格的話,在這裡修改成兩個空格即可。
在這裡不能使用\t
,我嘗試了\t
,在PyCharm中無法自動轉換為4個空格,會報錯。
上一步獲取到的變數,有可能不存在下換線字首,也有可能存在1個或者2個下劃線字首,比如var
,_var
,__var
,他們對應的gett和setter如下:
# 假如變數為_var
def get_var(self):
return self._var;
def set_var(self, var):
self._var = var;
可以看到在self.xxx
中需要使用變數,而在get_xxx
和setter的引數中,需要刪除對應的下劃線。所以有:
……
// 對於 “_value” 型別的變數,在set方法引數中,只需要“value”
for (String field : fieldList) {
String tmp = field;
int i = 0;
while (tmp.charAt(i) == '_') {
tmp = tmp.substring(1);
}
// 替換掉模板中的變數
String customGetter = getterTemplate.replaceAll("word", tmp).replaceAll("field", field);
String customSetter = setterTemplate.replaceAll("word", tmp).replaceAll("field", field);
stringBuilder.append("\n").append(customGetter).append("\n").append(customSetter);
}
……
4. 計算位置
首先需要獲取到Document物件,這是負責描述文件的,裡面有很多負責文件的方法,比如在檔案中插入字串,計算檔案行數,計算文件長度,刪除相應內容等等。
Document document = editor.getDocument();
為了方便簡單,我們設定在選中文字的下一行生成getter和setter。
// 得到選中字串的結束位置
int endOffset = selectionModel.getSelectionEnd();
// 得到最大插入字串(生成的Getter和Setter函式字串)的位置
int maxOffset = document.getTextLength();
// 計算選中字串所在的行號,通過行號得到下一行的第一個字元的起始偏移量
int curLineNumber = document.getLineNumber(endOffset);
int docLineCount = document.getLineCount();
// 如果目前檔案行數不足以支援選中文字的下一行,也就是選中文字包含最後一行,就插入一個空行
if (docLineCount - 1 < curLineNumber + 1) {
Runnable runnable = () -> document.insertString(maxOffset,"\n");
WriteCommandAction.runWriteCommandAction(project, runnable);
}
int nextLineStartOffset = document.getLineStartOffset(curLineNumber + 1);
5. 回寫
將字串插入文件中,不能直接使用document.insertString
,會error: Assertion failed: Write access is allowed inside write-action only (see com.intellij.openapi.application.Application.runWriteAction())
需要把這個任務放入一個Runnable中,然後由WriteCommandAction
來排程。
參考:Write access is allowed inside write-action only
// 對文件進行操作部分程式碼,需要放入runnable,不然IDEA會卡住
Runnable runnable = () -> document.insertString(nextLineStartOffset, genGetterAndGetter(fieldList));
// 加入任務,由IDE排程任務
WriteCommandAction.runWriteCommandAction(project, runnable);
效果
目前來看效果還不錯,關於安裝方法、使用方法,見github的README。