Py学习  »  Python

Pandas和Python的技巧

磐创AI • 1 年前 • 235 次点击  

动机

内容分为两个主要部分:

  • Pandas技巧。

  • Python技巧。

如果你更喜欢视频形式,你可以在这个YouTube频道上观看有关这些技巧的系列视频,以获得更多的互动性。每个视频一次涵盖大约两到三个技巧。

https://youtu.be/pvNvgW0USZU

Pandas技巧和窍门

本节提供了所有技巧的列表

1.从多列中创建新列的简单算术运算

执行诸如将两个列的和作为新列创建之类的简单算术任务可以很简单。

🤔 但是,如果你想要实现更复杂的函数并将其用作列创建背后的逻辑,情况可能会变得有些棘手。

猜猜看…

✅ 𝙖𝙥𝙥𝙡𝙮和𝙡𝙖𝙢𝙗𝙙𝙖可以帮助你轻松地应用任何逻辑到你的列,使用以下格式:

𝙙𝙛[𝙣𝙚𝙬_𝙘𝙤𝙡] = 𝙙𝙛.𝙖𝙥𝙥𝙡𝙮(𝙡𝙖𝙢𝙗𝙙𝙖 𝙧𝙤𝙬: 𝙛𝙪𝙣𝙘(𝙧𝙤𝙬), 𝙖𝙭𝙞𝙨=1) 

其中:

➡ 𝙙𝙛是你的数据框。

➡ 𝙧𝙤𝙬将对应你数据框中的每一行。

➡ 𝙛𝙪𝙣𝙘是你要应用于数据框的函数。

➡ 𝙖𝙭𝙞𝙨=1将函数应用于数据框中的每一行。

💡 下面是一个示例。

import pandas as pd

# Create the dataframe
candidates= {
    'Name':["Aida","Mamadou","Ismael","Aicha","Fatou""Khalil"],
    'Degree':['Master','Master','Bachelor'"PhD""Master""PhD"],
    'From':["Abidjan","Dakar","Bamako""Abidjan","Konakry""Lomé"],
    'Years_exp' : [230543],
    'From_office(min)': [12095758010034]
          }
candidates_df = pd.DataFrame(candidates)

"""
----------------My custom function-------------------
"""
 
def candidate_info(row):

  # Select columns of interest 
  name = row.Name 
  is_from = row.From
  year_exp = row.Years_exp
  degree = row.Degree
  from_office = row["From_office(min)"]

  # Generate the description from previous variables
  info = f"""{name} from {is_from} holds a {degree} degree 
              with {year_exp} year(s) experience 
              and lives {from_office} from the office"""


  return info

"""
-------Application of the function to the data ------
"""

candidates_df["Description"] = candidates_df.apply(lambda row: candidate_info(row), axis=1)

candidate_info函数将每个候选人的信息组合起来,创建一个关于该候选人的单独描述列。

2.将分类数据转换为数值数据

这个过程主要可以发生在特征工程阶段。它的一些好处包括:

  • 识别数据中的异常值、无效值和缺失值。

  • 通过创建更强大的模型,减少过拟合的机会。

➡根据你的需求,使用Pandas中的这两个函数。下面的图像提供了一些示例。

1️⃣ .𝙘𝙪𝙩() 用于明确定义你的箱边界。

𝙎𝙘𝙚𝙣𝙖𝙧𝙞𝙤

根据候选人的经验年限将其分类为专业知识,其中:

  • 入门级:0-1年

  • 中级:2-3年

  • 高级:4-5年

seniority = ['Entry level''Mid level''Senior level']
seniority_bins = [0 135]
candidates_df['Seniority'] = pd.cut(candidates_df['Years_exp'],
                                    bins=seniority_bins, 
                                    labels=seniority, 
                                    include_lowest=True)

candidates_df

2️⃣ .𝙦𝙘𝙪𝙩() 将数据分成等大小的箱。它使用数据分布的底层百分位数,而不是箱的边界。

𝙎𝙘𝙚𝙣𝙖𝙧𝙞𝙤:将候选人的通勤时间分类为𝙜𝙤𝙤𝙙、𝙖𝙘𝙘𝙚𝙥𝙩𝙖𝙗𝙡𝙚或𝙩𝙤𝙤 𝙡𝙤𝙣𝙜。

