搜索
Table_bottom

标签云
Table_bottom

分类
Table_bottom

声明
文章若未特別註明,皆採用 知识共享许可协议 請自覺遵守
Table_bottom

鏈。。。
Table_bottom

存档
Table_bottom

匆匆过客
36205
Table_bottom

功能
Table_bottom

Python中經驗致誤處

豫:這是一篇記述自己學藝不精的文章,而不是介紹什麼奇技淫巧或是隱藏語義的。

 

Python用了也有幾個年頭了(雖然並不是全年都在用),然而發現自己還是有很多依賴(其他語言的)經驗而導致問題的地方。可能是由於自己的學習方式,也可能僅僅是由於語義的問題,這些問題也許是一些人學習中總是會遇到的(嗯,我不會說有一些同學也遇到這些問題),故且總結一下,以饗後人。

 

我個人學習Python所依賴的是:1.其他語言的經驗(Pascal/C/C++/Java);2.實踐;3.《Python學習手冊》。

是的,我沒有看《Python Cookbook》一類的(貌似更適合用來入門的)東西——不是因爲覺得沒必要,而是因爲不知道《Python學習手冊》裏邊的內容居然是那個樣子……而看完《Python學習手冊》以後就再也不想看什麼書了,實在是被它的厚度噁心到了。

 

函數參數默認值

Python中支持函數重載,也(因此而得以)支持參數默認值。

在最初用過的其他語言中,一些(C、Java)不支持重載,而支持的(C++)也沒有內置一些高級數據結構(及其便捷的初始化方法),於是從來沒有遇到過這種問題。

 

到了Python中,我想當然地以爲:參數默認值在每次函數調用的時候都是一個全新的,或者說每次重入的時候都會重新初始化一遍。

而事實上,參數默認值在每次重入時都是同一個!

 

這在簡單一些的數據結構中問題/區別不大,例如數字等不可變類型:因爲它們每次賦值時都會修改引用爲一個新的,而不是修改目標數據的值。

然而,遇到複雜數據結構/對象的時候,區別就體現出來了。我碰到的就是以一個空列表(list)爲默認值時產生的奇妙現象:在函數中每次都要修改這個列表,而由於總是同一個對象,而不是每次從一個空白的新對象開始,於是進行索引、追加等等操作時候結果總是和預期的不同。

 

意識到這個問題之後開始排查問題所在,最終鎖定到參數默認值上。經過一段搜索,搜到了這裏。其中解釋道:參數默認值僅僅在def語句被執行時會被求值。並且,其中也給出了替代的解決方案,如:使用None作爲默認值,之後在函數體中進行判斷。

 

變量作用域

按照C++的習慣,每一個區塊中的變量在出了區塊之後就失效——其中用花括號({})來界定區塊。

故而,在python中我一直以爲每個縮進層級中所創建的變量在出了該層級之後就失效。

然而在教人(之前從沒學過編程)Python時,發現居然不是這樣!當時還鬧了個笑話……

 

所以看來Python中變量作用域是整個函數,而不僅僅是聲明/賦值的那個區塊。

 

import入口

寫的某個flask程序中由於需要在整個程序中共享一些變量/對象,所以在入口的最外區塊中直接進行賦值,然後在其他部分import之。

看起來一直運行正常,然而在假如migrate的時候出事了,數據庫ORM沒有出現。排查好久終於通過id()發現其他部分所用的變量和入口處的那個不同,而它們之間相同……

於是纔意識到,入口的那些代碼在調用的時候執行一次,在import時候又執行一次,而反復import的時候不會重複執行。

是了,以前只知道反復import不會重複執行,但是從來沒意識到入口處並不是import的……

 

 

(主作於2016.3.15。拖延症……)

Qt中鏈接信號和槽的方法

最近又在用Qt寫東西,順便翻了翻Qt的文檔,發現鏈接信號和槽的方法有不少,Qt5和C++11的組合也帶來了一些不小變化。

所以有此一篇文章,對其稍加總結。

继续阅读

記日前硬盤故障

先描述一下我的計算機概況:

500G硬盤,前大約100G是OEM的Windows7及其預置恢復分區(sda1-sda3),後邊全給了linux用。除了/boot,文件系統格式全是ext4,/home(sda6)有200G+。

發行版是arch,引導器是grub2。


某日,需要進Windows做點事,於是重啓電腦。到了引導項選擇那裏,由於我設置的是隱藏菜單(默認進arch),故而需要按ESC使菜單顯示出來。然而,我手快了些,在5s的倒計時開始前就按了ESC。然後只見硬盤持續亮着,引導菜單沒有出現。等了一會,實在不耐煩,於是長按電源,再開。這次刻意等到讀秒開始之後再按的ESC,引導項選擇也出現了,選擇Windows,確認。Windows的啓動用了很長時間,輸入密碼之後那個“歡迎”也持續旋轉很久。我以爲是上次安裝了一些更新所致,也沒有在意,之後使用沒什麼奇怪現象。

等到處理完,重啓進Arch時候,看到問題了:一直在報sda6一些塊讀取錯誤。看到這裏,大概判斷是硬盤有了壞塊,於是長按電源鍵,關掉了計算機。再次啓動時候是用U盤引導(上次給人裝Arch時候直接把鏡像dd到U盤),繼續看到sda6某某塊讀取出錯,不過這次倒是進了live系統。

然後用badblocks檢測,發現還真是有,主要分佈在sda6上,sda7也有少量。這下就令人着急了——數據可是無價的。幸好手上有個1T且只裝了一半的移動硬盤,可以將數據轉入。

