为何get_post_end()触发NameError而get_pre_end()可正常运行?
问题描述
定义了以下函数:
import math from numpy import nan from datetime import date mismatch_day = date(2022, 12, 1) def get_pre_end(row, mism_day): global end_pre if row['date'] < mism_day: end_pre = row['date_end'] return end_pre def get_post_end(row, mism_day): global end_post if row['date'] >= mism_day: end_post = row['date_end'] return end_post
调用get_pre_end()能得到预期输出:
post_end_df = pd.DataFrame() post_end_df['post_end'] = bydate_df.apply(lambda row: get_pre_end(row, mismatch_day), axis=1) post_end_list = post_end_df['post_end'].tolist() post_end_list = [item for item in post_end_list if not(math.isnan(item)) == True]
输出示例:[228.7, 179.3, 168.7, 111.9, 189.2, 220.6, 165.7, 183.8, 215.6,...
但调用get_post_end()时触发NameError:
post_end_df = pd.DataFrame() post_end_df['post_end'] = bydate_df.apply(lambda row: get_post_end(row, mismatch_day), axis=1) post_end_list = post_end_df['post_end'].tolist() post_end_list = [item for item in post_end_list if not(math.isnan(item)) == True]
报错信息:
--------------------------------------------------------------------------- NameError Traceback (most recent call last) Cell In[32], line 2 1 post_end_df = pd.DataFrame() ----> 2 post_end_df['post_end'] = bydate_df.apply(lambda row: get_post_end(row, mismatch_day), axis=1) 3 post_end_list = post_end_df['post_end'].tolist() 4 post_end_list = [item for item in post_end_list if not(math.isnan(item)) == True] File ~\Anaconda3\lib\site-packages\pandas\core\frame.py:8839, in DataFrame.apply(self, func, axis, raw, result_type, args, **kwargs) 8828 from pandas.core.apply import frame_apply 8830 op = frame_apply( 8831 self, 8832 func=func, (...) 8837 kwargs=kwargs, 8838 ) -> 8839 return op.apply().__finalize__(self, method="apply") File ~\Anaconda3\lib\site-packages\pandas\core\apply.py:727, in FrameApply.apply(self) 724 elif self.raw: 725 return self.apply_raw() -> 727 return self.apply_standard() File ~\Anaconda3\lib\site-packages\pandas\core\apply.py:851, in FrameApply.apply_standard(self) 850 def apply_standard(self): -> 851 results, res_index = self.apply_series_generator() 853 # wrap results 854 return self.wrap_results(results, res_index) File ~\Anaconda3\lib\site-packages\pandas\core\apply.py:867, in FrameApply.apply_series_generator(self) 864 with option_context("mode.chained_assignment", None): 865 for i, v in enumerate(series_gen): 866 # ignore SettingWithCopy here in case the user mutates -> 867 results[i] = self.f(v) 868 if isinstance(results[i], ABCSeries): 869 # If we have a view on v, we need to make a copy because 870 # series_generator will swap out the underlying data 871 results[i] = results[i].copy(deep=False) Cell In[32], line 2, in <lambda>(row) 1 post_end_df = pd.DataFrame() ----> 2 post_end_df['post_end'] = bydate_df.apply(lambda row: get_post_end(row, mismatch_day), axis=1) 3 post_end_list = post_end_df['post_end'].tolist() 4 post_end_list = [item for item in post_end_list if not(math.isnan(item)) == True] Cell In[20], line 5, in get_post_end(row, mism_day) 3 if row['date'] >= mism_day: 4 end_post = row['date_end'] ----> 5 return end_post NameError: name 'end_post' is not defined
疑问:无法理解为何get_post_end()触发该错误,以为end_post是全局定义的,且else分支会处理不满足if条件的行。
报错原因
- 全局变量未初始化:
global end_post仅声明函数内部要使用全局作用域的end_post变量,但你从未在全局作用域定义过这个变量。当函数执行时,如果没有满足if条件的行先给end_post赋值,直接return就会找不到该变量。 get_pre_end能运行是侥幸:get_pre_end正常输出只是因为bydate_df的第一行数据满足row['date'] < mism_day,提前给end_pre赋了值,后续不满足条件的行可以返回之前的全局变量值。如果bydate_df的第一行不满足条件,get_pre_end同样会触发NameError。- 无隐式else分支:函数里没有写else分支,不满足if条件时不会执行任何赋值操作,直接return未定义的变量,必然报错。
修复方法
方法1:提前初始化全局变量
在全局作用域先定义end_post和end_pre,给一个初始值(比如nan),这样即使第一行不满足条件,也能返回初始值:
import math from numpy import nan from datetime import date mismatch_day = date(2022, 12, 1) # 初始化全局变量 end_pre = nan end_post = nan def get_pre_end(row, mism_day): global end_pre if row['date'] < mism_day: end_pre = row['date_end'] return end_pre def get_post_end(row, mism_day): global end_post if row['date'] >= mism_day: end_post = row['date_end'] return end_post
方法2:放弃全局变量(更推荐)
全局变量在pandas的apply操作中容易引发不可控的副作用,改用pandas原生逻辑实现更安全:
import pandas as pd import math from numpy import nan from datetime import date mismatch_day = date(2022, 12, 1) # 获取满足条件的第一个date_end值,无满足条件的则设为nan post_end_value = bydate_df.loc[bydate_df['date'] >= mismatch_day, 'date_end'].iloc[0] if not bydate_df.loc[bydate_df['date'] >= mismatch_day].empty else nan # 将值填充到整列 bydate_df['post_end'] = post_end_value post_end_list = bydate_df['post_end'].dropna().tolist()
如果需要保留最后一个满足条件的值,只需把iloc[0]改为iloc[-1]即可。
内容的提问来源于stack exchange,提问作者NigelBlainey
相关产品推荐
相关产品推荐