commute_time_labels = ["good""acceptable""too long"]
candidates_df["Commute_level"] = pd.qcut(
                                candidates_df["From_office(min)"],
                                q = 3
                                labels=commute_time_labels
                              )
candidates_df

注意💡

使用.𝙘𝙪𝙩()时:箱的数量=标签的数量+1。

使用.𝙦𝙘𝙪𝙩()时:箱的数量=标签的数量。

对于.𝙘𝙪𝙩():设置𝙞𝙣𝙘𝙡𝙪𝙙𝙚_𝙡𝙤𝙬𝙚𝙨𝙩=𝙏𝙧𝙪𝙚,否则最低值将被转换为NaN。

3.根据列值选择Pandas数据框中的行

➡ 使用.𝙦𝙪𝙚𝙧𝙮()函数,并指定过滤条件。

➡ 过滤表达式可以包含任何操作符(、==、!= 等)。

➡ 使用 @ 符号在表达式中使用变量。

# Get all the candidates with a Master degree
ms_candidates = candidates_df.query("Degree == 'Master'")

# Get non bachelor candidates
no_bs_candidates = candidates_df.query("Degree != 'Bachelor'")

# Get values from list
list_locations = ["Abidjan""Dakar"]
candiates = candidates_df.query("From in @list_locations")

4.处理压缩文件

有时,直接在本地磁盘上读取和写入.zip文件而不解压它们可能是有效的。下面是一个示例。

import pandas as pd

"""
------------ READ ZIP FILES -----------
"""

# Case 1: read a single zip file 
candidate_df_unzip = pd.read_csv('candidates.csv.zip', compression='zip')

# Case 2: read a file from a folder
from zipfile import ZipFile

# Read the file from a zip folder
sales_df = pd.read_csv(ZipFile("data.zip").open('data/sales_df.csv'))


"""
------------ WRITE ZIP FILES -----------
"""

# Read data from internet
url = "https://raw.githubusercontent.com/keitazoumana/Fastapi-tutorial/master/data/spam.csv"
spam_data = pd.read_csv(url, encoding="ISO-8859-1")

# Save it as a zip file
spam_data.to_csv("spam.csv.zip", compression="zip")

# Check the files sizes
from os import path
path.getsize('spam.csv') / path.getsize('spam.csv.zip')

5.选择带有特定特征列的数据子集

你可以使用 select_dtypes 函数。它有两个主要参数:include 和 exclude。

  • df.select_dtypes(include=['type_1', 'type_2', …, 'type_n']) 表示我想要包含列 type_1、type_2、…、type_n 的数据子集。

  • df.select_dtypes(exclude=['type_1', 'type_2', …, 'type_n']) 表示我想要排除列 type_1、type_2、…、type_n 的数据子集。

✨ 下面是一个示例。

# Import pandas library
import pandas as pd

# Read my dataset
candidates_df = pd.read_csv("./data/candidates_data.csv")

# Check the data columns' types
candidates_df.dtypes

# Only select columns of type "object" & "datetime"
candidates_df.select_dtypes(include = ["object""datetime64"])

# Exclude columns of type "datetime" & "int"
candidates_df.select_dtypes(exclude = ["int64""datetime64"])

6.从Pandas数据框中移除注释

假设想要清理这个数据(candidates.csv),即从申请日期列中移除注释。在加载 Pandas 数据框时,可以通过在参数 commment 中进行如下设置来实现:

df_read_csv = pd.read_csv(path_to_data, comment='symbol')

在我们的情况下,comment='#',但根据实际情况,它可以是任何其他字符(|、/ 等)。以下是第一个示例的说明。

✋🏽 等等,如果想要为这些注释创建一个新列,同时还从申请日期列中移除它们怎么办?以下是第二个示例的说明。

# Import pandas library
import pandas as pd

# Read my messy dataset
messy_df = pd.read_csv("./data/candidates_data.csv")

# FIRST SCENARIO -> REMOVE COMMENTS
clean_df = pd.read_csv("./data/candidates_data.csv", comment='#')

# SECOND SCENARIO -> CREATE NEW COLUMN FOR COMMENTS
messy_df[['application_date''comment']] = messy_df['application_date'].str.split('#'1, expand=True)

7.在控制台中以表格格式打印Pandas数据框

❌ 不,将 print() 函数应用于 Pandas 数据框并不总是会输出易于阅读的结果,特别是对于具有多列的数据框。