然而,嘗試只讀掛載sda6的時候,提示superblock讀取失敗,拒絕掛載。上網一查,發現ext4會創建備份的superblock,於是按照網上說的,掛載時加上-o sb=xxx參數指定副superblock的塊號。這裏我man了一下mount,發現這個參數是以1K爲標準的。我創建的文件系統塊大小是4K,所以第32768塊的備份superblock應該用-o sb=131072參數。

這樣,連起來的掛載命令就是

mount -r -o sb=131072 -t ext4 /dev/sda6 /mnt/sda6

滿懷期待得讓命令執行,卻再次得到信息說“掛載失敗,可能是錯誤的文件系統類型”。看到這點時候真的有些慌,因爲我確認它是ext4,而掛載時候卻告訴我失敗,莫非數據真的無法挽救?之後又嘗試用其他的副superblock,結果卻沒有什麼變化。

這時候我抱着試試看的心態敲了一下dmesg,在最下邊看到journal讀取失敗,於是拒絕掛載。心情一下清平了,再掛載時候加上norecovery參數,掛載成功,備份數據。

最終的命令如下:

mount -r -o sb=131072,norecovery -t ext4 /dev/sda6 /mnt/sda6

慘痛的經歷再次說明經常備份數據的重要性。

以及,若有遇到類似情況的,望可告知產生原因是什麼……

(某想不通究竟是因爲斷電導致的還是確實是grub的bug……更不想再試試……)

自記初習Qt

百無聊賴,於是就拿出Qt Creator來學一下Qt(C++)。若有錯誤,還望指正。


Qt使用標準的C++,只是加入了預處理器(moc)以及一些宏。這就意味着可以使用任意的C++編譯器來編譯Qt程序。

文件

Qt圖形界面程序一般有三個文件,通常狀態下後綴名爲:.ui、.h、.cpp。

.ui文件是界面的設計文件,本質是xml文件。在構建時會依據其產生一些源文件以進行編譯。

.h文件是標準的c++頭文件,內部包含各類聲明等內容。需要特別注意的是,如果要使用Qt的信號/槽機制,需要在類聲明代碼塊首部分加入Q_OBJECT宏。

.cpp文件顯然是普通的C++程序源代碼。

從預設的組織結構看,Qt中的.h文件中僅僅寫聲明,定義在相應的.cpp文件中書寫。這點看起來比微軟的VS中那些亂七八糟的強許多。

然而打開進行編譯的臨時目錄,其中依據.ui文件自動生成的.h文件中卻包含了一些定義。不過這似乎是有意爲之,而且沒有直接暴露在編程者面前,也算是很不錯的做法。

信號/槽

相傳Qt最引以爲傲的就是它的“信號/槽”機制。該機制是一個解耦程度極高的組件通信方案,允許組件間在互不相知的狀態下進行通信。

如名稱所示,“信號”表示發送消息的部分,“槽”表示接收消息的部分。當一個信號發出時,其相關聯的槽會被立即(實際上仍有極其微小的延遲)調用。

信號或者槽連同它的參數類型表,一起被稱爲其“簽名”,意即其標識。Qt通過簽名類辨識信號或槽,也通過簽名來判斷一個信號是否能和槽相關聯。事實上,一個信號可和多個槽對應,一個槽也可和多個信號對應,甚至信號也可以引發另一個信號,這也就爲編程帶來了極大的靈活性。

爲了便於編程,Qt已對其組件(例如PushButton)定義好了一些信號(例如“按下”),編程時可直接使用。

信號

信號是一個不需要定義的函數。它應當在類中聲明,並在代碼中被引發。

聲明信號,只需在類聲明中寫入

signals:
    void 信號名( 參數類型表 );

除了聲明方法不同以及槽可以用於信號/槽機制通信外,槽的一切均和普通函數一致。

聲明槽,應當在類聲明中加入:

訪問方式 slots:
	返回值類型 槽名( 參數表 );

之後在相應的.cpp文件中定義即可。

其中訪問方式可爲public、protected、private中任一種,並會導致槽的作用域不同(和C++標準概念中一致)。

關聯信號和槽

關聯信號和槽的通常做法是

connect(發送者, SIGNAL(信號(參數類型表)), 接受者/*即槽所在*/, SLOT(槽(參數類型表)));

其中SIGNAL和SLOT均爲宏,其參數中實際上要寫的是信號或者槽的簽名。之前說過,我們可以將一個信號和另一個信號關聯起來,這只需將上面的槽換成信號即可。

其中信號的參數可以比槽的多,多餘的部分在槽被激活(調用)時會被忽略掉。

實際上,關聯信號與槽的方法還有一些變體,具體可參考我的這篇文章。

解除信號和槽的關聯

解除信號和槽的關聯需要用到disconnect函數,其使用方法和connect幾乎一致:

disconnect(發送者, SIGNAL(信號(參數類型表)), 接受者, SLOT(槽(參數類型表)));

實際上,該函數中除了發送者外,其餘部分均可以填“0”,表示“任意”。

手動發送信號

有時候Qt自帶的信號無法滿足我們的需求,這時候我們就需要知道如何手動發送自己定義的信號。

手動發送信號,只需要在想要發送信號的地方使用emit關鍵字發送你的信號:

emit 信號( 參數 );

實際上,emit是一個宏,並且其內容爲空。這也就意味着完全可以不使用emit來發送一個信號。但是個人推薦使用emit來明確標識這是一個信號。