pd.DataFrame(...) 在元类之前定义时导致 TypeError
pd.DataFrame(...) resulting in TypeError when a metaclass is defined before it
我一直在研究 metaclasses 以尝试对它们有一个好的感觉。我想出的一个非常简单(而且毫无意义)的方法如下:
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
class 单独运行时效果很好。
现在当我做类似的事情时:
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
奇怪的是(至少对我而言)如果我在 metaclass 的定义之前放置另一个 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
的超级 class 删除时,我没有收到错误 - 但是我没有得到我正在寻找的功能(本质上是使用 Test
作为字典)
我知道这可能不是 metaclasses 的最佳用途,但我很好奇是否有人知道发生了什么。
编辑
已接受的回复回答了问题,但只是为了提供一些关于 为什么 的上下文,我在 meta[ 中使用了 collections.abc.Mapping class =48=]。我这样写的原因是我可以像下面这样写 classes:
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
提供的方法以避免任何其他潜在的冲突
我意识到这可能会造成比它解决的更多的麻烦,但如果您不打算从 MappingMeta 派生任何东西,这似乎可以解决问题(因为您上面的代码运行)。
class MappingMeta(type,collections.abc.Mapping):
def __subclasses__(obj=None):
return []
问题在于 collections.abc
中的抽象基础 class 而不是 旨在用作元 classes。
虽然这可能是“可以想象的”,但需要确切地知道他在做什么:metaclasses 用于创建模板classes 本身,而 collections.abc 是基础 classes - 不是 metaclasses,提供了一个框架来实现通用集合模式工作量最少。
所以,发生的事情显然是在实例化 collections.abc.mapping
的实例时,Python 机器对所有已注册的映射类型进行了一些检查,而你的 quimera 妨碍了一切,并使事情休息。
干净的解决方案就是在您的 metaclass 上手动实现您想要的任何映射方法。即使你开始解决这个问题——就像@DS_London 的答案一样,Mapping mixin 实现了很多可能与 type
本身需要的机制冲突的方法和机制——而且你有那时无法控制。你得到的错误就是这样一个例子。
collections.abc 将免费提供给您的只是 __contains__, keys, items, values, get, __eq__, __ne__
- 您甚至可能不需要所有这些 - 所以只需实施您想要的任何东西,然后完成。
如果在定义元 class 之前实例化,Dataframe
不会中断的原因很简单:在此之前,collections.abc 虚拟子 class 的机制] 检查还没有被你的 metaclass.
毒化
我一直在研究 metaclasses 以尝试对它们有一个好的感觉。我想出的一个非常简单(而且毫无意义)的方法如下:
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
class 单独运行时效果很好。
现在当我做类似的事情时:
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
奇怪的是(至少对我而言)如果我在 metaclass 的定义之前放置另一个 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
的超级 class 删除时,我没有收到错误 - 但是我没有得到我正在寻找的功能(本质上是使用 Test
作为字典)
我知道这可能不是 metaclasses 的最佳用途,但我很好奇是否有人知道发生了什么。
编辑
已接受的回复回答了问题,但只是为了提供一些关于 为什么 的上下文,我在 meta[ 中使用了 collections.abc.Mapping class =48=]。我这样写的原因是我可以像下面这样写 classes:
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
提供的方法以避免任何其他潜在的冲突
我意识到这可能会造成比它解决的更多的麻烦,但如果您不打算从 MappingMeta 派生任何东西,这似乎可以解决问题(因为您上面的代码运行)。
class MappingMeta(type,collections.abc.Mapping):
def __subclasses__(obj=None):
return []
问题在于 collections.abc
中的抽象基础 class 而不是 旨在用作元 classes。
虽然这可能是“可以想象的”,但需要确切地知道他在做什么:metaclasses 用于创建模板classes 本身,而 collections.abc 是基础 classes - 不是 metaclasses,提供了一个框架来实现通用集合模式工作量最少。
所以,发生的事情显然是在实例化 collections.abc.mapping
的实例时,Python 机器对所有已注册的映射类型进行了一些检查,而你的 quimera 妨碍了一切,并使事情休息。
干净的解决方案就是在您的 metaclass 上手动实现您想要的任何映射方法。即使你开始解决这个问题——就像@DS_London 的答案一样,Mapping mixin 实现了很多可能与 type
本身需要的机制冲突的方法和机制——而且你有那时无法控制。你得到的错误就是这样一个例子。
collections.abc 将免费提供给您的只是 __contains__, keys, items, values, get, __eq__, __ne__
- 您甚至可能不需要所有这些 - 所以只需实施您想要的任何东西,然后完成。
如果在定义元 class 之前实例化,Dataframe
不会中断的原因很简单:在此之前,collections.abc 虚拟子 class 的机制] 检查还没有被你的 metaclass.