✅ 如果你想要获得一个漂亮的适合控制台的表格输出,请使用 .to_string() 函数,如下所示。

# Import pandas library
import pandas as pd

data_URL = "https://raw.githubusercontent.com/keitazoumana/Experimentation-Data/main/vgsales.csv" 

# Read your dataframe
video_game_data = pd.read_csv(data_URL)

"""
Printing without to_string() function
"""

print(video_game_data.head())

"""
Printing with to_string() function
"""

print(video_game_data.head().to_string())

8.在Pandas中突出显示数据点

将颜色应用于 Pandas 数据框可以是强调某些数据点以进行快速分析的一种好方法。

✅ 这就是 pandas.style 模块派上用场的地方。它具有许多功能,但不限于以下内容:

✨ df.style.highlight_max() 将颜色分配给每列的最大值。

✨ df.style.highlight_min() 将颜色分配给每列的最小值。

✨ df.style.apply(my_custom_function) 将你自定义的函数应用于数据框。

import pandas as pd

my_info = {
    "Salary": [100000.295000.9103000.265984.1150987.08], 
    "Height": [6.55.25.596.7 6.92], 
    "weight": [185.23105.12110.3190.12200.59]      
}
my_data = pd.DataFrame(my_info)

"""
Function to highlight min and max
"""


def highlight_min_max(data_frame, min_color, max_color):

  # This first line create a styler object
  final_data = data_frame.style.highlight_max(color = max_color)

  # On this second line, no need to use ".style"
  final_data = final_data.highlight_min(color = min_color)

  return final_data

# Function to apply ORANGE to min and GREEN to max
highlight_min_max(my_data, min_color='orange', max_color='green')


"""
Custom function: apply RED or GREEN whether data is below or above the mean. 
"""

def highlight_values(data_row):
  low_value_color = "background-color:#C4606B  ; color: white;"
  high_value_color = "background-color: #C4DE6B; color: white;"   
  filter = data_row 
  return [low_value_color if low_value else high_value_color for low_value in filter]

# Application of my custom function to only 'Height' & 'weight'
my_data.style.apply(highlight_values, subset=['Height''weight'])

9.减少数据中的小数位数

有时,数据集中非常长的小数值并不提供重要信息,并且可能会让人感到困扰 🤯。

因此,你可能希望将数据转换为大约2到3位小数,以便简化分析。

✅ 你可以使用 pandas.DataFrame.round() 函数来执行此操作,如下所示。

long_decimals_info = {
    "Salary": [100000.2340000095000.900300103000.230053565984.14000450150987.080345], 
    "Height": [6.5010505.2700005.59000010506.7300010506.92100050], 
    "weight": [185.23000059105.1200099110.350003190.12000000200.59000000]      
}

long_decimals_df = pd.DataFrame(long_decimals_info)

"""
Format the data with 2 decimal places
"""

fewer_decimals_df = long_decimals_df.round(decimals=2)
fewer_decimals_df

10.替换数据框中的某些值

你可能希望替换数据框中的某些信息,以使其尽可能保持最新状态。

✅ 你可以使用 Pandas 的 DataFrame.replace() 函数来实现,如下所示。

import pandas as pd
import numpy as np

candidates_info = {
    'Full_Name':["Aida Kone","Mamadou Diop","Ismael Camara","Aicha Konate",
                 "Fanta Koumare""Khalil Cisse"],
    'degree':['Master','MS','Bachelor'"PhD""Masters", np.nan],
    'From':[np.nan,"Dakar","Bamako""Abidjan","Konakry""Lomé"],
    'Age':[23,26,19, np.nan,25, np.nan],
          }

candidates_df = pd.DataFrame(candidates_info) 

"""
Replace Masters, Master by MS
"""

degrees_to_replace = ["Master""Masters"]
candidates_df.replace(to_replace = degrees_to_replace, value = "MS", inplace=True)

"""
Replace all the NaN by "Missing"
"""

candidates_df.replace(to_replace=np.nan, value = "Missing", inplace=True)


11.比较两个数据框并获取它们的差异

有时,在比较两个 Pandas 数据框时,你不仅想要知道它们是否相等,还想知道如果它们不相等,差异出现在哪里。

✅ 这就是 .compare() 函数派上用场的地方。

✨ 它生成一个数据框,将有差异的列并排显示。

