我正在构建一个Python类,它可以与PySpark数据帧或Hive表一起使用。输入数据可以是表名称的str
,也可以是DataFrame
。Python的最佳实践是什么?我想这样做,以便它根据用例的不同而灵活。唯一的区别是数据应该如何传递到类(作为表或Spark DataFrame),其他一切都是一样的。
参见以下示例:
class DataPipeline:
def __init__(self, data):
if isinstance(data, str):
self.df = spark.read.table(data)
elif isinstance(data, DataFrame):
self.df = data
else:
raise ValueError("some error")
def process_data(self):
# do something with the self.df here
不要。使__init__
期望DataFrame
,并使调用者负责(可能通过类方法)在调用DataPipeline.__init__
之前将字符串转换为DataFrame
。
class DataPipeline:
def __init__(self, data: DataFrame):
self.df = df
@classmethod
def from_string(cls, data: str):
return cls(spark.read.table(data))
p1 = DataPipeline(some_data_frame)
p2 = DataPipeline.from_string("...") # DataPipline(spark.read.table("..."))
Ryan Singer(据推测,尽管经常被引用,但我还没有找到原始来源)曾说过
软件中如此多的复杂性来自于试图让一件事做两件事。
这里,您试图将一件事(DataPipeline.__init__
)变成两件事(使用字符串初始化DataPipeline
,使用DataFrame
初始化DataPipeline
)。
对于重构,我选择对__init__
进行最简单的处理(接受DataFrame
),将首先将字符串解析为DataFrame
的更复杂逻辑转移到一个单独的类方法中,该方法使用适当的数据帧调用__init__
。
(是否要对类型验证进行任何显式运行时检查以提前失败,或者只是假设调用者将接受未能传递正确类型的值的后果,取决于您。)
您有正确的基本想法,但语法错误——使用raise
引发异常(我建议TypeError
用于无效类型,而不是ValueError
),并确保您设置的是self.df
而不是本地vardf
:
class DataPipeline:
def __init__(self, data):
if isinstance(data, str):
self.df = spark.read.table(data)
elif isinstance(data, DataFrame):
self.df = data
else:
raise TypeError("Data must be str or DataFrame")