<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

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

          共 31314字,需瀏覽 63分鐘

           ·

          2024-04-11 15:13

          推薦關(guān)注↓


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

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

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

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

          有意義的名稱

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

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

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

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

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

          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 是什么。但是對(duì)于新手來(lái)說(shuō)呢?

          將 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

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

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

          但是請(qǐng)不要過(guò)度使用不提供任何附加信息的命名。

          讓我們看另一個(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í),你有什么感覺(jué)?

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

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

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

          函數(shù)

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

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

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

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

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

          現(xiàn)在讓我們?cè)倏匆粋€(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ù)不長(zhǎng),但顯然違反了一個(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ù)只做一件事,使得閱讀代碼變得更容易。測(cè)試本身現(xiàn)在也變得更容易,因?yàn)槊總€(gè)函數(shù)都可以與其他函數(shù)隔離地進(jìn)行測(cè)試。

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

          但是現(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):
              """
              通過(guò)刪除帶有缺失值的行并過(guò)濾掉非正年齡的行來(lái)清理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):
              """
              對(duì)DataFrame進(jìn)行特征工程,包括年齡分組和成人標(biāo)識(shí)。
              
              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):
              """
              通過(guò)使用StandardScaler對(duì)'Age'和'Fare'列進(jìn)行標(biāo)準(zhǔn)化來(lái)預(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)練集和測(cè)試集。
              
              Args:
                df (DataFrame): 輸入數(shù)據(jù)集。
                target_name (str): 目標(biāo)變量列的名稱。
              
              Returns:
                tuple: 包含訓(xùn)練特征、測(cè)試特征、訓(xùn)練目標(biāo)和測(cè)試目標(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)

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

          格式化

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

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

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

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

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

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

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

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

          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來(lái)檢查我們的代碼段的分?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ù)這些問(wèn)題,然后重新運(yùn)行它。或者您可以使用autopep8軟件包自動(dòng)解決其中一些問(wèn)題。

          讓我們采取第二種方法:

          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ì)崩潰,這對(duì)用戶體驗(yàn)來(lái)說(shuō)不是一個(gè)好印象。他們很可能會(huì)責(zé)怪您的應(yīng)用程序,并聲稱它開(kāi)發(fā)得不好。

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

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

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

          那么您會(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)用戶沒(méi)有提供CSV文件時(shí)會(huì)發(fā)生什么呢?

          您的程序?qū)⒈罎ⅲ@示以下錯(cuò)誤消息:

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

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

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

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

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

          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 處的文件不存在。請(qǐng)確保您已正確上傳文件。", data_path)

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

          import pandas as pd
          import logging

          class DataLoadError(Exception):
              """當(dāng)數(shù)據(jù)無(wú)法加載時(shí)引發(fā)的異常。"""
              def __init__(self, message="數(shù)據(jù)無(wú)法加載"):
                  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 處的文件不存在。請(qǐng)確保您已正確上傳文件。", data_path)
                  raise DataLoadError(f"路徑 {data_path} 處的文件不存在。請(qǐng)確保您已正確上傳文件。")

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

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

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

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

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

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

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

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

          現(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)練集和測(cè)試集。"""
                  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)練集和測(cè)試集。"""
                  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)圖。

          測(cè)試

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

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

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

          但請(qǐng)相信我,這是值得的努力!

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

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

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

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

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

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

          讓我們?cè)俅慰匆幌轮罢鹿?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ù)添加單元測(cè)試:

          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)

          這些單元測(cè)試是:

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

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

          系統(tǒng)

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

          對(duì)軟件也是一樣的。

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

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

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

          并發(fā)

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

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

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

          例如,您需要處理共享資源和同步訪問(wèn)。

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

          重構(gòu)

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

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

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

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

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

          結(jié)論

          編寫清潔的代碼是一門藝術(shù)。它需要紀(jì)律性,并且經(jīng)常不夠。但它對(duì)于軟件項(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é)家編寫干凈的代碼對(duì)于確保模型更快地投入生產(chǎn)也是至關(guān)重要的。

          - EOF -

          作者簡(jiǎn)介


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


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

          星球服務(wù)


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

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



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







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

          手機(jī)掃一掃分享

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

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  黄色毛片学生妹免费看视频 | 亚洲无码在线观看视频 | 国产日韩视频在线观看 | 激情操逼 | 欧美日韩亚洲中文字幕 |