首页 > 解决方案 > pd.DataFrame(...) 在它之前定义元类时导致 TypeError

问题描述

我一直在玩元类来尝试对它们有一个好的感觉。我想出的一个非常简单(且毫无意义)的方法如下:

class MappingMeta(type, collections.abc.Mapping):
    def __setattr__(self, *args, **kwargs):
        raise RuntimeError("Can not set attributes of Mapping type")

    def __call__(self, *args, **kwargs):
        raise RuntimeError("Can not directly instantiate Mapping type")

    def __getitem__(self, value):
        return getattr(self, value)

    def __iter__(self):
        return (k for k in vars(self) if not k.startswith("_"))

    def __len__(self):
        return sum(1 for _ in self)


class Mapping(metaclass=MappingMeta):
    pass


class Test(Mapping):
    x = 1
    y = 2

该课程在孤立的情况下完美运行。

现在当我做类似的事情时:

import pandas as pd
class MappingMeta(type, collections.abc.Mapping):
    ... # same as above

class Mapping(metaclass=MappingMeta):
    pass


class Test(Mapping):
    x = 1
    y = 2

print(pd.DataFrame({'x': [1, 2]}))

我收到以下错误:

Traceback (most recent call last):
  File "metamapping.py", line 22, in <module>
    print(pd.DataFrame({"x": [1, 2]}))
  File "/Users/rnlondono/miniconda3/envs/p38/lib/python3.8/site-packages/pandas/core/frame.py", line 803, in __repr__
    self.to_string(
  File "/Users/rnlondono/miniconda3/envs/p38/lib/python3.8/site-packages/pandas/core/frame.py", line 939, in to_string
    return fmt.DataFrameRenderer(formatter).to_string(
  File "/Users/rnlondono/miniconda3/envs/p38/lib/python3.8/site-packages/pandas/io/formats/format.py", line 1031, in to_string
    string = string_formatter.to_string()
  File "/Users/rnlondono/miniconda3/envs/p38/lib/python3.8/site-packages/pandas/io/formats/string.py", line 23, in to_string
    text = self._get_string_representation()
  File "/Users/rnlondono/miniconda3/envs/p38/lib/python3.8/site-packages/pandas/io/formats/string.py", line 38, in _get_string_representation
    strcols = self._get_strcols()
  File "/Users/rnlondono/miniconda3/envs/p38/lib/python3.8/site-packages/pandas/io/formats/string.py", line 29, in _get_strcols
    strcols = self.fmt.get_strcols()
  File "/Users/rnlondono/miniconda3/envs/p38/lib/python3.8/site-packages/pandas/io/formats/format.py", line 519, in get_strcols
    strcols = self._get_strcols_without_index()
  File "/Users/rnlondono/miniconda3/envs/p38/lib/python3.8/site-packages/pandas/io/formats/format.py", line 752, in _get_strcols_without_index
    if not is_list_like(self.header) and not self.header:
  File "pandas/_libs/lib.pyx", line 1033, in pandas._libs.lib.is_list_like
  File "pandas/_libs/lib.pyx", line 1038, in pandas._libs.lib.c_is_list_like
  File "/Users/rnlondono/miniconda3/envs/p38/lib/python3.8/abc.py", line 98, in __instancecheck__
    return _abc_instancecheck(cls, instance)
  File "/Users/rnlondono/miniconda3/envs/p38/lib/python3.8/abc.py", line 102, in __subclasscheck__
    return _abc_subclasscheck(cls, subclass)
  File "/Users/rnlondono/miniconda3/envs/p38/lib/python3.8/abc.py", line 102, in __subclasscheck__
    return _abc_subclasscheck(cls, subclass)
  File "/Users/rnlondono/miniconda3/envs/p38/lib/python3.8/abc.py", line 102, in __subclasscheck__
    return _abc_subclasscheck(cls, subclass)
  [Previous line repeated 1 more time]
TypeError: descriptor '__subclasses__' of 'type' object needs an argument

奇怪的是(至少对我来说)如果我print(pd.DataFrame({'x': [1, 2]}))在元类的定义之前放另一个,整个事情就可以了。出于某种原因,它必须是印刷品……

import pandas as pd
print(pd.DataFrame({'x': [1, 2]}))

class MappingMeta(type, collections.abc.Mapping):
    ... # same as above

class Mapping(metaclass=MappingMeta):
    pass


class Test(Mapping):
    x = 1
    y = 2

print(pd.DataFrame({'x': [1, 2]}))

因此,作为一个 hacky 解决方案,我绝对可以使用它......

另外,当我将其collections.abc.Mapping作为超类删除时,MappingMeta我没有收到错误消息-但是我没有得到我正在寻找的功能(基本上Test用作字典)

我知道这可能不是元类的最佳用途,但我只是好奇是否有人对正在发生的事情有任何想法。

编辑

接受的响应回答了这个问题,但只是为了提供一些关于我为什么在元类中使用 collections.abc.Mapping 类的上下文。我这样写的原因是我可以编写如下类:

class Test(Mapping): 
    x = 1
    y = 2
    z = 3

'x' in Test           # True
list(Test.items())    # [('x', 1), ('y', 2), ('z', 3)]
{**Test}              # {'x': 1, 'y': 2, 'z': 3}

虽然接受的答案肯定回答了这个问题,但我最终决定只实施提供的方法collections.abc.Mapping以避免任何其他潜在的冲突

标签: pythonpython-3.xpandasmetaclass

解决方案


问题是其中的抽象基类collections.abc并非旨在用作元类。

尽管这可能是“可以想象的”,但需要确切地知道他在做什么:元类用于模板类本身的创建,而 collections.abc 是基- 不是元类,提供了一个框架来实现公共集合工作量最小的模式。

因此,显然在实例化 的实例时collections.abc.mapping,Python 机器会对所有已注册的映射类型进行一些检查,而您的 quimera 会妨碍您,并使事情中断。

干净的解决方案只是手动在您的元类上实现您想要的任何映射方法。即使您解决了这个问题 - 就像@DS_London 的答案一样,Mapping mixin 实现了许多可能与type自身需要的机制发生冲突的方法和机制 - 而且您无法控制。你得到的错误就是这样一个例子。

collections.abc 将免费为您提供的只是 __contains__, keys, items, values, get, __eq__, __ne__-您甚至可能不需要所有这些-所以只需实现您想要的任何东西,然后完成。

Dataframe如果在定义元类之前实例化并不会中断,原因很简单:在此之前,用于虚拟子类检查的 collections.abc 机制还没有被元类毒害。


推荐阅读