✨ 如果要显示相等的值,请将 keep_equal 参数设置为 True。否则,它们将显示为 NaN。

import pandas as pd
from pandas.testing import assert_frame_equal

candidates_df = pd.read_csv("data/candidates.csv")

"""
Create a second dataframe by changing "Full_Name" & "Age" columns
"""

candidates_df_test = candidates_df.copy()
candidates_df_test.loc[0'Full_Name'] = 'Aida Traore'
candidates_df_test.loc[2'Age'] = 28

"""
Compare the two dataframes: candidates_df & candidates_df_test
"""

# 1. Comparison showing only unmatching values
candidates_df.compare(candidates_df_test)

# 2. Comparison including similar values
candidates_df.compare(candidates_df_test, keep_equal=True)

12.从非常大的数据集中获取一个子集以进行快速分析

有时,我们只需要从非常大的数据集中获取一个子集进行快速分析。其中一种方法是在获取样本之前将整个数据读入内存中。

这可能需要大量的内存,具体取决于数据的大小。而且,读取数据可能需要很长时间。

✅ 你可以在 pandas 的 read_csv() 函数中使用 nrows 参数指定要获取的行数。

# Pandas library
import pandas as pd 

# Load execution time
%load_ext autotime

# File to get sample from: Size: 261,6 MB
large_data = "diabetes_benchmark_data.csv"

# Sample size of interest
sample_size = 400

"""
Approach n°1: Read all the data in memory before getting the sample 
"""

read_whole_data = pd.read_csv(large_data)
sample_data = read_whole_data.head(sample_size)

"""
Approach n°2: Read the sample on the fly
"""

read_sample = pd.read_csv(large_data, nrows=sample_size)


13.将数据框从宽格式转换为长格式

有时,将数据框从宽格式转换为长格式可能很有用,这样更有利于进行更好的分析,特别是在处理时间序列数据时。

𝙒𝙝𝙖𝙩 𝙙𝙤 𝙮𝙤𝙪 𝙢𝙚𝙖𝙣 𝙗𝙮 𝙬𝙞𝙙𝙚 & 𝙡𝙤𝙣𝙜?

✨ 宽格式是指具有大量列的情况。

✨ 长格式则指具有大量行的情况。

✅ 𝙿𝚊𝚗𝚍𝚊𝚜.𝚖𝚎𝚕𝚝() 是这项任务的完美选择。

下面是一个示例

import pandas as pd

# My experimentation data
candidates= {
    'Name':["Aida","Mamadou","Ismael","Aicha"],
    'ID': [1234],
    '2017':[85878991],
    '2018':[9698100102],
    '2019':[100102106106],
    '2020':[899598100],
    '2021':[949698100],
    '2022':[100104104107],
          }
"""
Data in wide format
"""

salary_data = pd.DataFrame(candidates)

"""
Transformation into the long format
"""

long_format_data = salary_data.melt(id_vars=['Name''ID'], 
                                    var_name='Year', value_name='Salary(k$)')

14.通过忽略索引来减小

Pandas 数据框的大小 你知道吗,通过在保存数据框时忽略索引,可以减小 Pandas 数据框的大小吗?

✅ 就像在保存文件时设置 index=False 一样简单。

下面是一个示例。

import pandas as pd

# Read data from Github
URL = "https://raw.githubusercontent.com/keitazoumana/Experimentation-Data/main/diabetes.csv"
data = pd.read_csv(URL)

# Create large data by repeating each row 10000 times
large_data = data.loc[data.index.repeat(10000)]

"""
SAVE WITH INDEX
"""

large_data.to_csv("large_data_with_index.csv")

# Check the size of the file 
!ls -GFlash large_data_with_index.csv

"""
SAVE WITHOUT INDEX
"""

large_data.to_csv("large_data_without_index.csv", index = False)

# Check the size of the file 
!ls -GFlash large_data_without_index.csv     

15.使用 Parquet 替代 CSV

很多时候,我们不会手动查看将由 Pandas 用于进一步分析的 CSV 或 Excel 文件的内容。

如果你的情况是如此,也许你不应再使用 .CSV,并考虑其他更好的选择。

尤其是如果你只关心以下方面:

✨ 处理速度

✨ 保存和加载速度

✨ 数据框占用的磁盘空间

✅ 在这种情况下,.parquet 格式是你的最佳选择,如下所示。

import pandas as pd

