從Python的exec()聊起


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結構體,對應的類方法可以使用類似的方式來實現。

父類和子類的繼承呢?當然可以通過遞歸調用宏定義來實現了。

當然了,以上只是一些粗淺的理解,這個方向還有很多細節可以挖掘。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM