问题描述
现在,我有两套 python mixin,A 和 B。
设 (A_1,A_2,...,A_i,...)
和 (B_1,B_2,B_i,...)
分别是这些集合中的元素。
我想创建作为这两个 mixin 集之间的交叉的类,并且能够通过名称引用交叉的元素。在 python 中有一些很好的方法来做到这一点吗?具体来说,如果说我有 mixins
class A_1():
pass
class A_2():
pass
class B_1():
pass
class B_2():
pass
我希望能够生成类:
class A_1B_1(A_1,B_1):
pass
class A_1B_2(A_1,B_2):
pass
class A_2B_1(A_2,B_1):
pass
class A_2B_2(A_2,B_2):
pass
最好的方法是什么?
另外,假设我想向集合 B 添加一个新的 mixin,将其命名为 B_x
。有没有一种很好的方法来定义一些我可以在 B_x
上调用的函数来生成通过将所有 A_i
与 B_x
交叉而产生的类?即,是否有我可以编写的函数 f
使得
f(B_x,[A_1,A_n])
将导致能够生成
class A_1B_x(A_1,B_x):
pass
class A_2B_x(A_2,B_x):
pass
# ... and so on
值得注意的是,我希望能够在另一个文件中引用这些生成的类以按名称实例化它们。
四处寻找,我发现了 Dynamically mixin a base class to an instance in Python 之类的东西,但它们更改了一个实例,而不是生成类。
解决方法
使用 type(name,bases,dict)
是因为您基本上是在使用元类,但是以一种简化的方式。
class X:
pass
class Y:
pass
XY = type("xy",(X,Y),{})
print(XY.__mro__)
# (<class '__main__.xy'>,<class '__main__.X'>,<class '__main__.Y'>,<class 'object'>)
(__mro__
+ explanation)
并与 itertools.combinations()
结合以动态生成所有可能的基数(甚至 itertools.permutations()
或 itertools.product()
):
combinations([X,Y],2)
# <itertools.combinations object at 0x7fbbc2ced4a0>
list(combinations([X,2))
# [(<class '__main__.X'>,<class '__main__.Y'>)]
因此有点像这样,如果您将它们放在单独的包中:
import your_package
classes = [
getattr(your_package,cls)
for cls in dir(your_package)
if filter_somehow(getattr(your_package,cls))
]
combined = {}
for comb in combinations(classes,2):
name = "".join(cls.__name__ for cls in comb)
combined[name] = type(name,comb,{})
print(combined)
对于文件外部的引用,要么将该字典作为常量导入,要么简单地使用 globals()
而不是 combined
,您将在模块的命名空间中动态创建此类变量。
对于过滤 isinstance(<class>,type)
应该有帮助。
或者您可以通过例如过滤它们__name__
attribute) 或者在这样的包中没有任何其他东西 + 忽略所有 dunder parts:
# empty test.py file
import test
dir(test)
# ['__builtins__','__cached__','__doc__','__file__','__loader__','__name__','__package__','__spec__']
...或为所有类创建一些基类,然后使用 issubclass()
仅从 dir(package)
获取这些基类。
对于普通功能,这应该就足够了:
# f(B_x,[A_1,A_2,...,A_n])
def f(base,mixins):
classes = []
for mixin in mixins:
bases = (mixin,base)
name = "".join(cls.__name__ for cls in bases)
classes.append(type(name,{}))
return classes
# example
class Base: pass
class A: pass
class B: pass
class C: pass
f(Base,[A,B,C])
# [<class '__main__.ABase'>,<class '__main__.BBase'>,<class '__main__.CBase'>]
让我们稍微扩展一下:
# f(B_x,A_n])
from typing import Generic,Iterable
from itertools import combinations
def f(base: Generic,mixins: Iterable,base_count: int = 1):
classes = []
for mixin_comb in combinations(mixins,base_count):
bases = (*mixin_comb,{}))
return classes
f(Base,C],2)
# [<class '__main__.ABBase'>,<class '__main__.ACBase'>,<class '__main__.BCBase'>]