# Read data from Github
URL = "https://raw.githubusercontent.com/keitazoumana/Experimentation-Data/main/diabetes.csv"
data = pd.read_csv(URL)

# Create large data for experimentation by repeating each row 20.000 times
exp_data = data.loc[data.index.repeat(20000)]

"""
EXPERIMENT WITH .CSV FORMAT
"""

# Write Time
%%time 
exp_data.to_csv("exp_data.csv", index=False)

# Read Time
%%time
csv_data = pd.read_csv("exp_data.csv")

# File Size
!ls -GFlash exp_data.csv

"""
EXPERIMENT WITH .PARQUET FORMAT
"""

# Write Time
%%time 
exp_data.to_parquet('exp_data.parquet')

# Read Time
%%time 
parquet_data = pd.read_parquet('exp_data.parquet')

# File Size
!ls -GFlash exp_data.parquet   

16.将数据框转换为 Markdown

以更容易理解的方式打印数据框始终更好。

✅ 一种方法是使用 .to_markdown() 函数将数据框呈现为 Markdown 格式。

💡 下面是一个示例。

17.格式化日期时间列

在加载 Pandas 数据框时,默认情况下,日期列以 object 形式表示,这不是 ❌ 正确的日期格式。

✅ 你可以在 parse_dates 参数中指定目标列以获得正确的列类型。

Python 技巧和诀窍

1.使用 tqdm 和 rich 创建进度条

使用进度条可以在执行给定任务时获得可视化状态是有益的。

#!pip -q install rich
from rich.progress import track
from tqdm import tqdm
import time

实现回调函数

def compute_double(x):
      return 2*x

创建进度条

final_dict_doubles = {}

for i in track(range(20), description="Computing 2.n..."):
  final_dict_doubles[f"Value = {i}"] = f"double = {compute_double(i)}"

  # Sleep the process to highligh the progress 
  time.sleep(0.8)
for i in tqdm(range(20), desc="Computing 2.n..."):
  final_dict_doubles[f"Value = {i}" ] = f"double = {compute_double(i)}"

  # Sleep the process to highligh the progress 
  time.sleep(1)

2. 获取日期的天、月、年、星期几、月份

candidates= {
    'Name':["Aida","Mamadou","Ismael","Aicha","Fatou""Khalil"],
    'Degree':['Master','Master','Bachelor'"PhD""Master""PhD"],
    'From':["Abidjan","Dakar","Bamako""Abidjan","Konakry""Lomé"],
    'Application_date': ['11/17/2022''09/23/2022''12/2/2021'
                         '08/25/2022''01/07/2022''12/26/2022']
          }
candidates_df = pd.DataFrame(candidates)
candidates_df['Application_date'] = pd.to_datetime(candidates_df["Application_date"])

# GET the Values
application_date = candidates_df["Application_date"]

candidates_df["Day"] = application_date.dt.day 
candidates_df["Month"] = application_date.dt.month 
candidates_df["Year"] = application_date.dt.year 
candidates_df["Day_of_week"] = application_date.dt.day_name()
candidates_df["Month_of_year"] = application_date.dt.month_name()

3.获取列的最小值和最大值

如果你想要获取给定列的最大值或最小值的行,可以使用以下函数:

✨ df.nlargest(N, "Col_Name") → 基于 Col_Name 获取前 N 行

✨ df.nsmallest(N, "Col_Name") → 基于 Col_Name 获取最小的 N 行

✨ Col_Name 是你感兴趣的列的名称。

4.忽略 pip install 命令的日志输出

有时,在从 Jupyter notebook 中安装库时,你可能不想看到由默认的 pip install 命令生成的安装过程的所有细节。

✅ 你可以指定 -q 或 —quiet 选项来摆脱这些信息。

以下是一个示例 💡

5.在单个笔记本单元格中运行多个命令

想要成功地从 Jupyter notebook 中运行 shell 命令,感叹号符号 '!' 是必不可少的。

然而,当处理多个命令或非常长且复杂的命令时,这种方法可能会变得非常重复 🔂。

✅ 解决这个问题的更好方法是在笔记本单元格开头使用 %%bash 表达式。

💡 下面是一个示例

6.虚拟环境。

数据科学项目可能涉及多个依赖项,处理所有这些依赖项可能有点麻烦。🤯

