簡體   English   中英

dict 屬性 'type' 到 select 數據類的子類

[英]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)

你問了,我很樂意答應。 但是,我質疑這是否真的是你需要的。 我認為這對你的情況來說可能是矯枉過正。 我最初想出了這個技巧,所以我可以直接從數據中實例化......

  • 我的數據是異構的,我事先不知道哪個子類適合每個數據,
  • 我無法控制數據,並且
  • 弄清楚要使用哪個子類需要對數據進行一些處理,我認為這些處理屬於 class 內部(出於邏輯原因以及避免污染發生實例化的 scope)。

如果這些條件適用於您的情況,那么我認為這是一種值得的方法。 如果不是這樣,使用__new__增加的復雜性 - 一個中等高級的操作 - 可能不會超過用於實例化的代碼復雜性的節省。 可能有更簡單的選擇。

例如,您似乎已經知道需要哪個子類; 它是數據中的字段之一。 如果你把它放在那里,大概你寫的任何邏輯都可以用來在當時和那里實例化適當的子類,繞過對我的解決方案的需求。 或者,不要將子類的名稱存儲為字符串,而是存儲子類本身。 然后你可以這樣做: data['type'](**data)

我還想到,也許您根本不需要 inheritance。 CsvParquet是否存儲相同類型的數據,只是它們讀取的文件格式不同? 那么也許你只需要一個帶有from_csvfrom_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=',')

驗證的_dc:https://github.com/EvgeniyBurdin/validated_dc

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM