假设我有一个脚本,该脚本从数据库中将数据读取到数据帧中,对该数据帧运行一些逻辑,然后将结果数据帧导出到另一个数据库表中,如下所示。问题是exec函数后, transform.py 中的数据框不会被覆盖。
注意:这是一个简单的示例,用于说明问题,而不是我尝试使用此方法解决的实际问题。
期望:
执行前
+---------+---------------+--------------+----------+
| metric | modified_date | current_date | datediff |
+---------+---------------+--------------+----------+
| metric1 | 2019-03-31 | 2019-05-03 | 33 |
| metric2 | 2019-03-31 | 2019-05-03 | 33 |
| metric3 | 2019-03-31 | 2019-05-03 | 33 |
| metric4 | 2019-03-20 | 2019-05-03 | 44 |
+---------+---------------+--------------+----------+
执行后
+---------+---------------+--------------+----------+
| metric | modified_date | current_date | datediff |
+---------+---------------+--------------+----------+
| metric4 | 2019-03-20 | 2019-05-03 | 44 |
+---------+---------------+--------------+----------+
实际:
执行前
+---------+---------------+--------------+----------+
| metric | modified_date | current_date | datediff |
+---------+---------------+--------------+----------+
| metric1 | 2019-03-31 | 2019-05-03 | 33 |
| metric2 | 2019-03-31 | 2019-05-03 | 33 |
| metric3 | 2019-03-31 | 2019-05-03 | 33 |
| metric4 | 2019-03-20 | 2019-05-03 | 44 |
+---------+---------------+--------------+----------+
执行后
+---------+---------------+--------------+----------+
| metric | modified_date | current_date | datediff |
+---------+---------------+--------------+----------+
| metric1 | 2019-03-31 | 2019-05-03 | 33 |
| metric2 | 2019-03-31 | 2019-05-03 | 33 |
| metric3 | 2019-03-31 | 2019-05-03 | 33 |
| metric4 | 2019-03-20 | 2019-05-03 | 44 |
+---------+---------------+--------------+----------+
它们是相同的!
transform.py
def dataframe_transform(logic, source_table, dest_table, database, existing_rows='truncate'):
...
df = table_to_df(table=source_table, database=database)
try:
exec(logic)
except Exception:
raise
result = df_to_table(dataframe=df, database=database, table=dest_table, existing_rows=existing_rows)
return result
逻辑过滤出数据框以查找需要更新的记录,并启动另一个过程,并使用新的过滤后的数据覆盖原始数据框。
logic.py
# This is just an example I made up - please don't focus on solving this.
late_df = pd.DataFrame()
# Check if data is late
late_cutoff = 40
for index, row in df.iterrows():
if row['datediff'] >= late_cutoff:
late_df = late_df.append(row, ignore_index=True)
... # Do something else
df = late_df # Save flagged records by updating the original dataframe.
我为什么要这样做?在这种情况下,我知道输入是安全的,它使我可以将此代码重用于各种脚本并分离出变换逻辑。
答案 0 :(得分:5)
检查范围。从提供的代码中无法分辨,但我怀疑您的exec调用未正确管理范围(本地,全局)。 “在Python 3中,exec是一个函数;它的使用对使用它的函数的编译字节码没有影响。” (来自What's the difference between eval, exec, and compile?)
另请参阅:https://www.programiz.com/python-programming/methods/built-in/exec
个人意见:eval / exec是邪恶的,应避免。
其他人在评论中表达的最后一点。该代码示例显示,您仍在行中思考,并在基于行的操作(对于itterrows中的x)中将向量(df ['col'])与标量(late_cutoff)混合使用这是熊猫用户的常见问题,我对于其他人,在此类问题上进行了大量重构。如果您可以按照设计的方式使用熊猫来更改代码以使用熊猫,那么程序的速度将提高一个数量级:无循环且不更改原始数据。一次读取-使用更改后的数据创建一个新的数据框,而无需使用迭代器-一次写入。如果必须循环,请创建一组键并遍历该键以创建矢量化操作:
keys = set(df['key_col'])
for key in keys:
dfx = df[df[key > limit]]
这也可能对您有用(请参阅“多次写入逻辑以提高写入速度”)Bulk Insert A Pandas DataFrame Using SQLAlchemy
答案 1 :(得分:1)
这是关于python和数据库的混合问题。我认为您可能需要同时检查python和数据库。
假设您使用的是mysql。 (我注意到检查部分看起来像mysql客户端输出) 期望从DB的source_table读取到数据帧,然后通过ogic.py更改数据帧。将其写入dest_table。你期望 1.之前/之后应该不同。
我的问题: 1.源表之前/之后应该相同。是您的期望吗?是否有可能,检查零件错误:比较了源表之前/之后。正如我提到的一样。
应该不同。如果期望不同,如何确保df_table进程按期望完成? 是否有可能在参数中传递了错误的目标表名称并更改了错误的表。但仍要检查目标名称
不变,并且返回的结果代码未显示它。
要解决此问题,我认为跟踪点或日志应该是有效的工具
祝你好运
答案 2 :(得分:1)
就像已经提到的每个人一样,您应该更喜欢简单地导入代码,但是我认为您正在以某种方式动态地构建“代码字符串”,所以这对您来说是不可能的。
确定要使用exec(logic)
吗?在我测试的简单示例中,它工作正常。也许您正在使用exec(logic, globals(), locals())
或exec(logic, globals())
?这种情况下exec使用“ locals / globals的副本”,因此它不会更新当前作用域的真实“ locals”。
logic = """
late_df = df.replace('a', 'x')
df = late_df
"""
def simple_exec_transform(df):
df = df + 'opq'
try:
exec(logic)
except Exception:
raise
return df
def bad_transform(df):
df = df + 'opq'
try:
exec(logic, globals(), locals())
except Exception:
raise
return df
def run_logic(locals_dict, return_variable='df'):
exec(logic, globals(), locals_dict)
if return_variable not in locals_dict:
raise NameError("return_variable is not available in locals after exec(logic)")
return locals_dict[return_variable]
def controlled_locals_exec_transform(df):
df = df + 'opq'
try:
df = run_logic({'df': df})
except Exception:
raise
return df
print(simple_exec_transform('abcdef'))
print(bad_transform('abcdef'))
print(controlled_locals_exec_transform('abcdef'))
# xbcdefopq
# abcdefopq
# xbcdefopq
答案 3 :(得分:0)
我知道它很容易将代码片段作为输入,方便您使用解释器。您可以根据需要选择在运行时导入不同的logic
模块。
if predicate1:
import logic_one as logic
elif predicate2:
import logic_two as logic
logic.my_logic_operations_on_dataframe(df)