DatetimeIndex阻止DataFrame从装饰函数返回

时间:2019-10-06 11:40:54

标签: python pandas dataframe python-decorators datetimeindex

我有一个装饰器,可以将函数的返回值添加到提供的字典或熊猫数据框中。只要数据框在返回值上没有不同的DateTimeIndex,它就可以正常工作。我尝试仅合并数据帧并考虑索引,但由于某种原因,这意味着收集帧最终为空。

因此此代码可以正常工作:

    def add_return_to_dict_or_pandas_col_decorator(return_dict):
        def actual_decorator(func):
            @functools.wraps(func)
            def wrapper(*args, **kwargs):
                nonlocal return_dict
                return_dict[args[0]] = func(*args, **kwargs)        
            return wrapper    
        return actual_decorator

如果应用于:

accumulate_dict = dict()    
@add_return_to_dict_or_pandas_col_decorator(accumulate_dict)
def f2(identifier, x):
    return x * x    
f2('thrity', 30)
f2('three', 3)
print(accumulate_dict)

accumulate_df = pd.DataFrame()
@add_return_to_dict_or_pandas_col_decorator(accumulate_df)
def f3(identifier, x):
    return [x, x * x, x + x]
f3('thrity', 30)
f3('three', 3)
print(accumulate_df)

但是使用返回带有DateTimeIndex的数据帧的函数会使它失败(因为它们实际上不匹配)。这是解决该问题的尝试:

def add_return_to_pandas_indexed_col_decorator(return_data_frame):
def actual_decorator(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        nonlocal return_data_frame
        if return_data_frame.shape[0] > 0:
            return_data_frame = pd.merge(return_data_frame, func(*args, **kwargs),
                                         how='outer', left_index=True, right_index=True)
        else:
            return_data_frame = func(*args, **kwargs)
    return wrapper

return actual_decorator

现在,我的测试代码实际上已经通过此过程运行了(想象一下该函数返回带有DateTimeIndex的数据帧),但是最终结果是一个空的数据帧。

return_df = pd.DataFrame()
tckrs = ['GLD', 'GDX']  
@add_return_to_pandas_indexed_col_decorator(return_df)
def set_df_get_return_series(*args, **kwargs):
    return get_return_series(*args, **kwargs)

for ticker in tckrs:
    set_df_get_return_series(ticker)
print(return_df)

get_return_series在哪里:

def get_return_series(ticker):
    from faker import Faker
    fake = Faker()
    return pd.DataFrame(np.random.randn(2).tolist(),
                    columns=[ticker],
                    index=pd.DatetimeIndex([fake.date_between(start_date='-30y', end_date='-1d'),
                                            fake.date_between(start_date='today', end_date='+30y')]))

1 个答案:

答案 0 :(得分:1)

通过同事(感谢Dillon)解决了这个问题。问题看起来与整个变量的覆盖有关。该覆盖在函数中被视为nonlocal,但是变量的任何完全覆盖都不会保留在装饰器的本地范围之外。全局/外部名称不能指向经过修饰的装饰器中的其他内存地址,但是它的任何可变成员都可以。这也解释了为什么以前的实现有效而索引不起作用。因此,问题与DatetimeIndex没有直接关系。

添加了一个额外的间接使它起作用。如果有人可以找到更好的实现,请发布:

def add_return_to_pandas_indexed_col_decorator(return_object):
def actual_decorator(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        nonlocal return_object
        if return_object.frame is not None:
            return_object.frame = pd.merge(return_object.frame, func(*args, **kwargs), how='outer', left_index=True,
                                           right_index=True)
        else:
            return_object.frame = func(*args, **kwargs)
    return wrapper
return actual_decorator

如此使用(将Test Class集成到装饰器中可能是一个好主意):

class Test(object):
    def __init__(self):
        self.frame = pd.DataFrame()

tckrs = ['GLD', 'GDX']
accumulate_object = Test()
@add_return_to_pandas_indexed_col_decorator(accumulate_object)
def set_df_get_return_series(*args, **kwargs):
    return get_return_series(*args, **kwargs)
for ticker in tckrs:
    set_df_get_return_series(ticker)
print(accumulate_object.frame)