exec()
是Python的built-in函數。其作用很好描述,就是執行以string類型存儲的Python代碼。話不多說舉個例子。
>>> i = 2
>>> j = 3
>>> exec("ans = i + j")
>>> print("Answer is: ", ans)
Answer is: 5
>>>
在上個例子里面,ans變量並沒有顯式的定義,但仍然可以在print函數中調用。這是exec語句執行了"ans = i + j"
中的代碼,定義了ans變量。
乍一看,這個功能很像C語言里的define宏定義:都是在代碼里面插入可變的代碼段。但其實還不一樣,再看一個例子。
>>> i = 5
>>> j = 7
>>> n = 0
>>> while n < i:
... print("looping")
... if j > i:
... break
... n += 1
...
looping
>>>
假設使用exec函數。
>>> i = 5
>>> j = 7
>>> n = 0
>>> while n < i:
... print("looping")
... exec("""if j > 5:
... \n break""")
... n += 1
...
looping
Traceback (most recent call last):
File "<stdin>", line 4, in <module>
File "<string>", line 3
SyntaxError: "break" outside loop
在這里,exec函數為什么失效了呢?
根據Python文檔,解釋器會在執行到break
語句時,會跳出離該句最近的while、for循環,如果解釋器無法找到while、for循環,就會報錯。因此,此處報錯,說明了Python解釋器沒有找到exec之前的while循環。
實際上,仔細看文檔會發現,解釋器遇到exec函數時,會獨立執行字符串內的語句。如果還有傳參,那都是定義變量的字典。解釋器,不會尋找字符串外的語法結構。也就是說,在這個例子中,解釋器會獨立執行語句
if j > i:
break
難怪,解釋器會報錯了。
而C語言完全不存在這個問題。
#include <stdio.h>
#define JUDGE(x, y) if ((x) > (y))\
break;
int main() {
int i = 5;
int j = 7;
int n = 0;
while (n < i) {
printf("looping\n");
JUDGE(j, i)
n++;
};
return 0;
}
編譯以后運行一下,看看。
$ gcc test.c -o test
$ ./test
looping
$
兩個表面看上去類似的功能背后的原理完全不同。C語言的define,會在編譯的第一步——“預處理”中完成替換。編譯器在后續語法分析時,完全不知道原始代碼里的宏定義是什么樣子。
多說兩句
exec可以幫助完成過程抽象
exec是一個比較偏門的函數,而且過多地使用這個函數會降低代碼的可讀性。
不過,它有助於在開發過程中循序漸進的完成“過程抽象”。
最近,工作中就遇到一個場景:解析不同語言的代碼文件。代碼文件大致一樣,卻又隨着語言語法的不同而在解析細節上有着不一樣。我在最初拿到這個任務時,對於如何抽象出類和對象完全沒有頭緒。就先對不同的代碼文件,單獨寫一個過程函數。全部寫完,單元測試跑過之后,再去對比:歸納出共有的方法,提取共同的結構作為父類的內容。用exec函數剝離代碼,分離出子類的私有方法。
C語言中實現面向對象
C語言是典型的面向過程語言,怎么做到面向對象呢?
這個腦洞有點大,但是大牛們已經在這么做了。之前看知乎上有人講:真正的C語言用家,都是用宏定義來實現類似於C++的面向對象特性。當初看了也是一頭霧水,這次才悟出來怎么實現。
隨便舉個例子吧。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MALE 0
#define FEMALE 1
#define INIT_PERSON(Person_var, Gender, Birthday)\
struct Person* Person_var;\
Person_var = malloc(sizeof(Person_var));\
Person_var->gender = (Gender);\
strcpy(Person_var->birthday, Birthday);
struct Person {
int gender;
char birthday[];
};
int main() {
INIT_PERSON(li_ming, MALE, "1992-02-13")
printf("Li ming gender is %d\n", li_ming->gender);
printf("Li ming birthday is %s\n", li_ming->birthday);
INIT_PERSON(han_mei_mei, FEMALE, "1989-09-21")
printf("Han Meimei gender is %d\n", han_mei_mei->gender);
printf("Han Meimei birthday is %s\n", han_mei_mei->birthday);
free(li_ming);
free(han_mei_mei);
return 0;
}
代碼中,INIT_PERSON
宏就實現了:類似於面向對象中創建實例的方法。Person結構體,對應的類方法可以使用類似的方式來實現。
父類和子類的繼承呢?當然可以通過遞歸調用宏定義來實現了。
當然了,以上只是一些粗淺的理解,這個方向還有很多細節可以挖掘。