围绕 JSON 数据包装一个 python 类,哪个更好?

Posted

技术标签:

【中文标题】围绕 JSON 数据包装一个 python 类,哪个更好?【英文标题】:Wrapping a python class around JSON data, which is better? 【发布时间】:2016-11-22 16:32:28 【问题描述】:

序言:我正在针对提供 JSON 的服务编写一个 python API。 这些文件以 JSON 格式存储在磁盘上以缓存值。 API 应该对 JSON 数据进行分类访问,因此 IDE 和用户可以在运行前了解对象中存在哪些(只读)属性,同时还提供一些便利功能。

问题:我有两种可能的实现方式,我想知道哪个更好或“pythonic”。虽然我两者都喜欢,但如果您提出更好的解决方案,我愿意提供建议。

第一个解决方案:定义和继承 JSONWrapper 虽然不错,但它非常冗长且重复。

class JsonDataWrapper:
    def __init__(self, json_data):
        self._data = json_data

    def get(self, name):
        return self._data[name]


class Course(JsonDataWrapper):
    def __init__(self, data):
        super().__init__(data)
        self._users =   # class omitted
        self._groups =   # class omitted
        self._assignments = 

    @property
    def id(self): return self.get('id')

    @property
    def name(self): return self.get('full_name')

    @property
    def short_name(self): return self.get('short_name')

    @property
    def users(self): return self._users

    @users.setter
    def users(self, data):
        users = [User(u) for u in data]
        for user in users:
            self.users[user.id] = user
            # self.groups = user  # this does not make much sense without the rest of the code (It works, but that decision will be revised :D)

第二种解决方案:使用 lambda 获得更短的语法。虽然工作且简短,但它看起来不太正确(请参阅下面的编辑 1。)

def json(name): return property(lambda self: self.get(name))

class Group(JsonDataWrapper):
    def __init__(self, data):
        super().__init__(data)
        self.group_members = []  # elements are of type(User). edit1, was self.members = []

    id = json('id')
    description = json('description')
    name = json('name')
    description_format = json('description_format')

(命名这个函数'json'没有问题,因为我不在那里导入json。)

我想到了第三种可能的解决方案,但我无法完全理解:覆盖内置属性,因此我可以定义一个装饰器来包装返回的字段名称以进行查找:

@json  # just like a property fget
def short_name(self): return 'short_name'

这可能会更短一些,不知道这是否会使代码更好。

不合格的解决方案(恕我直言):

JSONDe,Encoder:扼杀所有灵活性,不提供只读属性方法 __get,setattr__:使得在运行前无法确定属性。虽然它会将 self.get('id') 缩短为 self['id'],但如果属性不在基础 json 数据中,它也会使事情变得更加复杂。

感谢您的阅读!

编辑 1:2016-07-20T08:26Z

进一步澄清 (@SuperSaiyan) 为什么我不太喜欢第二种解决方案: 我觉得 lambda 函数与其他类语义完全脱节(这也是它更短的原因:D)。我想我可以通过在代码中正确记录决定来帮助自己更喜欢它。对于理解@property含义的每个人来说,第一个解决方案都很容易理解,无需任何额外解释。

@SuperSaiyan 的第二条评论:你的问题是,为什么我把Group.members 作为属性放在那里?列表存储了 type(User) 实体,可能不是你想的那样,我改了例子。

@jwodder:下次我会使用 Code Review,不知道那是一回事。

(另外:我真的认为Group.members 让你们有些失望,我编辑了代码以使其更加明显:组成员是将添加到列表中的用户。

The complete code is on github,虽然没有记录,但对某些人来说可能很有趣。请记住:这都是 WIP :D)

【问题讨论】:

