1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        如何在 Python 中編寫干凈的代碼

        共 31314字,需瀏覽 63分鐘

         ·

        2024-04-11 15:13

        推薦關(guān)注↓


        當(dāng)我們閱讀別人的代碼時(shí),最痛苦的莫過于代碼難以閱讀和維護(hù)。

        在這篇文章中,我將分享如何在 Python 中編寫干凈的代碼及代碼編寫規(guī)則。

        對于每個(gè)原則,我將提供小的代碼片段來更好地解釋原則,并向你展示如何處理事情以及如何不處理事情。

        我希望這篇文章能為所有使用 Python 的人提供價(jià)值,但特別是激勵(lì)其他數(shù)據(jù)科學(xué)家編寫干凈的代碼。

        有意義的名稱

        這一部分應(yīng)該是顯而易見的,但許多開發(fā)人員仍然沒有遵循它。

        創(chuàng)建有意義的名稱!

        每個(gè)人在閱讀你的代碼時(shí)都應(yīng)該直接理解發(fā)生了什么。不應(yīng)該需要內(nèi)聯(lián)注釋來描述您的代碼在做什么以及某些變量代表什么。

        如果名稱是描述性的,那么應(yīng)該非常清楚函數(shù)在做什么。

        讓我們看一個(gè)典型的機(jī)器學(xué)習(xí)示例:加載數(shù)據(jù)集并將其拆分為訓(xùn)練集和測試集:

        import pandas as pd
        from sklearn.model_selection import train_test_split

        def load_and_split(d):
            df = pd.read_csv(d)
            X = df.iloc[:, :-1]
            y = df.iloc[:, -1]
            X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
            return X_train, X_test, y_train, y_test

        大多數(shù)了解數(shù)據(jù)科學(xué)的人都知道這里發(fā)生了什么,他們也知道 X 是什么,y 是什么。但是對于新手來說呢?

        將 CSV 文件的路徑僅用 d 命名,這是一個(gè)好的做法嗎?

        將特征命名為 X,將目標(biāo)命名為 y,這是一個(gè)好的做法嗎?

        讓我們看一個(gè)更具有意義的名稱的例子:

        import pandas as pd
        from sklearn.model_selection import train_test_split

        def load_data_and_split_into_train_test(dataset_path):
            data_frame = pd.read_csv(dataset_path)
            features = data_frame.iloc[:, :-1]
            target = data_frame.iloc[:, -1]
            features_train, features_test, target_train, target_test = train_test_split(features, target, test_size=0.2, random_state=42)
            return features_train, features_test, target_train, target_test

        這樣更容易理解?,F(xiàn)在,即使是對于不熟悉 pandas 和 train_test_split 約定的人,也非常清楚該函數(shù)正在從 dataset_path 中列出的路徑加載數(shù)據(jù),從數(shù)據(jù)幀中檢索特征和目標(biāo),然后計(jì)算訓(xùn)練集和測試集的特征和目標(biāo)。

        這些更改使代碼更易于閱讀和理解,特別是對于可能不熟悉機(jī)器學(xué)習(xí)代碼約定的人來說,其中特征大多以 X 命名,目標(biāo)以 y 命名。

        但是請不要過度使用不提供任何附加信息的命名。

        讓我們看另一個(gè)例子代碼片段:

        import pandas as pd
        from sklearn.model_selection import train_test_split

        def load_data_from_csv_and_split_into_training_and_testing_sets(dataset_path_csv):
            data_frame_from_csv = pd.read_csv(dataset_path_csv)
            features_columns_data_frame = data_frame_from_csv.iloc[:, :-1]
            target_column_data_frame = data_frame_from_csv.iloc[:, -1]
            features_columns_data_frame_for_training, features_columns_data_frame_for_testing, target_column_data_frame_for_training, target_column_data_frame_for_testing = train_test_split(features_columns_data_frame, target_column_data_frame, test_size=0.2, random_state=42)
            return features_columns_data_frame_for_training, features_columns_data_frame_for_testing, target_column_data_frame_for_training, target_column_data_frame_for_testing

        當(dāng)你看到這段代碼時(shí),你有什么感覺?

        有必要包含函數(shù)加載 CSV 嗎?以及數(shù)據(jù)集路徑是指向 CSV 文件的路徑嗎?

        這段代碼包含太多沒有提供任何額外信息的信息。它反而使讀者分心。

        因此,添加有意義的名稱是在描述性和簡潔之間取得平衡的行為。

        函數(shù)

        現(xiàn)在讓我們來看看函數(shù)。

        函數(shù)的第一個(gè)規(guī)則是它們應(yīng)該很小。函數(shù)的第二個(gè)規(guī)則是它們應(yīng)該比那更小 [1]。

        這一點(diǎn)非常重要!

        函數(shù)應(yīng)該很小,不超過 20 行。如果函數(shù)中有大塊占用大量空間的代碼,請將它們放入新的函數(shù)中。

        另一個(gè)重要原則是函數(shù)應(yīng)該做一件事。而不是更多。如果它們做了更多,請將第二個(gè)事情分離到新的函數(shù)中。

        現(xiàn)在讓我們再看一個(gè)小例子:

        import pandas as pd
        from sklearn.model_selection import train_test_split
        from sklearn.preprocessing import StandardScaler

        def load_clean_feature_engineer_and_split(data_path):
            # Load data
            df = pd.read_csv(data_path)
            
            # Clean data
            df.dropna(inplace=True)
            df = df[df['Age'] > 0]
            
            # Feature engineering
            df['AgeGroup'] = pd.cut(df['Age'], bins=[0186599], labels=['child''adult''senior'])
            df['IsAdult'] = df['Age'] > 18
            
            # Data preprocessing
            scaler = StandardScaler()
            df[['Age''Fare']] = scaler.fit_transform(df[['Age''Fare']])
            
            # Split data
            features = df.drop('Survived', axis=1)
            target = df['Survived']
            features_train, features_test, target_train, target_test = train_test_split(features, target, test_size=0.2, random_state=42)
            return features_train, features_test, target_train, target_test

        您能否已經(jīng)發(fā)現(xiàn)上述提到的規(guī)則的違規(guī)情況?

        這個(gè)函數(shù)不長,但顯然違反了一個(gè)函數(shù)應(yīng)該做一件事的規(guī)則。

        此外,注釋表明這些代碼塊可以放在一個(gè)單獨(dú)的函數(shù)中,并且可以為每個(gè)函數(shù)命名,以便更清楚地了解情況,并且不需要注釋(關(guān)于這一點(diǎn)將在下一節(jié)中討論)。

        所以,讓我們看看重構(gòu)后的例子:

        import pandas as pd
        from sklearn.model_selection import train_test_split
        from sklearn.preprocessing import StandardScaler

        def load_data(data_path):
            return pd.read_csv(data_path)

        def clean_data(df):
            df.dropna(inplace=True)
            df = df[df['Age'] > 0]
            return df

        def feature_engineering(df):
            df['AgeGroup'] = pd.cut(df['Age'], bins=[0186599], labels=['child''adult''senior'])
            df['IsAdult'] = df['Age'] > 18
            return df

        def preprocess_features(df):
            scaler = StandardScaler()
            df[['Age''Fare']] = scaler.fit_transform(df[['Age''Fare']])
            return df

        def split_data(df, target_name='Survived'):
            features = df.drop(target_name, axis=1)
            target = df[target_name]
            return train_test_split(features, target, test_size=0.2, random_state=42)

        if __name__ == "__main__":
          data_path = 'data.csv'
          df = load_data(data_path)
          df = clean_data(df)
          df = feature_engineering(df)
          df = preprocess_features(df)
          X_train, X_test, y_train, y_test = split_data(df)

        在這個(gè)重構(gòu)后的代碼片段中,每個(gè)函數(shù)只做一件事,使得閱讀代碼變得更容易。測試本身現(xiàn)在也變得更容易,因?yàn)槊總€(gè)函數(shù)都可以與其他函數(shù)隔離地進(jìn)行測試。

        即使注釋也不再需要,因?yàn)楝F(xiàn)在函數(shù)名稱就像是對自己的注釋一樣。

        但是現(xiàn)在還缺少一個(gè)部分:文檔字符串

        文檔字符串是 Python 的標(biāo)準(zhǔn),旨在提供可讀和可理解的代碼。

        每個(gè)用于生產(chǎn)代碼的函數(shù)都應(yīng)包含一個(gè)文檔字符串,描述其意圖、輸入?yún)?shù)以及有關(guān)返回值的信息。

        文檔字符串直接被諸如 Sphinx 這樣的工具使用,其目的是為代碼創(chuàng)建文檔。

        現(xiàn)在讓我們?yōu)樯厦娴拇a片段添加文檔字符串:

        import pandas as pd
        from sklearn.model_selection import train_test_split
        from sklearn.preprocessing import StandardScaler

        def load_data(data_path):
            """
            從CSV文件中加載數(shù)據(jù)到pandas DataFrame中。
            
            Args:
              data_path (str): 數(shù)據(jù)集的文件路徑。
            
            Returns:
              DataFrame: 加載的數(shù)據(jù)集。
            """

            return pd.read_csv(data_path)

        def clean_data(df):
            """
            通過刪除帶有缺失值的行并過濾掉非正年齡的行來清理DataFrame。
            
            Args:
              df (DataFrame): 輸入數(shù)據(jù)集。
            
            Returns:
              DataFrame: 清理后的數(shù)據(jù)集。
            """

            df.dropna(inplace=True)
            df = df[df['Age'] > 0]
            return df

        def feature_engineering(df):
            """
            對DataFrame進(jìn)行特征工程,包括年齡分組和成人標(biāo)識。
            
            Args:
              df (DataFrame): 輸入數(shù)據(jù)集。
            
            Returns:
              DataFrame: 添加了新特征的數(shù)據(jù)集。
            """

            df['AgeGroup'] = pd.cut(df['Age'], bins=[0186599], labels=['child''adult''senior'])
            df['IsAdult'] = df['Age'] > 18
            return df

        def preprocess_features(df):
            """
            通過使用StandardScaler對'Age'和'Fare'列進(jìn)行標(biāo)準(zhǔn)化來預(yù)處理特征。
            
            Args:
              df (DataFrame): 輸入數(shù)據(jù)集。
            
            Returns:
              DataFrame: 帶有標(biāo)準(zhǔn)化特征的數(shù)據(jù)集。
            """

            scaler = StandardScaler()
            df[['Age''Fare']] = scaler.fit_transform(df[['Age''Fare']])
            return df

        def split_data(df, target_name='Survived'):
            """
            將數(shù)據(jù)集分割為訓(xùn)練集和測試集。
            
            Args:
              df (DataFrame): 輸入數(shù)據(jù)集。
              target_name (str): 目標(biāo)變量列的名稱。
            
            Returns:
              tuple: 包含訓(xùn)練特征、測試特征、訓(xùn)練目標(biāo)和測試目標(biāo)數(shù)據(jù)集。
            """

            features = df.drop(target_name, axis=1)
            target = df[target_name]
            return train_test_split(features, target, test_size=0.2, random_state=42)

        if __name__ == "__main__":
            data_path = 'data.csv'
            df = load_data(data_path)
            df = clean_data(df)
            df = feature_engineering(df)
            df = preprocess_features(df)
            X_train, X_test, y_train, y_test = split_data(df)

        集成開發(fā)環(huán)境(IDEs)如VSCode通常提供文檔字符串的擴(kuò)展,因此只要您在函數(shù)定義下添加多行字符串,文檔字符串就會(huì)自動(dòng)添加。這有助于您快速獲得所需格式的正確文檔字符串。

        格式化

        代碼主要是閱讀的次數(shù)要比編寫的次數(shù)多。沒有人愿意閱讀格式混亂、難以理解的代碼。

        在Python中,有PEP 8風(fēng)格指南可供遵循,以使代碼更易讀。

        一些重要的格式化規(guī)則包括:

        • 使用四個(gè)空格進(jìn)行代碼縮進(jìn)
        • 將所有行限制在最多79個(gè)字符
        • 在某些情況下避免不必要的空白(即在括號內(nèi)部,尾隨逗號和右括號之間,...) 但請記住:格式化規(guī)則應(yīng)該使代碼更易讀。有時(shí),應(yīng)用其中一些規(guī)則是沒有意義的,因?yàn)槟菢拥拇a就不會(huì)更易讀。在這種情況下,請忽略其中一些規(guī)則。

        您可以使用IDE中的擴(kuò)展來支持遵循您的指南。例如,VSCode提供了幾種用于此目的的擴(kuò)展。

        您可以使用Pylint和autopep8等Python包來支持格式化您的Python腳本。

        Pylint是一個(gè)靜態(tài)代碼分析器,它會(huì)給您的代碼打分(最高10分),而autopep8可以自動(dòng)將您的代碼格式化為符合PEP8標(biāo)準(zhǔn)的形式。

        讓我們使用本文中早期代碼片段來了解一下:

        import pandas as pd
        from sklearn.model_selection import train_test_split
        from sklearn.preprocessing import StandardScaler

        def load_data(data_path):
            return pd.read_csv(data_path)

        def clean_data(df):
            df.dropna(inplace=True)
            df = df[df['Age'] > 0]
            return df

        def feature_engineering(df):
            df['AgeGroup'] = pd.cut(df['Age'], bins=[0186599], labels=['child''adult''senior'])
            df['IsAdult'] = df['Age'] > 18
            return df

        def preprocess_features(df):
            scaler = StandardScaler()
            df[['Age''Fare']] = scaler.fit_transform(df[['Age''Fare']])
            return df

        def split_data(df, target_name='Survived'):
            features = df.drop(target_name, axis=1)
            target = df[target_name]
            return train_test_split(features, target, test_size=0.2, random_state=42)

        if __name__ == "__main__":
          data_path = 'data.csv'
          df = load_data(data_path)
          df = clean_data(df)
          df = feature_engineering(df)
          df = preprocess_features(df)
          X_train, X_test, y_train, y_test = split_data(df)

        現(xiàn)在將其保存到一個(gè)名為train.py的文件中,并運(yùn)行Pylint來檢查我們的代碼段的分?jǐn)?shù):

        pylint train.py

        這將產(chǎn)生以下輸出:

        ************* Module train
        train.py:29:0: W0311: Bad indentation. Found 2 spaces, expected 4 (bad-indentation)
        train.py:30:0: W0311: Bad indentation. Found 2 spaces, expected 4 (bad-indentation)
        train.py:31:0: W0311: Bad indentation. Found 2 spaces, expected 4 (bad-indentation)
        train.py:32:0: W0311: Bad indentation. Found 2 spaces, expected 4 (bad-indentation)
        train.py:33:0: W0311: Bad indentation. Found 2 spaces, expected 4 (bad-indentation)
        train.py:34:0: C0304: Final newline missing (missing-final-newline)
        train.py:34:0: W0311: Bad indentation. Found 2 spaces, expected 4 (bad-indentation)
        train.py:1:0: C0114: Missing module docstring (missing-module-docstring)
        train.py:5:0: C0116: Missing function or method docstring (missing-function-docstring)
        train.py:5:14: W0621: Redefining name 'data_path' from outer scope (line 29) (redefined-outer-name)
        train.py:8:0: C0116: Missing function or method docstring (missing-function-docstring)
        train.py:8:15: W0621: Redefining name 'df' from outer scope (line 30) (redefined-outer-name)
        train.py:13:0: C0116: Missing function or method docstring (missing-function-docstring)
        train.py:13:24: W0621: Redefining name 'df' from outer scope (line 30) (redefined-outer-name)
        train.py:18:0: C0116: Missing function or method docstring (missing-function-docstring)
        train.py:18:24: W0621: Redefining name 'df' from outer scope (line 30) (redefined-outer-name)
        train.py:23:0: C0116: Missing function or method docstring (missing-function-docstring)
        train.py:23:15: W0621: Redefining name 'df' from outer scope (line 30) (redefined-outer-name)
        train.py:29:2: C0103: Constant name "data_path" doesn't conform to UPPER_CASE naming style (invalid-name)

        ------------------------------------------------------------------
        Your code has been rated at 3.21/10

        哇,只有3.21分中的得分。

        您現(xiàn)在可以手動(dòng)修復(fù)這些問題,然后重新運(yùn)行它?;蛘吣梢允褂胊utopep8軟件包自動(dòng)解決其中一些問題。

        讓我們采取第二種方法:

        autopep8 --in-place --aggressive --aggressive train.py

        現(xiàn)在train.py腳本如下所示:

        import pandas as pd
        from sklearn.model_selection import train_test_split
        from sklearn.preprocessing import StandardScaler


        def load_data(data_path):
            return pd.read_csv(data_path)


        def clean

        _data(df):

            df.dropna(inplace=True)
            df = df[df['Age'] > 0]
            return df


        def feature_engineering(df):
            df['AgeGroup'] = pd.cut(
                df['Age'], bins=[
                    0186599], labels=[
                    'child''adult''senior'])
            df['IsAdult'] = df['Age'] > 18
            return df


        def preprocess_features(df):
            scaler = StandardScaler()
            df[['Age''Fare']] = scaler.fit_transform(df[['Age''Fare']])
            return df


        def split_data(df, target_name='Survived'):
            features = df.drop(target_name, axis=1)
            target = df[target_name]
            return train_test_split(features, target, test_size=0.2, random_state=42)


        if __name__ == "__main__":
            data_path = 'data.csv'
            df = load_data(data_path)
            df = clean_data(df)
            df = feature_engineering(df)
            df = preprocess_features(df)
            X_train, X_test, y_train, y_test = split_data(df)

        再次運(yùn)行Pylint,我們得到了10分的分?jǐn)?shù):

        pylint train.py

        太棒了!

        這真正展示了Pylint在使您的代碼更清晰并迅速遵循PEP8標(biāo)準(zhǔn)方面的作用。

        錯(cuò)誤處理

        錯(cuò)誤處理確保您的代碼能夠處理意外情況,而不會(huì)崩潰或產(chǎn)生不正確的結(jié)果。

        想象一下,您部署了一個(gè)模型在一個(gè)API后面,用戶可以向該部署模型發(fā)送數(shù)據(jù)。然而,用戶可能會(huì)向該模型發(fā)送錯(cuò)誤的數(shù)據(jù),因此應(yīng)用程序可能會(huì)崩潰,這對用戶體驗(yàn)來說不是一個(gè)好印象。他們很可能會(huì)責(zé)怪您的應(yīng)用程序,并聲稱它開發(fā)得不好。

        如果用戶收到一個(gè)特定的錯(cuò)誤代碼和一個(gè)清楚告訴他們出了什么問題的消息,那就更好了。

        這就是Python異常發(fā)揮作用的地方。

        假設(shè)用戶可以上傳一個(gè)CSV文件到您的應(yīng)用程序,將其加載到pandas數(shù)據(jù)框中,然后將其轉(zhuǎn)發(fā)給您的模型進(jìn)行預(yù)測。

        那么您會(huì)有一個(gè)類似以下的函數(shù):

        import pandas as pd

        def load_data(data_path):
            """
            從指定的CSV文件中加載數(shù)據(jù)集到pandas DataFrame中。

            參數(shù):
                data_path (str): 數(shù)據(jù)集的文件路徑。

            返回:
                DataFrame: 加載的數(shù)據(jù)集。
            """

            return pd.read_csv(data_path)

        到目前為止,一切都很順利。

        但是當(dāng)用戶沒有提供CSV文件時(shí)會(huì)發(fā)生什么呢?

        您的程序?qū)⒈罎?,并顯示以下錯(cuò)誤消息:

        FileNotFoundError: [Errno 2] No such file or directory: 'data.csv'

        由于您正在運(yùn)行一個(gè)API,它將簡單地向用戶返回一個(gè)HTTP 500代碼,告訴他有一個(gè)“內(nèi)部服務(wù)器錯(cuò)誤”。

        用戶可能會(huì)因此責(zé)怪您的應(yīng)用程序,因?yàn)樗麩o法看到他對該錯(cuò)誤負(fù)責(zé)。

        有什么更好的處理方法嗎?

        添加一個(gè)try-except塊并捕獲FileNotFoundError來正確處理該情況:

        import pandas as pd
        import logging

        def load_data(data_path):
            """
            從指定的CSV文件中加載數(shù)據(jù)集到pandas DataFrame中。

            參數(shù):
                data_path (str): 數(shù)據(jù)集的文件路徑。

            返回:
                DataFrame: 加載的數(shù)據(jù)集。
            """

            try:
                return pd.read_csv(data_path)
            except FileNotFoundError:
                logging.error("路徑 %s 處的文件不存在。請確保您已正確上傳文件。", data_path)

        但現(xiàn)在我們只記錄了該錯(cuò)誤消息。最好定義一個(gè)自定義異常,然后在我們的API中處理該異常,以向用戶返回特定的錯(cuò)誤代碼:

        import pandas as pd
        import logging

        class DataLoadError(Exception):
            """當(dāng)數(shù)據(jù)無法加載時(shí)引發(fā)的異常。"""
            def __init__(self, message="數(shù)據(jù)無法加載"):
                self.message = message
                super().__init__(self.message)

        def load_data(data_path):
            """
            從指定的CSV文件中加載數(shù)據(jù)集到pandas DataFrame中。

            參數(shù):
                data_path (str): 數(shù)據(jù)集的文件路徑。

            返回:
                DataFrame: 加載的數(shù)據(jù)集。
            """

            try:
                return pd.read_csv(data_path)
            except FileNotFoundError:
                logging.error("路徑 %s 處的文件不存在。請確保您已正確上傳文件。", data_path)
                raise DataLoadError(f"路徑 {data_path} 處的文件不存在。請確保您已正確上傳文件。")

        然后,在您的API的主要函數(shù)中:

        try:
            df = load_data('path/to/data.csv')
            # 進(jìn)行進(jìn)一步的處理和模型預(yù)測
        except DataLoadError as e:
            # 向用戶返回一個(gè)包含錯(cuò)誤消息的響應(yīng)
            # 例如:return Response({"error": str(e)}, status=400)

        現(xiàn)在,用戶將收到一個(gè)錯(cuò)誤代碼400(錯(cuò)誤的請求),并帶有一個(gè)告訴他們出了什么問題的錯(cuò)誤消息。

        他現(xiàn)在知道該怎么做了,不會(huì)再責(zé)怪您的程序無法正常工作了。

        面向?qū)ο缶幊?/span>

        面向?qū)ο缶幊淌且环N編程范式,它提供了一種將屬性和行為捆綁到單個(gè)對象中的方法。

        主要優(yōu)點(diǎn):

        • 對象通過封裝隱藏?cái)?shù)據(jù)。
        • 通過繼承可以重用代碼。
        • 可以將復(fù)雜問題分解為小對象,并且開發(fā)人員可以一次專注于一個(gè)對象。
        • 提高可讀性。
        • 還有許多其他優(yōu)點(diǎn)。我強(qiáng)調(diào)了最重要的幾個(gè)(至少對我來說是這樣)。

        現(xiàn)在讓我們看一個(gè)小例子,其中創(chuàng)建了一個(gè)名為“TrainingPipeline”的類,并帶有一些基本函數(shù):

        from abc import ABC, abstractmethod

        class TrainingPipeline(ABC):
            def __init__(self, data_path, target_name):
                """
                初始化TrainingPipeline。

                Args:
                    data_path (str): 數(shù)據(jù)集的文件路徑。
                    target_name (str): 目標(biāo)列的名稱。
                """

                self.data_path = data_path
                self.target_name = target_name
                self.data = None
                self.X_train = None
                self.X_test = None
                self.y_train = None
                self.y_test = None

            @abstractmethod
            def load_data(self):
                """從數(shù)據(jù)路徑加載數(shù)據(jù)集。"""
                pass

            @abstractmethod
            def clean_data(self):
                """清理數(shù)據(jù)。"""
                pass

            @abstractmethod
            def feature_engineering(self):
                """執(zhí)行特征工程。"""
                pass

            @abstractmethod
            def preprocess_features(self):
                """預(yù)處理特征。"""
                pass

            @abstractmethod
            def split_data(self):
                """將數(shù)據(jù)拆分為訓(xùn)練集和測試集。"""
                pass

            def run(self):
                """運(yùn)行訓(xùn)練管道。"""
                self.load_data()
                self.clean_data()
                self.feature_engineering()
                self.preprocess_features()
                self.split_data()

        這是一個(gè)抽象基類,僅定義了派生自基類的類必須實(shí)現(xiàn)的抽象方法。

        這在定義所有子類都必須遵循的藍(lán)圖或模板時(shí)非常有用。

        然后,一個(gè)示例子類可能如下所示:

        import pandas as pd
        from sklearn.preprocessing import StandardScaler

        class ChurnPredictionTrainPipeline(TrainingPipeline):
            def load_data(self):
                """從數(shù)據(jù)路徑加載數(shù)據(jù)集。"""
                self.data = pd.read_csv(self.data_path)

            def clean_data(self):
                """清理數(shù)據(jù)。"""
                self.data.dropna(inplace=True)

            def feature_engineering(self):
                """執(zhí)行特征工程。"""
                categorical_cols = self.data.select_dtypes(include=['object''category']).columns
                self.data = pd.get_dummies(self.data, columns=categorical_cols, drop_first=True)

            def preprocess_features(self):
                """預(yù)處理特征。"""
                numerical_cols = self.data.select_dtypes(include=['int64''float64']).columns
                scaler = StandardScaler()
                self.data[numerical_cols] = scaler.fit_transform(self.data[numerical_cols])

            def split_data(self):
                """將數(shù)據(jù)拆分為訓(xùn)練集和測試集。"""
                features = self.data.drop(self.target_name, axis=1)
                target = self.data[self.target_name]
                self.features_train, self.features_test, self.target_train, self.target_test = train_test_split(features, target, test_size=0.2, random_state=42)

        這樣做的好處是,您可以構(gòu)建一個(gè)應(yīng)用程序,該應(yīng)用程序自動(dòng)調(diào)用訓(xùn)練管道的方法,并且可以創(chuàng)建訓(xùn)練管道的不同類。它們始終兼容,并且必須遵循抽象基類中定義的藍(lán)圖。

        測試

        這一章是最重要的章節(jié)之一。

        有了測試,可以決定整個(gè)項(xiàng)目的成功或失敗。

        創(chuàng)建沒有測試的代碼更快,因?yàn)楫?dāng)您還需要為每個(gè)函數(shù)編寫單元測試時(shí),這似乎是一種“浪費(fèi)時(shí)間”的行為。編寫單元測試的代碼很快就會(huì)超過函數(shù)的代碼量。

        但請相信我,這是值得的努力!

        如果您不快速添加單元測試,就會(huì)感到痛苦。有時(shí),并不是在一開始就會(huì)感到痛苦。

        但是當(dāng)您的代碼庫增長并且添加更多功能時(shí),您肯定會(huì)感到痛苦。突然之間,調(diào)整一個(gè)函數(shù)的代碼可能會(huì)導(dǎo)致其他函數(shù)失敗。新的發(fā)布需要大量的緊急修復(fù)??蛻舾械綈阑?。團(tuán)隊(duì)中的開發(fā)人員害怕適應(yīng)代碼庫中的任何東西,導(dǎo)致發(fā)布新功能的速度非常緩慢。

        因此,無論何時(shí)您在編寫后續(xù)需要投入生產(chǎn)的代碼時(shí),請始終遵循測試驅(qū)動(dòng)開發(fā)(TDD)原則!

        在Python中,可以使用類似unittest或pytest的庫來測試您的函數(shù)。

        我個(gè)人更喜歡pytest。

        您可以在這篇文章中了解有關(guān)Python測試的更多信息。該文章還側(cè)重于集成測試,這是測試的另一個(gè)重要方面,以確保您的系統(tǒng)端到端正常工作。

        讓我們再次看一下之前章節(jié)中的ChurnPredictionTrainPipeline類:

        import pandas as pd
        from sklearn.preprocessing import StandardScaler

        class ChurnPredictionTrainPipeline(TrainingPipeline):
            def load_data(self):
                """Load dataset from data path."""
                self.data = pd.read_csv(self.data_path)

            ...

        現(xiàn)在,讓我們使用pytest為加載數(shù)據(jù)添加單元測試:

        import os
        import shutil
        import logging
        from unittest.mock import patch
        import joblib
        import pytest
        import numpy as np
        import pandas as pd
        from sklearn.datasets import make_classification
        from sklearn.model_selection import train_test_split
        from sklearn.ensemble import RandomForestClassifier
        from sklearn.linear_model import LogisticRegression
        from churn_library import ChurnPredictor

        @pytest.fixture
        def path():
            """
            Return the path to the test csv data file.
            """

            return r"./data/bank_data.csv"

        def test_import_data_returns_dataframe(path):
            """
            Test that import data can load the CSV file into a pandas dataframe.
            """

            churn_predictor = ChurnPredictionTrainPipeline(path, "Churn")
            churn_predictor.load_data()

            assert isinstance(churn_predictor.data, pd.DataFrame)


        def test_import_data_raises_exception():
            """
            Test that exception of "FileNotFoundError" gets raised in case the CSV
            file does not exist.
            """

            with pytest.raises(FileNotFoundError):
                churn_predictor = ChurnPredictionTrainPipeline("non_existent_file.csv",
                                                               "Churn")
                churn_predictor.load_data()


        def test_import_data_reads_csv(path):
            """
            Test that the pandas.read_csv function gets called.
            """

            with patch("pandas.read_csv"as mock_csv:
                churn_predictor = ChurnPredictionTrainPipeline(path, "Churn")
                churn_predictor.load_data()
                mock_csv.assert_called_once_with(path)

        這些單元測試是:

        • 測試CSV文件是否可以加載到pandas數(shù)據(jù)框中。
        • 測試在CSV文件不存在的情況下是否會(huì)引發(fā)FileNotFoundError異常。
        • 測試pandas的“read_csv”函數(shù)是否被調(diào)用。

        這個(gè)過程并不完全是TDD,因?yàn)樵谔砑訂卧獪y試之前,我已經(jīng)開發(fā)了代碼。但在理想情況下,您應(yīng)該在實(shí)現(xiàn)load_data函數(shù)之前甚至編寫這些單元測試。

        系統(tǒng)

        你會(huì)一次性建造一座城市嗎?很可能不會(huì)。

        對軟件也是一樣的。

        構(gòu)建一個(gè)干凈的系統(tǒng)就是將其拆分為更小的組件。每個(gè)組件都使用清晰的代碼原則構(gòu)建,并經(jīng)過良好的測試。

        這一章中最重要的部分是關(guān)注關(guān)注點(diǎn)的分離:

        將啟動(dòng)過程與運(yùn)行時(shí)邏輯分開,構(gòu)造依賴項(xiàng)。在主函數(shù)中初始化所有對象,并將它們插入到依賴它們的類中(依賴注入)。這種方法有助于逐步構(gòu)建系統(tǒng),使其易于擴(kuò)展和添加更多功能。

        并發(fā)

        并發(fā)有時(shí)對于通過巧妙地在任務(wù)之間跳轉(zhuǎn)來加快進(jìn)程速度是有幫助的。

        并發(fā)也可以被看作是一種解耦策略,因?yàn)椴煌牟糠中枰?dú)立運(yùn)行,以便并發(fā)可以提高總體運(yùn)行時(shí)間。

        并發(fā)也會(huì)帶來一些開銷,并使程序更加復(fù)雜,因此明智地決定是否值得投入這樣的工作。

        例如,您需要處理共享資源和同步訪問。

        在Python中,您可以利用 asyncio 模塊。在這篇文章中閱讀更多關(guān)于Python中并發(fā)的內(nèi)容。

        重構(gòu)

        重構(gòu)您的代碼可以提高可讀性和可維護(hù)性。

        總是從簡單開始,甚至是從丑陋的代碼開始。讓它運(yùn)行起來。然后進(jìn)行重構(gòu)。消除重復(fù),改進(jìn)命名,并降低復(fù)雜性。

        但請記住,在開始重構(gòu)之前一定要有您的測試。這樣可以確保在重構(gòu)時(shí)不會(huì)破壞東西。

        您應(yīng)該重構(gòu)您的代碼使其更加清晰。太多的開發(fā)人員,包括我開始時(shí),都有這樣的想法,即我的代碼現(xiàn)在可以運(yùn)行,所以我推送它然后繼續(xù)下一個(gè)任務(wù)。

        擺脫這種思維方式!否則,隨著代碼庫的增長,您將會(huì)遇到很多問題,現(xiàn)在您必須處理難以維護(hù)的丑陋代碼。

        結(jié)論

        編寫清潔的代碼是一門藝術(shù)。它需要紀(jì)律性,并且經(jīng)常不夠。但它對于軟件項(xiàng)目的成功非常重要。

        作為一名數(shù)據(jù)科學(xué)家,您往往不會(huì)編寫干凈的代碼,因?yàn)槟饕獙W⒂趯ふ液玫哪P筒⒃贘upyter Notebooks中運(yùn)行代碼以獲得您所追求的指標(biāo)。

        當(dāng)我主要從事數(shù)據(jù)科學(xué)項(xiàng)目時(shí),我也從不關(guān)心編寫干凈的代碼。

        但是,數(shù)據(jù)科學(xué)家編寫干凈的代碼對于確保模型更快地投入生產(chǎn)也是至關(guān)重要的。

        - EOF -

        作者簡介


        城哥,公眾號9年博主,一線互聯(lián)網(wǎng)工作10年、公司校招和社招技術(shù)面試官,主導(dǎo)多個(gè)公司級實(shí)戰(zhàn)項(xiàng)目(Python、數(shù)據(jù)分析挖掘、算法、AI平臺、大模型等)。


        關(guān)注我,陪你一起成長,遇見更好的自己。

        星球服務(wù)


        會(huì)不定期發(fā)放知識星球優(yōu)惠券,加入星球前可以添加城哥微信:dkl88191,咨詢優(yōu)惠券問題。

        加入知識星球,可以享受7大福利與服務(wù):免費(fèi)獲取海量技術(shù)資料、向我 1 對 1 技術(shù)咨詢、求職指導(dǎo),簡歷優(yōu)化、歷史文章答疑(源碼+數(shù)據(jù))、綜合&專業(yè)技術(shù)交流社群、大模型技術(shù)分享、定制專屬學(xué)習(xí)路線,幫你快速成長、告別迷茫。



        原創(chuàng)不易,技術(shù)學(xué)習(xí)資料如下,星球成員可免費(fèi)獲取,非星球成員,添加城哥微信:dkl88191,請城哥喝杯星巴克。







        瀏覽 40
        點(diǎn)贊
        評論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        評論
        圖片
        表情
        推薦
        點(diǎn)贊
        評論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            肏屄电影网 | 羞羞漫画黄漫免费观看 | 老女人黄片| 成人免费无码淫片在线观看免费 | xxxx农村野外hd | 我要操操| 深夜福利在线看 | 色色草视频 | 精品夜夜澡人妻无码AV | 一级无码视频黄片免费的 |