[英]dict attribute 'type' to select Subclass of dataclass
我有以下 class
@dataclass_json
@dataclass
class Source:
type: str =None
label: str =None
path: str = None
和兩個子類:
@dataclass_json
@dataclass
class Csv(Source):
csv_path: str=None
delimiter: str=';'
和
@dataclass_json
@dataclass
class Parquet(Source):
parquet_path: str=None
現在給定字典:
parquet={type: 'Parquet', label: 'events', path: '/.../test.parquet', parquet_path: '../../result.parquet'}
csv={type: 'Csv', label: 'events', path: '/.../test.csv', csv_path: '../../result.csv', delimiter:','}
現在我想做類似的事情
Source().from_dict(csv)
並且 output 將是 class Csv 或 Parquet。 我知道,如果您啟動 class 源,您只需使用“來自 dict”的方法“上傳”參數,但是在不使用“構造函數”的情況下,是否有可能通過某種類型的繼承來執行此操作,這會產生 if-else if - 其他所有可能的“類型”?
Pureconfig 是一個 Scala 庫,當屬性“類型”具有所需子類的名稱時,它會創建不同的案例類。 在 Python 這可能嗎?
您可以構建一個幫助器來選擇並實例化適當的子類。
def from_data(data: dict, tp: type):
"""Create the subtype of ``tp`` for the given ``data``"""
subtype = [
stp for stp in tp.__subclasses__() # look through all subclasses...
if stp.__name__ == data['type'] # ...and select by type name
][0]
return subtype(**data) # instantiate the subtype
這可以使用您的數據和基礎 class 調用,從中調用 select:
>>> from_data(
... {'type': 'Csv', 'label': 'events', 'path': '/.../test.csv', 'csv_path': '../../result.csv', 'delimiter':','},
... Source,
... )
Csv(type='Csv', label='events', path='/.../test.csv', csv_path='../../result.csv', delimiter=',')
如果您需要經常運行它,則值得構建一個dict
來優化子類型查找。 一種簡單的方法是向您的基礎 class 添加一個方法,並將查找存儲在那里:
@dataclass_json
@dataclass
class Source:
type: str =None
label: str =None
path: str = None
@classmethod
def from_data(cls, data: dict):
if not hasattr(cls, '_lookup'):
cls._lookup = {stp.__name__: stp for stp in cls.__subclasses__()}
return cls._lookup[data["type"]](**data)
這可以直接在基礎 class 上調用:
>>> Source.from_data({'type': 'Csv', 'label': 'events', 'path': '/.../test.csv', 'csv_path': '../../result.csv', 'delimiter':','})
Csv(type='Csv', label='events', path='/.../test.csv', csv_path='../../result.csv', delimiter=',')
這是我對這個問題的回答的一個變體。
@dataclass_json
@dataclass
class Source:
type: str = None
label: str = None
path: str = None
def __new__(cls, type=None, **kwargs):
for subclass in cls.__subclasses__():
if subclass.__name__ == type:
break
else:
subclass = cls
instance = super(Source, subclass).__new__(subclass)
return instance
assert type(Source(**csv)) == Csv
assert type(Source(**parquet)) == Parquet
assert Csv(**csv) == Source(**csv)
assert Parquet(**parquet) == Source(**parquet)
你問了,我很樂意答應。 但是,我質疑這是否真的是你需要的。 我認為這對你的情況來說可能是矯枉過正。 我最初想出了這個技巧,所以我可以直接從數據中實例化......
如果這些條件適用於您的情況,那么我認為這是一種值得的方法。 如果不是這樣,使用__new__
增加的復雜性 - 一個中等高級的操作 - 可能不會超過用於實例化的代碼復雜性的節省。 可能有更簡單的選擇。
例如,您似乎已經知道需要哪個子類; 它是數據中的字段之一。 如果你把它放在那里,大概你寫的任何邏輯都可以用來在當時和那里實例化適當的子類,繞過對我的解決方案的需求。 或者,不要將子類的名稱存儲為字符串,而是存儲子類本身。 然后你可以這樣做: data['type'](**data)
我還想到,也許您根本不需要 inheritance。 Csv
和Parquet
是否存儲相同類型的數據,只是它們讀取的文件格式不同? 那么也許你只需要一個帶有from_csv
和from_parquet
方法的 class 。 或者,如果其中一個參數是文件名,則很容易根據文件擴展名確定您需要哪種類型的文件解析。 通常我會把它放在__init__
中,但是由於您使用的是dataclass
,我想這會發生在__post_init__
中。
你需要這種行為嗎?
from dataclasses import dataclass
from typing import Optional, Union, List
from validated_dc import ValidatedDC
@dataclass
class Source(ValidatedDC):
label: Optional[str] = None
path: Optional[str] = None
@dataclass
class Csv(Source):
csv_path: Optional[str] = None
delimiter: str = ';'
@dataclass
class Parquet(Source):
parquet_path: Optional[str] = None
@dataclass
class InputData(ValidatedDC):
data: List[Union[Parquet, Csv]]
# Let's say you got a json-string and loaded it:
data = [
{
'label': 'events', 'path': '/.../test.parquet',
'parquet_path': '../../result.parquet'
},
{
'label': 'events', 'path': '/.../test.csv',
'csv_path': '../../result.csv', 'delimiter': ','
}
]
input_data = InputData(data=data)
for item in input_data.data:
print(item)
# Parquet(label='events', path='/.../test.parquet', parquet_path='../../result.parquet')
# Csv(label='events', path='/.../test.csv', csv_path='../../result.csv', delimiter=',')
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.