【发布时间】:2021-08-27 04:11:32
【问题描述】:
我一直在玩元类来尝试对它们有一个好的感觉。我想出的一个非常简单(且毫无意义)的方法如下:
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 提供的方法以避免任何其他潜在的冲突
【问题讨论】:
-
可以复制这个。 Pandas 似乎在内部调用类型的 subclasses。如果在从 type 派生之前调用 print(),pandas 对此一无所知,因为 type 还没有 MappingMeta 作为子类。如果你调用 print 之后,type 现在有一个 MappingMeta 的子类。如果 pandas 递归调用 subclasses,也许您的 MappingMeta 类需要处理它?我对 Python 多重继承知之甚少,不知道遵循哪条通往 type.__subclasses__() 的路线:直接通过 type() 或通过 collections.abc.Mapping。也许第一个 panda 的调用正在缓存?
标签: python python-3.x pandas metaclass