好问题!您能否详细说明(通过编辑问题)为什么您觉得第二种解决方案看起来不正确?我个人喜欢它(并且有一些库/框架实现了第二种解决方案。 .. 另外,您希望在类级别定义 cls.membersselfproperty 的上下文中没有意义 这个问题似乎更适合Code Review - 请参阅它的comparative-review 标签。 使用 class JsonDataWrapper(object) 获取新的样式类,至少在 Python 2.x 上 @SuperSaiyan 我发布了一个编辑,不确定是否已经通知了你们所有人,但我认为这个评论会?不确定。 【参考方案1】:

(注意:这得到了更新,我现在正在使用具有运行时类型强制的数据类。见底部:3)

所以,已经一年了,我要回答我自己的问题。我不太喜欢自己回答,但是:这会将线程标记为已解决,这本身可能会对其他人有所帮助。

另一方面,我想记录并说明为什么我选择我的解决方案而不是建议的答案。不是为了证明我是对的,而是为了突出不同的权衡。

我刚刚意识到,这很长,所以:

tl;博士

collections.abc 包含强大的抽象,如果你可以访问它,你应该使用它们(cpython >= 3.3)。 @property 很好用,可以轻松添加文档并提供只读访问权限。 嵌套类看起来很奇怪,但可以很好地复制深度嵌套 JSON 的结构。

建议的解决方案

python 元类

首先:我喜欢这个概念。 我已经考虑了许多应用程序,它们被证明是有用的,尤其是在以下情况下:

    编写一个可插入的 API,其中元类强制正确使用派生类及其实现细节 拥有从元类派生的类的全自动注册表。

另一方面,python 的元类逻辑让我难以理解(我至少花了三天时间才弄明白)。虽然原则上很简单,但魔鬼在细节中。 所以,我决定不这样做,只是因为我可能会在不久的将来放弃这个项目,而其他人应该能够轻松地从我离开的地方继续。

命名元组

collections.namedtuple 非常高效且简洁,足以将我的解决方案简化为几行,而不是当前的 800 多行。我的 IDE 也将能够内省生成的类的可能成员。

缺点:namedtuple 的简洁性为 API 返回值的非常必要的文档留下了更少的空间。因此,使用不那么疯狂的 API,您可能会侥幸逃脱。 将类对象嵌套到命名元组中也感觉很奇怪,但这只是个人喜好。

我做了什么

所以最后我还是选择了我的第一个原创解决方案,添加了一些小细节,如果你觉得细节有趣,你可以看看source on github。

collections.abc

当我开始这个项目时,我的 Python 知识几乎一无所有,所以我使用了我对 Python 的了解(“一切都是字典”)并编写了类似的代码。例如:像字典一样工作的类,但在下面有一个文件结构(在pathlib 之前)。

在查看 python 的代码时,我注意到它们是如何通过 abstract base classes 实现和强制执行容器“特征”的,这听起来比在 python 中实际要复杂得多。

基础知识

以下确实非常基本,但我们将从那里开始。

from collections import Mapping, Sequence, Sized

class JsonWrapper(Sized):
    def __len__(self):
        return len(self._data)

    def __init__(self, json):
        self._data = json

    @property
    def raw(self): return self._data

我能想到的最基本的类,它只会让你在容器上调用len。如果您真的想打扰底层字典,也可以通过raw 获得只读访问权限。

那么为什么我要从 Sized 继承而不是从头开始和 def __len__ 那样呢?

    不覆盖__len__ 不会被python 解释器接受。我忘记了确切的时间,但是当您导入包含该类的模块时,您就不会在运行时被搞砸了。 虽然Sized 没有提供任何mixin 方法,但接下来的两个抽象确实提供了它们。我会在那里解释。

有了这个,我们在 JSON 列表和字典中只得到了两个基本案例。

列表

因此,对于我不得不担心的 API,我们并不总是确定我们得到了什么;所以我想要一种在初始化包装类时检查是否有列表的方法,主要是在更复杂的过程中提前中止而不是“对象没有成员”。

从序列派生将强制覆盖__getitem____len__(已在JsonWrapper 中实现)。

class JsonListWrapper(JsonWrapper, Sequence):
    def __init__(self, json_list):
        if type(json_list) is not list:
            raise TypeError('received type , expected list'.format(type(json_list)))
        super().__init__(json_list)

    def __getitem__(self, index):
        return self._data[index]

    def __iter__(self):
        raise NotImplementedError('__iter__')

    def get(self, index):
        try:
            return self._data[index]
        except Exception as e:
            print(index)
            raise e

所以您可能已经注意到,我选择不实现__iter__。 我想要一个产生类型化对象的迭代器,所以我的 IDE 能够自动完成。举例说明:

class CourseListResponse(JsonListWrapper):
    def __iter__(self):
        for course in self._data:
            yield self.Course(course)

    class Course(JsonDictWrapper):
        pass  # for now

实现Sequence的抽象方法,mixin方法__contains____reversed__indexcount是给你的,所以你不必担心可能的副作用。

字典

为了完成整理JSON的基本类型,这里是从Mapping派生的类:

class JsonDictWrapper(JsonWrapper, Mapping):
    def __init__(self, json_dict):
        super().__init__(json_dict)
        if type(self._data) is not dict:
            raise TypeError('received type , expected dict'.format(type(json_dict)))

    def __iter__(self):
        return iter(self._data)

    def __getitem__(self, key):
        return self._data[key]

    __marker = object()

    def get(self, key, default=__marker):
        try:
            return self._data[key]
        except KeyError:
            if default is self.__marker:
                raise
            else:
                return default

映射仅强制执行 __iter____getitem____len__。 为避免混淆:还有MutableMapping 将强制写入方法。但这既不需要也不需要。

排除了抽象方法后,python 提供了基于它们的 mixins __contains__keysitemsvaluesget__eq____ne__

我不知道为什么我选择覆盖 get mixin,我可能会在它回复给我时更新帖子。 __marker 用作检测是否未设置 default 关键字的后备。如果有人决定致电get(*args, default=None),否则您将无法检测到。

所以继续前面的例子:

class CourseListResponse(JsonListWrapper):
    # [...]    
    class Course(JsonDictWrapper):
        # Jn is just a class that contains the keys for JSON, so I only mistype once.
        @property
        def id(self): return self[Jn.id]

        @property
        def short_name(self): return self[Jn.short_name]

        @property
        def full_name(self): return self[Jn.full_name]

        @property
        def enrolled_user_count(self): return self[Jn.enrolled_user_count]
        # [...] you get the idea

属性提供对成员的只读访问权限,并且可以像函数定义一样记录在案。 虽然很冗长,但对于基本的访问器,您可以轻松地在编辑器中定义模板,因此编写起来不那么乏味。

属性还允许从幻数和可选的 JSON 返回值中抽象出来,以提供默认值,而不是在任何地方保护 KeyError

        @property
        def isdir(self): return 1 == self[Jn.is_dir]

        @property
        def time_created(self): return self.get(Jn.time_created, 0)

        @property
        def file_size(self): return self.get(Jn.file_size, -1)

        @property
        def author(self): return self.get(Jn.author, "")

        @property
        def license(self): return self.get(Jn.license, "")

类嵌套

在其他人中嵌套类似乎有点奇怪。 我选择这样做,因为 API 对具有不同属性的各种对象使用相同的名称,具体取决于您调用的远程函数。

另一个好处:新人可以很容易地理解返回的 JSON 的结构。

end of the file 包含嵌套类的各种别名,以便于从模块外部访问。

添加逻辑

现在我们已经封装了大部分返回值,我希望有更多与数据相关的逻辑,以增加一些便利。 似乎还需要将一些数据合并到一个更全面的树中,其中包含通过多个 API 调用收集的所有数据:

    获取所有“任务”。每个作业都包含许多提交,因此: for(assignment in assignments) 获取所有“提交” 将提交的内容合并到各自的作业中。 现在获取提交的成绩,等等...

我选择单独实现它们,所以我只是从“哑”访问器(full source)继承:

所以在this class

class Assignment(MoodleAssignment):
    def __init__(self, data, course=None):
        super().__init__(data)
        self.course = course
        self._submissions =   # accessed via submission.id
        self._grades =   # are accessed via user_id

这些属性进行合并

    @property
    def submissions(self): return self._submissions

    @submissions.setter
    def submissions(self, data):
        if data is None:
            self.submissions = 
            return
        for submission in data:
            sub = Submission(submission, assignment=self)
            if sub.has_content:
                self.submissions[sub.id] = sub

    @property
    def grades(self):
        return self._grades

    @grades.setter
    def grades(self, data):
        if data is None:
            self.grades = 
            return
        grades = [Grade(g) for g in data]
        for g in grades:
            self.grades[g.user_id] = g

这些实现了一些可以从数据中抽象出来的逻辑。

    @property
    def is_due(self):
        now = datetime.now()
        return now > self.due_date

    @property
    def due_date(self): return datetime.fromtimestamp(super().due_date)

虽然 setter 掩盖了争论,但它们的编写和使用都很好:所以这只是一个权衡。

警告:逻辑实现并不是我想要的,它不应该存在很多相互依赖。它源于我对 python 的了解不够,无法正确抽象并完成工作,因此我可以在乏味的情况下完成实际工作。 现在我知道了,可以做些什么:我看了一些意大利面,嗯……你知道那种感觉。

结论

将 JSON 封装到类中证明对我和项目结构非常有用,我对此非常满意。 该项目的其余部分很好并且可以工作,尽管有些部分很糟糕:D 谢谢大家的反馈,我会在附近回答问题和评论。

更新:2019-05-02

正如@RickTeachey 在 cmets 中指出的那样,pythons dataclasses (DCs) 也可以在这里使用。 而且我忘了在这里更新,因为我前段时间已经did that 并用pythons typing 功能扩展了它:D

原因:我越来越厌倦手动检查我从中抽象出来的 API 的文档是否正确,或者我的实现是否错误。 使用dataclasses.fields,我可以检查响应是否符合我的架构;现在我能够更快地找到外部 API 中的更改,因为在实例化的运行时会检查假设。

一旦__init__ 成功完成,DC 会提供一个__post_init__(self) 挂钩来进行一些后期处理。 Python 的类型提示仅用于为静态检查器提供提示,我构建了一个小系统,在初始化后阶段对数据类强制执行类型。

这里是 BaseDC,所有其他 DC 都从它继承(缩写)

import dataclasses as dc
@dataclass
class BaseDC:
    def _typecheck(self):
        for field in dc.fields(self):
            expected = field.type
            f = getattr(self, field.name)
            actual = type(f)
            if expected is list or expected is dict:
                log.warning(f'untyped list or dict in self.__class__.__qualname__: field.name')
            if expected is actual:
                continue
            if is_generic(expected):
                return self._typecheck_generic(expected, actual)
                # Subscripted generics cannot be used with class and instance checks
            if issubclass(actual, expected):
                continue
            print(f'mismatch field.name: should be: expected, but is actual')
            print(f'offending value: f')

    def __post_init__(self):
        for field in dc.fields(self):
            castfunc = field.metadata.get('castfunc', False)
            if castfunc:
                attr = getattr(self, field.name)
                new = castfunc(attr)
                setattr(self, field.name, new)
        if DEBUG:
            self._typecheck()

Fields 有一个附加属性,允许存储任意信息,我用它来存储转换响应值的函数;但稍后会详细介绍。

一个基本的响应包装器如下所示:

@dataclass
class DCcore_enrol_get_users_courses(BaseDC):
    id: int  # id of course
    shortname: str  # short name of course
    fullname: str  # long name of course
    enrolledusercount: int  # Number of enrolled users in this course
    idnumber: str  # id number of course
    visible: int  # 1 means visible, 0 means hidden course
    summary: Optional[str] = None  # summary
    summaryformat: Optional[int] = None  # summary format (1 = html, 0 = MOODLE, 2 = PLAIN or 4 = MARKDOWN)
    format: Optional[str] = None  # course format: weeks, topics, social, site
    showgrades: Optional[int] = None  # true if grades are shown, otherwise false
    lang: Optional[str] = None  # forced course language
    enablecompletion: Optional[int] = None  # true if completion is enabled, otherwise false
    category: Optional[int] = None  # course category id
    progress: Optional[float] = None  # Progress percentage
    startdate: Optional[int] = None  # Timestamp when the course start
    enddate: Optional[int] = None  # Timestamp when the course end

    def __str__(self): return f'self.fullname[0:39]:40 id:self.id:5d short: self.shortname'


core_enrol_get_users_courses = destructuring_list_cast(DCcore_enrol_get_users_courses)

只是列表的响应一开始就给我带来了麻烦,因为我无法使用普通的List[DCcore_enrol_get_users_courses] 对它们进行类型检查。 这就是destructuring_list_cast 为我解决这个问题的地方,这涉及到更多。我们正在进入高阶函数领域:

T = typing.TypeVar('T')
def destructuring_list_cast(cls: typing.Callable[[dict], T]) -> typing.Callable[[list], T]:
    def cast(data: list) -> List[T]:
        if data is None:
            return []

        if not isinstance(data, list):
            raise SystemExit(f'listcast expects a list, you sent: type(data)')
        try:
            return [cls(**entry) for entry in data]
        except TypeError as err:
            # here is more code that explains errors
            raise SystemExit(f'listcast for class cls failed:\nerr')

    return cast

这需要一个接受 dict 并返回类型为 T 的类实例的 Callable,这是您对构造函数或工厂的期望。 它返回一个接受列表的 Callable,这里是cast。 当您调用core_enrol_get_users_courses(response.json()) 时,return [cls(**entry) for entry in data] 通过构建数据类列表来完成所有工作。 (抛出 SystemExit 不好,但这是在上层处理的,所以它对我有用;我希望它快速失败。)

另一个用例是定义嵌套字段,然后响应是深度嵌套的:还记得BaseDC 中的field.metadata.get('castfunc', False) 吗?这就是这两个快捷方式的用武之地:

# destructured_cast_field
def dcf(cls):
    return dc.field(metadata='castfunc': destructuring_list_cast(cls))


def optional_dcf(cls):
    return dc.field(metadata='castfunc': destructuring_list_cast(cls), default_factory=list)

这些用于像这样的嵌套情况(见底部):

@dataclass
class core_files_get_files(BaseDC):
    @dataclass
    class parent(BaseDC):
        contextid: int
        # abbrev ...

    @dataclass
    class file(BaseDC):
        contextid: int
        component: str
        timecreated: Optional[int] = None  # Time created
        # abbrev ...

    parents: List[parent] = dcf(parent)
    files: Optional[List[file]] = optional_dcf(file)

【讨论】:

一个赞成票,因为这显然是一种学习劳动,我为你鼓掌。一个更正:在您尝试实例化类之前,不会发生 ABC 错误(因为不提供抽象方法)。另外,我建议查看the new typing.NamedTuple,其中provides a much nicer API 用于命名元组(包括编写允许文档的代码的能力)。 对于这个或未来的项目,我也会仔细查看dataclasses 另一个 nit 选择——这对于您的列表类型检查来说更加 Pythonic:if not isinstance(json_list, list):。既然您已经在使用 ABC,我非常建议您这样做:if not isinstance(json_list, Sequence):,这样更灵活。 @RickTeachey 提醒我为我的解决方案添加更新,我前段时间用数据类重写了它:D 好极了。 dclasses 规则。【参考方案2】:

您是否考虑过使用元类?

class JsonDataWrapper(object):
    def __init__(self, json_data):
        self._data = json_data

    def get(self, name):
        return self._data[name]

class JsonDataWrapperMeta(type):
    def __init__(self, name, base, dict):
        for mbr in self.members:
            prop = property(lambda self: self.get(mbr))
            setattr(self, mbr, prop)

# You can use the metaclass inside a class block
class Group(JsonDataWrapper):
    __metaclass__ = JsonDataWrapperMeta
    members = ['id', 'description', 'name', 'description_format']

# Or more programmatically
def jsonDataFactory(name, members):
    d = "members":members
    return JsonDataWrapperMeta(name, (JsonDataWrapper,), d)

Course = jsonDataFactory("Course", ["id", "name", "short_name"])

【讨论】:

我认为 IDE 对发现属性的支持将受到这种方法的限制。 我认为这个实现中可能存在与闭包相关的错误 @BiRico 是的,我做到了:虽然这对于快速脚本来说会很好,但我认为这不会使代码保持可读性。我也试过了,helm 和 pycharm 都猜不到类的属性(除了members),这违背了目的。 (你可能也得到了 Group.members 的错误想法,我试图在帖子中澄清这一点。)【参考方案3】:

在开发这样的 API 时,其中所有成员都是只读的(这意味着您不希望它们被覆盖,但可能仍然具有可变数据结构作为成员),我经常考虑使用 collections.namedtuple 一个硬-击败方法,除非我有很好的理由不这样做。它速度很快,并且只需要最少的代码。

from collections import namedtuple as nt

Group = nt('Group', 'id name shortname users')
g = Group(**json)

简单。

如果json 中的数据多于对象中将使用的数据,只需将其过滤掉:

g = Group(**k:v for k,v in json.items() if k in Group._fields)

如果您想要缺失数据的默认值,您也可以这样做:

Group.__new__.__defaults__ = (0, 'DefaultName', 'DefN', None)
# now this works:
g = Group()
# and now this will still work even if some keys are missing; 
g = Group(**k:v for k,v in json.items() if k in Group._fields)

使用上述设置默认值技术的一个陷阱:不要将成员之一的默认值设置为任何可变对象,例如list,因为它将是所有实例中相同的可变共享对象:

# don't do this:
Group.__new__.__defaults__(0, 'DefaultName', 'DefN', [])
g1 = Group()
g2 = Group()
g1.users.append(user1)
g2.users # output: [user1] <-- whoops!

相反,将其全部封装在一个不错的工厂中,为需要它们的成员实例化一个新的 list(或 dict 或任何用户定义的数据结构):

# jsonfactory.py

new_list = Object()

def JsonClassFactory(name, *args, defaults=None):
    '''Produces a new namedtuple class. Any members 
    intended to default to a blank list should be set to 
    the new_list object.
    '''
    cls = nt(name, *args)
    if defaults is not None:
        cls.__new__.__defaults__ = tuple(([] if d is new_list else d) for d in defaults)

现在给定一些 json 对象来定义你想要呈现的字段:

from jsonfactory import JsonClassFactory, new_list

MyJsonClass = JsonClassFactory(MyJsonClass, *json_definition,
                               defaults=(0, 'DefaultName', 'DefN', new_list))

然后像以前一样:

obj = MyJsonClass(**json)

或者,如果有额外的数据:

obj = MyJsonClass(**k:v for k,v in json.items() if k in MyJsonClass._fields)

如果您希望默认容器不是列表,这很简单 - 只需将 new_list 哨兵替换为您想要的任何哨兵。如果需要,您可以同时拥有多个哨兵。

如果您仍然需要额外的功能,您可以随时扩展您的MyJsonClass

class ExtJsonClass(MyJsonClass):
    __slots__ = () # optional- needed if you want the low memory benefits of namedtuple
    def __new__(cls, *args, **kwargs):
        self = super().__new__(cls, *args, **k:v for k,v in kwargs.items()
                                              if k in cls._fields)
        return self
    def add_user(self, user):
        self.users.append(user)

上面的__new__ 方法可以很好地解决丢失数据的问题。所以现在你总是可以这样做:

obj = ExtJsonClass(**json)

简单。

【讨论】:

【参考方案4】:

我自己是 python 的新手,如果我听起来很天真,请原谅。一种解决方案可能是使用__dict__,如下文所述:

https://www.safaribooksonline.com/library/view/python-cookbook-3rd/9781449357337/ch06s02.html

当然,如果类中的对象低于其他类并且需要序列化或反序列化,则此解决方案会产生问题。我很想听听这里的专家对此解决方案和不同限制的意见。

关于 jsonpickle 的任何反馈。

更新:

我刚刚看到您对序列化的反对意见以及您不喜欢它,因为一切都是运行时的。明白了。非常感谢。

下面是我为解决这个问题而编写的代码。有点牵强,但效果很好,我不必每次都添加 get/set !!!

import json

class JSONObject:
    exp_props = "id": "", "title": "Default"

    def __init__(self, d):
        self.__dict__ = d
        for key in [x for x in JSONObject.exp_props if x not in self.__dict__]:
            setattr(self, key, JSONObject.exp_props[key]) 

    @staticmethod
    def fromJSON(s):
        return json.loads(s, object_hook=JSONObject)

    def toJSON(self):
        return json.dumps(self.__dict__, indent=4)


s = '"name": "ACME", "shares": 50, "price": 490.1'
anObj = JSONObject.fromJSON(s)

print("Name - ".format(anObj.name))
print("Shares - ".format(anObj.shares))
print("Price - ".format(anObj.price))
print("Title - ".format(anObj.title))

sAfter = anObj.toJSON()

print("Type of dumps is ".format(type(sAfter)))
print(sAfter)

结果如下

Name - ACME
Shares - 50
Price - 490.1
Title - Default
Type of dumps is <type 'str'>

    "price": 490.1, 
    "title": "Default", 
    "name": "ACME", 
    "shares": 50, 
    "id": ""

【讨论】:

以上是关于围绕 JSON 数据包装一个 python 类,哪个更好?的主要内容,如果未能解决你的问题,请参考以下文章

mongodb 3.0.3 export json - 围绕数字创建 Number() 包装器

使用可变参数模板函数围绕类实现基于 pImpl 的包装器

如何围绕 C++ 代码编写 C 包装器以公开类方法

使用 SWIG 围绕 C++ 的 Python 包装器。参数类型无法识别

如何将围绕 C++ 函数的 R 包装器转换为 Python/Numpy

自制工具类struts返回json数据包装格式类