【问题标题】:Python init object of generic type泛型类型的 Python 初始化对象
【发布时间】:2021-09-27 16:13:23
【问题描述】:

来自 C# 背景并了解其泛型类型方法,我现在正尝试在 Python 中实现类似的东西。我需要以特殊的字符串格式对类进行序列化和反序列化,因此我创建了以下两个基类,第一个用于单个实体序列化,第二个用于该实体类型的列表序列化。

from typing import Any, TypeVar, List, cast, Type, Generic, NewType
import re

T = TypeVar('T')

class Serializable(Generic[T]):
    def to_str(self) -> str:
        raise NotImplementedError

    @classmethod
    def from_str(cls, str: str):
        raise NotImplementedError


class SerializableList(List[Serializable[T]]):
    def __init__(self):
        self.separator: str = "\n"

    @classmethod
    def from_str(cls, str: str):
        list = cls()
        for match in re.finditer(list.separator, str):
            list.append(T().from_str(match)) # <-- PROBLEM: HOW TO INIT A GENERIC ENTITY ???
            # list.append(Serializable[T].from_str(match)) <-- Uses base class (NotImplemented) instead of derived class
        
        return list
    
    def to_str(self) -> str:
        str = ""
        for e in self:
            str = str + f"{e.to_str()}{self.separator}"
    
        return str

然后我可以从这些类派生并且必须实现to_strfrom_str。请查看标记 。我不知道如何为列表初始化当前使用类型的新实体。我们如何以 Python 方式执行此操作?

【问题讨论】:

  • Python 泛型更接近 Java 而不是 C# - 它们几乎完全用于静态分析。许多信息在运行时不可用。
  • 我对 Python 还很陌生。有更好的方法吗?我认为我尝试实现的过程非常简单。具有两个派生方法的基类和相同类型实体的列表。会有很多必须实现基类的派生类。如何做到这一点?

标签: python generics python-typing


【解决方案1】:

正如@user2357112supportsMonica 在 cmets 中所说,typing.Generic 几乎只用于静态分析,在几乎所有情况下在运行时基本上没有影响。从您的代码的外观来看,您正在做的事情可能更适合抽象基类(文档here,教程here),它可以很容易地与Generic 结合使用。

ABCMeta 作为元类的类被标记为抽象基类 (ABC)。除非 ABC 中标有 @abstractmethod 装饰器的所有方法都已被覆盖,否则无法实例化 ABC 的子类。在下面我建议的代码中,我已将ABCMeta 元类显式添加到您的Serializable 类中,并通过使其继承自collections.UserList 而不是typing.List 将其隐式添加到您的SerializableList 类中。 (collections.UserList 已经有 ABCMeta 作为它的元类。)

使用 ABC,您可以像这样定义一些接口(由于抽象方法,您将无法实例化这些接口):

### ABSTRACT INTERFACES ###

from abc import ABCMeta, abstractmethod
from typing import Any, TypeVar, Type, Generic
from collections import UserList
import re

T = TypeVar('T')

class AbstractSerializable(metaclass=ABCMeta):
    @abstractmethod
    def to_str(self) -> str: ...

    @classmethod
    @abstractmethod
    def from_str(cls: Type[T], string: str) -> T: ...


S = TypeVar('S', bound=AbstractSerializable)


class AbstractSerializableList(UserList[S]):
    separator = '\n'

    @classmethod
    @property
    @abstractmethod
    def element_cls(cls) -> Type[S]: ...

    @classmethod
    def from_str(cls, string: str):
        new_list = cls()
        for match in re.finditer(cls.separator, string):
            new_list.append(cls.element_cls.from_str(match))
        return new_list
    
    def to_str(self) -> str:
        return self.separator.join(e.to_str() for e in self)

然后你可以像这样提供一些具体的实现:

class ConcreteSerializable(AbstractSerializable):
    def to_str(self) -> str:
        # put your actual implementation here

    @classmethod
    def from_str(cls: Type[T], string: str) -> T:
        # put your actual implementation here

class ConcreteSerializableList(AbstractSerializableList[ConcreteSerializable]:
    # this overrides the abstract classmethod-property in the base class
    element_cls = ConcreteSerializable

(顺便说一句——我更改了你的几个变量名——strlist 等——因为它们隐藏了内置类型和/或函数。这通常会导致烦人的错误,即使它没有't,对于阅读您的代码的其他人来说非常混乱!我还清理了您的 to_str 方法,该方法可以简化为单行,并将您的 separator 变量移动为类变量,因为它似乎对所有类实例都相同,并且似乎永远不会改变。)

【讨论】:

  • 谢谢,这似乎就是我想要的。剩下一点:运行时出现 python 错误S = TypeVar('S', bound=AbstractSerializable[Any]) TypeError: 'ABCMeta' object is not subscriptable。知道如何解决这个问题吗?
  • 通过更改 List 的类声明使其运行,如下所示:class AbstractSerializableList(List[AbstractSerializable[T]], ABC):。请注意,不再需要类型 S
  • 感谢@TWP — 我的答案的早期版本留下的错字。应该是S = TypeVar('S', bound=AbstractSerializable)。 (您是正确的,您不需要需要 S,您可以对两个类都使用T,但是使用绑定的TypeVar 会使您的类型提示更加具体。)
【解决方案2】:

现在我找到了一个 dirty 解决方案 - 这是添加列表条目的类型(构造函数)参数,如下所示:

class SerializableList(List[Serializable[T]]):
    #                                           This one
    #                                              |
    #                                              v
    def __init__(self, separator: str = "\n", entity_class: Type = None):
        self.separator = separator
        self.entity_class = entity_class


    @classmethod
    def from_str(cls, str: str):
        list = cls()
        for match in re.finditer(list.separator, str):
            list.append(list.entity_class.from_str(match))

        return list

我想知道是否有一种更简洁的方法可以从 List[T] 中获取正确的 [T] 类型构造函数,因为它已经在那里提供了?

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2020-12-05
    • 2019-10-06
    • 2013-10-04
    • 1970-01-01
    • 2019-03-16
    • 1970-01-01
    • 2016-04-11
    • 1970-01-01
    相关资源
    最近更新 更多