✨ 一种良好的做法是以一种可以轻松与团队成员共享并以最少的工作量进行复现的方式组织项目。

✅ 其中一种方法是使用虚拟环境。

⚙️ 创建虚拟环境并安装所需的库。

→ 安装虚拟环境模块。pip install virtualenv

→ 通过给出一个有意义的名称创建你的环境。virtualenv [your_environment_name]

→ 激活你的环境。source [your_environment_name]/bin/activate

→ 开始安装项目的依赖项。pip install pandas …

这一切都很棒 👏🏼,但是… 你刚刚创建的虚拟环境只是在你的计算机上局部存在😏。

怎么办?🤷🏻‍♂️

💡 你需要永久保存这些依赖项,以便通过以下命令与其他人共享它们:

→ pip freeze > requirements.txt

这将创建一个包含项目依赖项的 requirements.txt 文件。

🔚 最后,任何人都可以通过运行以下命令安装完全相同的依赖项:

→ pip install -r requirements.txt

7.同时运行多个指标

Scikit-learn 指标

"""
Individual imports
"""

from sklearn.metrics import precision_score, recall_score, f1_score

y_true = [012012]
y_pred = [021001]

print("Precision: ", precision_score(y_true, y_pred, average='macro'))
print("Recall: ", recall_score(y_true, y_pred, average='macro'))
print("F1 Score: ", f1_score(y_true, y_pred, average='macro')) 


"""
Single Line import
"""

from sklearn.metrics import precision_recall_fscore_support 

precision, recall, f1_score, _ = precision_recall_fscore_support(y_true, 
                                                                 y_pred, 
                                                                 average='macro')
print(f"Precision: {precision}")
print(f"Recall: {recall}")
print(f"F1 Score: {f1_score}")

8.将多个列表链成单个序列

你可以使用单个 for 循环将多个列表作为单个序列进行迭代🔂。

✅ 你可以使用 Python 的 itertools 模块中的 chain() ⛓ 函数来实现这一点。

9.美观地打印 JSON 数据

❓ 你是否曾经希望以正确的缩进格式打印 JSON 数据以便更好地可视化?

✅ 可以使用 dumps() 方法的 indent 参数来指定格式化输出的缩进级别。

10.单元测试

你会对你的代码进行测试吗?🧪

我的意思是,你是否进行单元测试?

无论你是数据科学家还是软件开发人员,单元测试都是一个重要的步骤,以确保所实现的功能符合预期的行为。

这无疑具有许多好处:

✨ 更好的质量 💎 代码。

✨ 在添加新功能时,可以编写更简单、更灵活的代码。

✨ 通过节省开发时间 ⏳ 和避免后期错误发现阶段,可以降低成本 💰。

✨ 还有更多…

✅ 使用 unittest,你可以像专业人士一样执行单元测试 😎

下面是一个示例 💡

11.迭代多个列表

在尝试从多个列表中映射⛓信息时,同时迭代多个列表是有益的。

✅ 我常用的方法是使用 Python 的 zip 函数。

下面是一个示例 💡

12.迭代多个列表

当程序变得复杂时,这往往是不可避免的。

然而,使用嵌套循环会使你的程序变得更难阅读🔬和维护🙅🏽‍♂️。

✅ 你可以使用 Python 内置的 product() 函数来代替嵌套循环。

下面是一个示例 💡

13.替代嵌套循环的解决方案

文本预处理从未如此简单。

❓ 你需要编写多少函数或正则表达式来执行基本的文本预处理任务,例如:

✨ 修复 Unicode

✨ 删除 URL

✨ 摆脱数字、标点符号等?

这些任务不仅耗时⏰,而且根据文本的复杂性📈可能会增加复杂性。

✅ 使用 clean-text Python 库可以减轻所有这些负担。

下面是一个示例 💡

结论

感谢你的阅读!🎉 🍾

希望你觉得这些 Python 和 Pandas 技巧清单有帮助!

✄-----------------------------------------------

看到这里,说明你喜欢这篇文章,请点击「在看」或顺手「转发」「点赞」。

欢迎微信搜索「panchuangxx」,添加小编磐小小仙微信,每日朋友圈更新一篇高质量推文(无广告),为您提供更多精彩内容。


▼     扫描二维码添加小编  ▼  ▼  

Python社区是高质量的Python/Django开发社区
本文地址:http://www.python88.com/topic/166084
 
235 次点击