整合營(yíng)銷(xiāo)服務(wù)商

          電腦端+手機(jī)端+微信端=數(shù)據(jù)同步管理

          免費(fèi)咨詢(xún)熱線:

          「Medium 萬(wàn)贊好文」ViewModel 和 L

          「Medium 萬(wàn)贊好文」ViewModel 和 LIveData:模式 + 反模式

          文作者: https://medium.com/@JoseAlcerreca

          原文地址: https://medium.com/androiddevelopers/viewmodels-and-livedata-patterns-antipatterns-21efaef74a54

          譯者:秉心說(shuō)

          View 和 ViewModel

          分配責(zé)任

          理想情況下,ViewModel 應(yīng)該對(duì) Android 世界一無(wú)所知。這提升了可測(cè)試性,內(nèi)存泄漏安全性,并且便于模塊化。通常的做法是保證你的 ViewModel 中沒(méi)有導(dǎo)入任何 android.*,android.arch.* (譯者注:現(xiàn)在應(yīng)該再加一個(gè) androidx.lifecycle)除外。這對(duì) Presenter(MVP) 來(lái)說(shuō)也一樣。

          ? 不要讓 ViewModel 和 Presenter 接觸到 Android 框架中的類(lèi)

          條件語(yǔ)句,循環(huán)和通用邏輯應(yīng)該放在應(yīng)用的 ViewModel 或者其它層來(lái)執(zhí)行,而不是在 Activity 和 Fragment 中。View 通常是不進(jìn)行單元測(cè)試的,除非你使用了 http://robolectric.org/,所以其中的代碼越少越好。View 只需要知道如何展示數(shù)據(jù)以及向 ViewModel/Presenter 發(fā)送用戶事件。這叫做 https://martinfowler.com/eaaDev/PassiveScreen.html 模式。

          ? 讓 Activity/Fragment 中的邏輯盡量精簡(jiǎn)

          ViewModel 中的 View 引用

          https://developer.android.com/topic/libraries/architecture/viewmodel.html 和 Activity/Fragment具有不同的作用域。當(dāng) Viewmodel 進(jìn)入 alive 狀態(tài)且在運(yùn)行時(shí),activity 可能位于 https://developer.android.com/guide/components/activities/activity-lifecycle.html 的任何狀態(tài)。Activitie 和 Fragment 可以在 ViewModel 無(wú)感知的情況下被銷(xiāo)毀和重新創(chuàng)建。

          向 ViewModel 傳遞 View(Activity/Fragment) 的引用是一個(gè)很大的冒險(xiǎn)。假設(shè) ViewModel 請(qǐng)求網(wǎng)絡(luò),稍后返回?cái)?shù)據(jù)。若此時(shí) View 的引用已經(jīng)被銷(xiāo)毀,或者已經(jīng)成為一個(gè)不可見(jiàn)的 Activity。這將導(dǎo)致內(nèi)存泄漏,甚至 crash。

          ? 避免在 ViewModel 中持有 View 的引用

          在 ViewModel 和 View 中通信的建議方式是觀察者模式,使用 LiveData 或者其他類(lèi)庫(kù)中的可觀察對(duì)象。

          觀察者模式

          在 Android 中設(shè)計(jì)表示層的一種非常方便的方法是讓 View 觀察和訂閱 ViewModel(中的變化)。由于 ViewModel 并不知道 Android 的任何東西,所以它也不知道 Android 是如何頻繁的殺死 View 的。這有如下好處:

          1. ViewModel 在配置變化時(shí)保持不變,所以當(dāng)設(shè)備旋轉(zhuǎn)時(shí)不需要再重新請(qǐng)求資源(數(shù)據(jù)庫(kù)或者網(wǎng)絡(luò))。
          2. 當(dāng)耗時(shí)任務(wù)執(zhí)行結(jié)束,ViewModel 中的可觀察數(shù)據(jù)更新了。這個(gè)數(shù)據(jù)是否被觀察并不重要,嘗試更新一個(gè)
          3. 不存在的 View 并不會(huì)導(dǎo)致空指針異常。
          4. ViewModel 不持有 View 的引用,降低了內(nèi)存泄漏的風(fēng)險(xiǎn)。
          private void subscribeToModel() {
           // Observe product data
           viewModel.getObservableProduct().observe(this, new Observer<Product>() {
           @Override
           public void onChanged(@Nullable Product product) {
           mTitle.setText(product.title);
           }
           });
          }
          

          ? 讓 UI 觀察數(shù)據(jù)的變化,而不是把數(shù)據(jù)推送給 UI

          胖 ViewModel

          無(wú)論是什么讓你選擇分層,這總是一個(gè)好主意。如果你的 ViewModel 擁有大量的代碼,承擔(dān)了過(guò)多的責(zé)任,那么:

          • 移除一部分邏輯到和 ViewModel 具有同樣作用域的地方。這部分將和應(yīng)用的其他部分進(jìn)行通信并更新
          • ViewModel 持有的 LiveData。
          • 采用 https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html,添加一個(gè) domain 層。這是一個(gè)可測(cè)試,易維護(hù)的架構(gòu)。https://github.com/android/architecture-samples 中有 Clean Architecture 的示例。

          ? 分發(fā)責(zé)任,如果需要的話,添加 domain 層

          使用數(shù)據(jù)倉(cāng)庫(kù)

          如 https://developer.android.com/jetpack/docs/guide 中所說(shuō),大部分 App 有多個(gè)數(shù)據(jù)源:

          1. 遠(yuǎn)程:網(wǎng)絡(luò)或者云端
          2. 本地:數(shù)據(jù)庫(kù)或者文件
          3. 內(nèi)存緩存

          在你的應(yīng)用中擁有一個(gè)數(shù)據(jù)層是一個(gè)好主意,它和你的視圖層完全隔離。保持緩存和數(shù)據(jù)庫(kù)與網(wǎng)絡(luò)同步的算法并不簡(jiǎn)單。建議使用單獨(dú)的 Repository 類(lèi)作為處理這種復(fù)雜性的單一入口點(diǎn).

          如果你有多個(gè)不同的數(shù)據(jù)模型,考慮使用多個(gè) Repository 倉(cāng)庫(kù)。

          ? 添加數(shù)據(jù)倉(cāng)庫(kù)作為你的數(shù)據(jù)的單一入口點(diǎn)。

          處理數(shù)據(jù)狀態(tài)

          考慮下面這個(gè)場(chǎng)景:你正在觀察 ViewModel 暴露出來(lái)的一個(gè) LiveData,它包含了需要顯示的列表項(xiàng)。那么 View 如何區(qū)分?jǐn)?shù)據(jù)已經(jīng)加載,網(wǎng)絡(luò)錯(cuò)誤和空集合?

          • 你可以通過(guò) ViewModel 暴露出一個(gè) LiveData ,MyDataState 可以包含數(shù)據(jù)正在加載,已經(jīng)加載完成,發(fā)生錯(cuò)誤等信息。
          • 你可以將數(shù)據(jù)包裝在具有狀態(tài)和其他元數(shù)據(jù)(如錯(cuò)誤消息)的類(lèi)中。查看示例中的 https://developer.android.com/jetpack/docs/guide#addendum 類(lèi)。

          ? 使用包裝類(lèi)或者另一個(gè) LiveData 來(lái)暴露數(shù)據(jù)的狀態(tài)信息

          保存 activity 狀態(tài)

          當(dāng) activity 被銷(xiāo)毀或者進(jìn)程被殺導(dǎo)致 activity 不可見(jiàn)時(shí),重新創(chuàng)建屏幕所需要的信息被稱(chēng)為 activity 狀態(tài)。屏幕旋轉(zhuǎn)就是最明顯的例子,如果狀態(tài)保存在 ViewModel 中,它就是安全的。

          但是,你可能需要在 ViewModel 也不存在的情況下恢復(fù)狀態(tài),例如當(dāng)操作系統(tǒng)由于資源緊張殺掉你的進(jìn)程時(shí)。

          為了有效的保存和恢復(fù) UI 狀態(tài),使用 onSaveInstanceState() 和 ViewModel 組合。

          詳見(jiàn):[ViewModels: Persistence, onSaveInstanceState(), Restoring UIState and Loaders](https://medium.com/google-developers/viewmodels-persistence-onsaveinstancestate-restoring-ui-state-and-loaders-fc7cc4a6c090) 。

          Event

          Event 指只發(fā)生一次的事件。ViewModel 暴露出的是數(shù)據(jù),那么 Event 呢?例如,導(dǎo)航事件或者展示 Snackbar 消息,都是應(yīng)該只被執(zhí)行一次的動(dòng)作。

          LiveData 保存和恢復(fù)數(shù)據(jù),和 Event 的概念并不完全符合。看看具有下面字段的一個(gè) ViewModel:

          LiveData<String> snackbarMessage=new MutableLiveData<>();
          

          Activity 開(kāi)始觀察它,當(dāng) ViewModel 結(jié)束一個(gè)操作時(shí)需要更新它的值:

          snackbarMessage.setValue("Item saved!");
          

          Activity 接收到了值并且顯示了 SnackBar。顯然就應(yīng)該是這樣的。

          但是,如果用戶旋轉(zhuǎn)了手機(jī),新的 Activity 被創(chuàng)建并且開(kāi)始觀察。當(dāng)對(duì) LiveData 的觀察開(kāi)始時(shí),新的 Activity 會(huì)立即接收到舊的值,導(dǎo)致消息再次被顯示。

          與其使用架構(gòu)組件的庫(kù)或者擴(kuò)展來(lái)解決這個(gè)問(wèn)題,不如把它當(dāng)做設(shè)計(jì)問(wèn)題來(lái)看。我們建議你把事件當(dāng)做狀態(tài)的一部分。

          把事件設(shè)計(jì)成狀態(tài)的一部分。更多細(xì)節(jié)請(qǐng)閱讀 https://medium.com/google-developers/livedata-with-snackbar-navigation-and-other-events-the-singleliveevent-case-ac2622673150

          ViewModel 的泄露

          得益于方便的連接 UI 層和應(yīng)用的其他層,響應(yīng)式編程在 Android 中工作的很高效。LiveData 是這個(gè)模式的關(guān)鍵組件,你的 Activity 和 Fragment 都會(huì)觀察 LiveData 實(shí)例。

          LiveData 如何與其他組件通信取決于你,要注意內(nèi)存泄露和邊界情況。如下圖所示,視圖層(Presentation Layer)使用觀察者模式,數(shù)據(jù)層(Data Layer)使用回調(diào)。

          當(dāng)用戶退出應(yīng)用時(shí),View 不可見(jiàn)了,所以 ViewModel 不需要再被觀察。如果數(shù)據(jù)倉(cāng)庫(kù) Repository 是單例模式并且和應(yīng)用同作用域,那么直到應(yīng)用進(jìn)程被殺死,數(shù)據(jù)倉(cāng)庫(kù) Repository 才會(huì)被銷(xiāo)毀。 只有當(dāng)系統(tǒng)資源不足或者用戶手動(dòng)殺掉應(yīng)用這才會(huì)發(fā)生。如果數(shù)據(jù)倉(cāng)庫(kù) Repository 持有 ViewModel 的回調(diào)的引用,那么 ViewModel 將會(huì)發(fā)生內(nèi)存泄露。

          如果 ViewModel 很輕量,或者保證操作很快就會(huì)結(jié)束,這種泄露也不是什么大問(wèn)題。但是,事實(shí)并不總是這樣。理想情況下,只要沒(méi)有被 View 觀察了,ViewModel 就應(yīng)該被釋放。

          你可以選擇下面幾種方式來(lái)達(dá)成目的:

          • 通過(guò) ViewModel.onCLeared() 通知數(shù)據(jù)倉(cāng)庫(kù)釋放 ViewModel 的回調(diào)
          • 在數(shù)據(jù)倉(cāng)庫(kù) Repository 中使用 弱引用 ,或者 Event Bu(兩者都容易被誤用,甚至被認(rèn)為是有害的)。
          • 通過(guò)在 View 和 ViewModel 中使用 LiveData 的方式,在數(shù)據(jù)倉(cāng)庫(kù)和 ViewModel 之間進(jìn)程通信

          ? 考慮邊界情況,內(nèi)存泄露和耗時(shí)任務(wù)會(huì)如何影響架構(gòu)中的實(shí)例。

          ? 不要在 ViewModel 中進(jìn)行保存狀態(tài)或者數(shù)據(jù)相關(guān)的核心邏輯。 ViewModel 中的每一次調(diào)用都可能是最后一次操作。

          數(shù)據(jù)倉(cāng)庫(kù)中的 LiveData

          為了避免 ViewModel 泄露和回調(diào)地獄,數(shù)據(jù)倉(cāng)庫(kù)應(yīng)該被這樣觀察:

          當(dāng) ViewModel 被清除,或者 View 的生命周期結(jié)束,訂閱也會(huì)被清除:

          如果你嘗試這種方式的話會(huì)遇到一個(gè)問(wèn)題:如果不訪問(wèn) LifeCycleOwner 對(duì)象的話,如果通過(guò) ViewModel 訂閱數(shù)據(jù)倉(cāng)庫(kù)?使用 https://developer.android.com/topic/libraries/architecture/livedata#transform_livedata 可以很方便的解決這個(gè)問(wèn)題。Transformations.switchMap 可以讓你根據(jù)一個(gè) LiveData 實(shí)例的變化創(chuàng)建新的 LiveData。它還允許你通過(guò)調(diào)用鏈傳遞觀察者的生命周期信息:

          LiveData<Repo> repo=Transformations.switchMap(repoIdLiveData, repoId -> {
           if (repoId.isEmpty()) {
           return AbsentLiveData.create();
           }
           return repository.loadRepo(repoId);
           }
          );
          

          在這個(gè)例子中,當(dāng)觸發(fā)更新時(shí),這個(gè)函數(shù)被調(diào)用并且結(jié)果被分發(fā)到下游。如果一個(gè) Activity 觀察了 repo,那么同樣的 LifecycleOwner 將被應(yīng)用在 repository.loadRepo(repoId) 的調(diào)用上。

          無(wú)論什么時(shí)候你在 https://developer.android.com/reference/android/arch/lifecycle/ViewModel.html 內(nèi)部需要一個(gè) https://developer.android.com/reference/android/arch/lifecycle/Lifecycle.html 對(duì)象時(shí),https://developer.android.com/topic/libraries/architecture/livedata#transform_livedata 都是一個(gè)好方案。

          繼承 LiveData

          在 ViewModel 中使用 LiveData 最常用的就是 MutableLiveData,并且將其作為 LiveData 暴露給外部,以保證對(duì)觀察者不可變。

          如果你需要更多功能,繼承 LiveData 會(huì)讓你知道活躍的觀察者。這對(duì)你監(jiān)聽(tīng)位置或者傳感器服務(wù)很有用。

          public class MyLiveData extends LiveData<MyData> {
           public MyLiveData(Context context) {
           // Initialize service
           }
           @Override
           protected void onActive() {
           // Start listening
           }
           @Override
           protected void onInactive() {
           // Stop listening
           }
          }
          

          什么時(shí)候不要繼承 LiveData

          你也可以通過(guò) onActive() 來(lái)開(kāi)啟服務(wù)加載數(shù)據(jù)。但是除非你有一個(gè)很好的理由來(lái)說(shuō)明你不需要等待 LiveData 被觀察。下面這些通用的設(shè)計(jì)模式:

          • 給 ViewModel 添加 start() 方法,并盡快調(diào)用它。https://github.com/android/architecture-samples/blob/dev-todo-mvvm-live/todoapp/app/src/main/java/com/example/android/architecture/blueprints/todoapp/addedittask/AddEditTaskFragment.java#L64]
          • 設(shè)置一個(gè)觸發(fā)加載的屬性 https://github.com/googlesamples/android-architecture-components/blob/master/GithubBrowserSample/app/src/main/java/com/android/example/github/ui/repo/RepoFragment.kt]

          你并不需要經(jīng)常繼承 LiveData 。讓 Activity 和 Fragment 告訴 ViewModel 什么時(shí)候開(kāi)始加載數(shù)據(jù)。

          分割線

          翻譯就到這里了,其實(shí)這篇文章已經(jīng)在我的收藏夾里躺了很久了。最近 Google 重寫(xiě)了 https://github.com/android/plaid 應(yīng)用,用上了一系列最新技術(shù)棧, https://developer.android.com/topic/libraries/architecture/,MVVM, Kotlin,協(xié)程 等等。這也是我很喜歡的一套技術(shù)棧,之前基于此開(kāi)源了 https://github.com/lulululbj/wanandroid 應(yīng)用 ,詳見(jiàn) https://juejin.im/post/5cb473e66fb9a068af37a6ce 。

          當(dāng)時(shí)基于對(duì) MVVM 的淺薄理解寫(xiě)了一套自認(rèn)為是 MVVM 的 MVVM 架構(gòu),在閱讀一些關(guān)于架構(gòu)的文章,以及 Plaid 源碼之后,發(fā)現(xiàn)了自己的 MVVM 的一些認(rèn)知誤區(qū)。后續(xù)會(huì)對(duì) https://github.com/lulululbj/wanandroid 應(yīng)用進(jìn)行合理改造,并結(jié)合上面譯文中提到的知識(shí)點(diǎn)作一定的說(shuō)明。歡迎 Star !

          網(wǎng)站開(kāi)發(fā)過(guò)程中,需要從前端向后端傳入數(shù)據(jù),由后端對(duì)數(shù)據(jù)進(jìn)行操作,比如計(jì)算、存入數(shù)據(jù)庫(kù)等。

          從前端向后端傳輸數(shù)據(jù),一般使用form表單。在Django中,有三種方法:

          1. 手寫(xiě)前端html代碼,使用form表單:
          <form action=’’method=’post’> </form>
          1. Django提供了form類(lèi),生成form表單。
          2. Django提供了Modelform類(lèi),生成form表單。

          Modelform做為Django中集成的組件,主要針對(duì)數(shù)據(jù)庫(kù)中的某個(gè)表操作,通過(guò)models.py關(guān)聯(lián)數(shù)據(jù)庫(kù)。

          本文著重講modelform的使用,下面正式開(kāi)始。

          首先建立一個(gè)forms.py,用來(lái)寫(xiě)項(xiàng)目里的表單類(lèi)。

          首先引入幾個(gè)類(lèi)

          from django import forms #引入forms表單類(lèi)
          from users.models import User #引入models里的User類(lèi)
          from django.core.exceptions import ValidationError #引入異常拋出類(lèi)

          創(chuàng)建User表單類(lèi),類(lèi)繼承了forms.ModelForm,password_confirm是密碼確認(rèn),我們?cè)谶M(jìn)行注冊(cè)的時(shí)候,往往會(huì)要求確認(rèn)一次密碼。

          class Meta:是利用model創(chuàng)建表單的類(lèi)。model=User,用來(lái)實(shí)例化models.py中的User類(lèi),fields是表單中的字段,也就是表單項(xiàng)目。widgets是一個(gè)字典,在這里定義password表單為密碼輸入格式。

          class UserModelForm(forms.ModelForm):
              password_confirm=forms.CharField(label="確認(rèn)密碼",widget=forms.PasswordInput,min_length=6,max_length=20)
              class Meta:
                  model=User
                  fields=['username','password','password_confirm','gender','role']
                  widgets={"password":forms.PasswordInput()}

          下面在templates文件夾下建立一個(gè)user_add_form.html文件,用來(lái)展示表單。

          在views.py中增加一個(gè)方法user_add_form()方法。該方法需要使用forms.py中的UserModelForm類(lèi)和models中的User類(lèi),在頭部引入這兩個(gè)類(lèi),

          from users.models import User   #引入models里的User類(lèi)
          from users.forms import UserModelForm #引入forms里的UserModelForm

          增加一個(gè)user_add_form(request)方法,當(dāng)前端的request是一個(gè)get方法時(shí),實(shí)例化UserModelForm(),返回render方法,顯示form表單,否則,將request.POST的數(shù)據(jù)傳入U(xiǎn)serModelForm類(lèi)并實(shí)例化,

          def user_add_form(request):
              if request.method=="GET":
                  form=UserModelForm()
              return render(request,"user_add_form.html",{"form":form})

          在urls.py中增加一個(gè)路由。

          在user_add_form.html中寫(xiě)入{{form}},用來(lái)展示后端返回的form數(shù)據(jù)。

          下面在瀏覽中測(cè)試一下。

          輸入127.0.0.1:8000/user_add_form/

          右鍵檢查頁(yè)面源碼,發(fā)現(xiàn),字段及輸入框已經(jīng)在頁(yè)面中。

          本文結(jié)束。下一篇文章,將對(duì)user_add_form.html進(jìn)行修改,實(shí)現(xiàn)表單的輸入功能,并通過(guò)表單將數(shù)據(jù)傳入后端,并插入數(shù)據(jù)庫(kù)。

          前面幾期內(nèi)容連續(xù)的介紹了Python的函數(shù)相關(guān)編程知識(shí),是一個(gè)相對(duì)且完整的知識(shí)域,本文主要是對(duì)函數(shù)知識(shí)的一些有益拓展和補(bǔ)充。

          本文簡(jiǎn)單扼要地說(shuō),輔以代碼進(jìn)一步地加深理解。我們繼續(xù)——記得點(diǎn)贊+關(guān)注@傳新視界

          函數(shù)進(jìn)階與補(bǔ)充

          遞歸函數(shù)

          當(dāng)函數(shù)調(diào)用自身而生成最終結(jié)果時(shí),這樣的函數(shù)稱(chēng)為遞歸。有時(shí)遞歸函數(shù)非常有用,因?yàn)樗鼈兪咕帉?xiě)代碼變得更容易——使用遞歸范式編寫(xiě)一些算法非常容易,而其他算法則不是這樣。沒(méi)有不能以迭代方式重寫(xiě)的遞歸函數(shù),換句話說(shuō),所有遞歸函數(shù)都可以通過(guò)循環(huán)迭代的方式實(shí)現(xiàn),因此通常由程序員根據(jù)手頭的情況選擇最佳方法。

          遞歸函數(shù)主體通常有兩個(gè)部分:一部分的返回值依賴(lài)于對(duì)自身的后續(xù)調(diào)用,另一部分的返回值不依賴(lài)于對(duì)自身的后續(xù)調(diào)用(稱(chēng)基本情況,或遞歸邊界)。

          作為理解的參考示例,我們看一個(gè)階乘函數(shù)N!作為遞歸的兩部分分別是:基本情況(邊界,用來(lái)結(jié)束遞歸)是當(dāng)N為0或1時(shí),函數(shù)返回1,不需要進(jìn)一步計(jì)算。另一方面,在一般情況下的自我調(diào)用,即N!返回的生成結(jié)果:

          1 * 2 * ... * (N-1) * N

          如果你仔細(xì)想想,N!可以寫(xiě)成這樣:N!=(N - 1) !*N。作為一個(gè)實(shí)際的例子,請(qǐng)看如下的階乘表示:

          5!=1 * 2 * 3 * 4 * 5=(1 * 2 * 3 * 4) * 5=4! * 5

          我們來(lái)轉(zhuǎn)化成函數(shù)實(shí)現(xiàn):

          # 階乘遞歸函數(shù)實(shí)現(xiàn)
          def factorial(n):
              if n in (0, 1): # 遞歸邊界
                  return 1
              return factorial(n - 1) * n # 遞歸調(diào)用
          

          高手大俠們?cè)诰帉?xiě)算法時(shí)經(jīng)常使用遞歸函數(shù),編寫(xiě)遞歸函數(shù)非常有趣。作為練習(xí),嘗試使用遞歸和迭代方法解決幾個(gè)簡(jiǎn)單的問(wèn)題。很好的練習(xí)對(duì)象可能是計(jì)算斐波那契數(shù)列,或其它諸如此類(lèi)的東西。自己動(dòng)手去試試吧。

          提示:

          在編寫(xiě)遞歸函數(shù)時(shí),總是考慮要進(jìn)行多少個(gè)嵌套調(diào)用,因?yàn)檫@是有限制的。有關(guān)這方面的更多信息,請(qǐng)查看sys.getrecursionlimit()和sys.setrecursionlimit()。

          匿名函數(shù)

          還有一種函數(shù)是匿名函數(shù)(Anonymous functions)。這些函數(shù)在Python中稱(chēng)為lambda(蘭姆達(dá)),其通常在使用具有自己完整定義名稱(chēng)的函數(shù)有些多余時(shí)而使用,此時(shí)所需要的只是一個(gè)快速、簡(jiǎn)單的一行程序來(lái)完成這項(xiàng)工作。

          假設(shè)我們想要一個(gè)列表,所有N的某個(gè)值,是5的倍數(shù)的數(shù)字。為此,我們可以使用filter()函數(shù),它需要一個(gè)函數(shù)和一個(gè)可迭代對(duì)象作為輸入。返回值是一個(gè)過(guò)濾器對(duì)象,當(dāng)你遍歷它時(shí),會(huì)從輸入可迭代對(duì)象中生成元素,所需的參數(shù)函數(shù)會(huì)為其返回True。如果不使用匿名函數(shù),我們可能會(huì)這樣做:

          def isMultipleOfFive(n):
              return not n % 5
          
          def getMultiplesOfFive(n):
              return list(filter(isMultipleOfFive, range(n)))
          

          注意我們?nèi)绾问褂胕sMultipleOfFive()來(lái)過(guò)濾前n個(gè)自然數(shù)。這似乎有點(diǎn)過(guò)分——任務(wù)及其很簡(jiǎn)單,我們不需要為其他任何事情保留isMultipleOfFive()函數(shù)。此時(shí),我們就可用lambda函數(shù)來(lái)重寫(xiě)它:

          # lambda過(guò)濾
          def getMultiplesOfFive(n):
               return list(filter(lambda k: not k % 5, range(n)))
          

          邏輯是完全相同的,但是過(guò)濾函數(shù)現(xiàn)在是個(gè)lambda函數(shù),顯然,Lambda更簡(jiǎn)單。

          定義Lambda函數(shù)非常簡(jiǎn)單,它遵循以下形式:

          funcName=lambda [parameter_list]: expression

          其返回的是一個(gè)函數(shù)對(duì)象,相當(dāng)于:

          def func_ name([parameter_list]):return expression

          參數(shù)列表以逗號(hào)分隔。

          注意,可選參數(shù)是方括號(hào)括起來(lái)的部分,是通用語(yǔ)法的表示形式,即文中的方括號(hào)部分是可選的,根據(jù)實(shí)際需要提供,

          我們?cè)賮?lái)看另外兩個(gè)等價(jià)函數(shù)的例子,以?xún)煞N形式定義:

          # lambda說(shuō)明
          # 示例 1: 兩數(shù)相加
          def adder(a, b):
              return a + b
          # 等價(jià)于:
          adder_lambda=lambda a, b: a + b
          
          # 示例 2: 字符串轉(zhuǎn)大寫(xiě)
          def to_upper(s):
              return s.upper()
          # 等價(jià)于:
          to_upper_lambda=lambda s: s.upper()
          

          前面的例子非常簡(jiǎn)單。第一個(gè)函數(shù)將兩個(gè)數(shù)字相加,第二個(gè)函數(shù)生成字符串的大寫(xiě)版本。注意,我們將lambda表達(dá)式返回的內(nèi)容賦值給一個(gè)名稱(chēng)(adder_lambda, to_upper_lambda),但是當(dāng)按照f(shuō)ilter()示例中的方式使用lambda時(shí),就不需要這樣做了——不需要把匿名函數(shù)賦給變量。

          函數(shù)屬性

          Python中每個(gè)函數(shù)都是一個(gè)完整的對(duì)。因此,它有許多屬性。其中一些是特殊的,可以以?xún)?nèi)省的方式在運(yùn)行時(shí)檢查函數(shù)對(duì)象。下面的示例,展示了它們的一部分以及如何為示例函數(shù)顯示它們的值:

          # 函數(shù)屬性
          def multiplication(a, b=1):
              """返回a乘以b的結(jié)構(gòu). """
              return a * b
          
          if __name__=="__main__":
              special_attributes=[
              "__doc__", "__name__", "__qualname__", "__module__",
              "__defaults__", "__code__", "__globals__", "__dict__",
              "__closure__", "__annotations__", "__kwdefaults__",
              ]
              for attribute in special_attributes:
                  print(attribute, '->', getattr(multiplication, attribute))
          

          我們使用內(nèi)置的getattr()函數(shù)來(lái)獲取這些屬性的值。getattr(obj, attribute)等價(jià)于obj.attribute,當(dāng)我們需要在運(yùn)行時(shí)動(dòng)態(tài)地獲取屬性時(shí),就從變量中獲取屬性的名稱(chēng)(如本例中所示),此時(shí)它就會(huì)派上用場(chǎng)。

          運(yùn)行這個(gè)腳本會(huì)得到類(lèi)似如下輸出:

          __doc__ -> 返回a乘以b的結(jié)果.

          __name__ -> multiplication

          __qualname__ -> multiplication

          __module__ -> __main__

          __defaults__ -> (1,)

          __code__ -> <……>

          __globals__ -> {…略…}

          __dict__ -> {}

          __closure__ -> None

          __annotations__ -> {}

          __kwdefaults__ -> None

          這里省略了__globals__屬性的值,內(nèi)容太多。這個(gè)屬性的含義可以在Python數(shù)據(jù)模型文檔頁(yè)面(或自帶幫助文檔中)的可調(diào)用類(lèi)型部分找到:

          https://docs.python.org/3/reference/datamodel.html#the-standard-typehierarchy

          再次提醒:如果你想查看對(duì)象的所有屬性,只需調(diào)用dir(object_name),將得到其所有屬性的列表。

          內(nèi)置函數(shù)

          Python自帶很多內(nèi)置函數(shù)。它們可以在任何地方使用,你可以通過(guò)dir(__builtins__)來(lái)查看builtins模塊,或通過(guò)訪問(wèn)官方Python文檔來(lái)獲得它們的列表。這里就不一一介紹了。在前面的學(xué)習(xí)過(guò)程中,我們已經(jīng)見(jiàn)過(guò)其中的一些,如any、bin、bool、divmod、filter、float、getattr、id、int、len、list、min、print、set、tuple、type和zip等,但還有更多,建議你至少應(yīng)該閱讀一次。熟悉它們,嘗試它們,為它們每個(gè)編寫(xiě)一小段代碼,并確保您隨時(shí)可以使用它們,以便在需要時(shí)使用它們。

          可在官方文檔中找到這個(gè)內(nèi)置函數(shù)列表:https://docs.python.org/3/library/functions.html 。

          文檔化代碼

          我們非常喜歡不需要文檔的代碼。當(dāng)我們正確地編程、選擇正確的名稱(chēng)、并注意細(xì)節(jié)時(shí),代碼應(yīng)該是不言自明的,幾乎不需要文檔。不過(guò),有時(shí)注釋非常有用,添加一些文檔化描述也是如此。你可以在Python的PEP 257規(guī)范——文檔字符串約定中找到Python的文檔指南:

          https://www.python.org/dev/peps/pep-0257/,

          但在這里還是會(huì)向你展示基本原理。Python的文檔中包含字符串,這些字符串被恰當(dāng)?shù)胤Q(chēng)為文檔字符串(docstrings)。任何對(duì)象都可以被文檔化來(lái)加以描述記錄,可以使用單行或多行文檔字符串。單行程序非常簡(jiǎn)單。不是為函數(shù)提供另外的簽名,而應(yīng)該聲明或描述函數(shù)的目的。請(qǐng)看下面的示例:

          # 簡(jiǎn)單的文檔化代碼
          def square(n):
              """功能:返回?cái)?shù)字n的平方。 """
              return n ** 2
          
          def get_username(userid):
              """功能:返回給定id的用戶名稱(chēng)。 """
              return db.get(user_id=userid).username
          
          • 使用三重雙引號(hào)字符串可以在以后輕松展開(kāi)或擴(kuò)展文檔內(nèi)容。
          • 使用以句號(hào)結(jié)尾的句子,不要在前后留下空行。
          • 多行注釋的結(jié)構(gòu)與此類(lèi)似。應(yīng)該用一行代碼簡(jiǎn)單地說(shuō)明對(duì)象的主旨,然后是更詳細(xì)的描述。

          作為多行文檔化的一個(gè)例子,我們?cè)谙旅娴睦又惺褂肧phinx表示法記錄了一個(gè)虛構(gòu)的connect()函數(shù)及文檔化描述:

          # 多行文檔化代碼
          def connect(host, port, user, password):
              """功能:連接數(shù)據(jù)庫(kù)并返回連接對(duì)象.
              使用如下參數(shù)直接連接 PostgreSQL數(shù)據(jù)庫(kù).
              :param host: 主機(jī) IP.
              :param port: 端口.
              :param user: 連接用戶名.
              :param password: 連接密碼.
              :return: 連接對(duì)象.
              """
              # 函數(shù)主體...
              return connection

          提示:

          Sphinx是用于創(chuàng)建Python文檔的最廣泛使用的工具之一——事實(shí)上,官方Python文檔就是用它編寫(xiě)的。絕對(duì)值得花點(diǎn)時(shí)間去看看。

          內(nèi)置函數(shù)help()用于即時(shí)交互使用的,它就使用對(duì)象的文檔字符串為對(duì)象創(chuàng)建文檔頁(yè)面來(lái)展示對(duì)象的用法?;居梅ㄈ缦拢?/p>

          def square(n):
              """功能:返回?cái)?shù)字n的平方。 """
              return n ** 2
          
          help(square)
          Help on function square in module __main__:
          
          square(n)
          功能:返回?cái)?shù)字n的平方。

          首先明確或定義一個(gè)對(duì)象或函數(shù)(包括已有的對(duì)象或函數(shù)),然后使用內(nèi)置help函數(shù),并把對(duì)象或函數(shù)做help的參數(shù),該函數(shù)就會(huì)返回相應(yīng)對(duì)象的說(shuō)明文檔了。就這么簡(jiǎn)單。

          本文小結(jié)

          本文主要基于Python語(yǔ)言的一大特色——函數(shù)來(lái)拓展的一些相關(guān)編程知識(shí),包括遞歸函數(shù)(重點(diǎn)是有限性和邊界性)、lambda函數(shù)(簡(jiǎn)潔性和臨時(shí)性)以及函數(shù)的屬性以及如何實(shí)現(xiàn)函數(shù)的文檔化描述等。

          本文就寫(xiě)這些了,記得點(diǎn)贊 +關(guān)注@傳新視界,轉(zhuǎn)發(fā)分享給更多的朋友。再見(jiàn)^_^


          主站蜘蛛池模板: 日产一区日产2区| 精品一区二区三区影院在线午夜| 污污内射在线观看一区二区少妇| 国产精品福利一区二区久久| 无码人妻精品一区二区三区久久| 中文字幕一区二区三| 精品视频在线观看你懂的一区| 精品人妻一区二区三区四区| 亚洲毛片αv无线播放一区| 国产高清视频一区三区| 国产成人精品第一区二区| 精品一区二区三区在线观看l| 夜色福利一区二区三区| 日韩精品无码免费一区二区三区| 91精品国产一区| 中文字幕一区二区在线播放 | 免费人人潮人人爽一区二区| 本免费AV无码专区一区| 高清精品一区二区三区一区| 国产午夜精品免费一区二区三区| 精品一区二区三人妻视频| 日本免费一区二区三区| 亚洲乱色熟女一区二区三区丝袜| AV无码精品一区二区三区| 久久久久一区二区三区| 国产探花在线精品一区二区| 琪琪see色原网一区二区| 国产精品成人99一区无码| 黑巨人与欧美精品一区| 免费看无码自慰一区二区| 色精品一区二区三区| 日本无码一区二区三区白峰美| 久久久老熟女一区二区三区| 久久中文字幕一区二区| 精品无码日韩一区二区三区不卡| 少妇无码一区二区三区免费| 国产精品揄拍一区二区久久| 深田咏美AV一区二区三区| 精品国产日韩亚洲一区在线 | 一区视频在线播放| 成人国产一区二区三区|