在Python中是否有合理的方法来实现类似于Ruby中的mixin行为 - 也就是说,不使用继承?
class Mixin(object):
def b(self): print "b()"
def c(self): print "c()"
class Foo(object):
# Somehow mix in the behavior of the Mixin class,
# so that all of the methods below will run and
# the issubclass() test will be False.
def a(self): print "a()"
f = Foo()
f.a()
f.b()
f.c()
print issubclass(Foo, Mixin)
我有一个模糊的想法,与类装饰师这样做,但我的尝试导致混乱。我对该主题的大多数搜索都导致了使用继承(或者在更复杂的场景中,多重继承)来实现mixin行为。
答案 0 :(得分:9)
def mixer(*args):
"""Decorator for mixing mixins"""
def inner(cls):
for a,k in ((a,k) for a in args for k,v in vars(a).items() if callable(v)):
setattr(cls, k, getattr(a, k).im_func)
return cls
return inner
class Mixin(object):
def b(self): print "b()"
def c(self): print "c()"
class Mixin2(object):
def d(self): print "d()"
def e(self): print "e()"
@mixer(Mixin, Mixin2)
class Foo(object):
# Somehow mix in the behavior of the Mixin class,
# so that all of the methods below will run and
# the issubclass() test will be False.
def a(self): print "a()"
f = Foo()
f.a()
f.b()
f.c()
f.d()
f.e()
print issubclass(Foo, Mixin)
输出:
a()
b()
c()
d()
e()
False
答案 1 :(得分:4)
您可以将这些方法添加为函数:
Foo.b = Mixin.b.im_func
Foo.c = Mixin.c.im_func
答案 2 :(得分:3)
编辑:修正了可能(并且可能应该)被解释为错误的内容。现在它构建一个新的dict,然后从类的dict中更新它。这可以防止mixins覆盖直接在类上定义的方法。 代码仍然未经测试但应该有效。我忙于ATM,所以我稍后会测试它。除了语法错误外,它工作正常。回想起来,我认为我不喜欢它(即使在我进一步改进之后)并且更喜欢my other solution,即使它更复杂。这个测试代码也适用于此,但我不会复制它。
您可以使用元类工厂:
import inspect
def add_mixins(*mixins):
Dummy = type('Dummy', mixins, {})
d = {}
for mixin in reversed(inspect.getmro(Dummy)):
d.update(mixin.__dict__)
class WithMixins(type):
def __new__(meta, classname, bases, classdict):
d.update(classdict)
return super(WithMixins, meta).__new__(meta, classname, bases, d)
return WithMixins
然后使用它:
class Foo(object):
__metaclass__ = add_mixins(Mixin1, Mixin2)
# rest of the stuff
答案 3 :(得分:3)
我对Python并不熟悉,但根据我对Python元编程的了解,你实际上可以像在Ruby中那样完成它。
在Ruby中,模块基本上由两部分组成:指向方法字典的指针和指向常量字典的指针。一个类由三部分组成:指向方法字典的指针,指向常量字典的指针和指向超类的指针。
当您将模块M
混合到类C
中时,会发生以下情况:
α
(这称为包含类)α
的方法字典和常量字典指针设置为M
的α
的超类指针设置为C
的C
的超类指针设置为α
换句话说:与mixin共享其行为的假类被注入到继承层次结构中。所以,Ruby实际上 使用继承来进行mixin组合。
我遗漏了以上几个问题:首先,模块实际上并没有作为C
的超类插入,而是作为C
的超类插入(这是{ {1}}的单例类)超类。其次,如果mixin本身在其他mixins中混合了,那么那些也会被包装到伪类中,这些类直接插入到C
之上,并且这个过程是递归应用的,以防混合在mixins中又有mixins。
基本上,整个mixin层次结构变平直,并拼接到继承链中。
AFAIK,Python实际上允许你在事实之后改变一个类的超类(Ruby使不允许你做的事情),并且它还允许你访问类的{{ 1}}(再次,Ruby中不可能的事情),所以你应该能够自己实现它。
答案 4 :(得分:3)
这个基于它在ruby中的行为explained by Jörg W Mittag。 if __name__=='__main__'
之后的所有代码墙都是测试/演示代码。实际上只有13行真实代码。
import inspect
def add_mixins(*mixins):
Dummy = type('Dummy', mixins, {})
d = {}
# Now get all the class attributes. Use reversed so that conflicts
# are resolved with the proper priority. This rules out the possibility
# of the mixins calling methods from their base classes that get overridden
# using super but is necessary for the subclass check to fail. If that wasn't a
# requirement, we would just use Dummy above (or use MI directly and
# forget all the metaclass stuff).
for base in reversed(inspect.getmro(Dummy)):
d.update(base.__dict__)
# Create the mixin class. This should be equivalent to creating the
# anonymous class in Ruby.
Mixin = type('Mixin', (object,), d)
class WithMixins(type):
def __new__(meta, classname, bases, classdict):
# The check below prevents an inheritance cycle from forming which
# leads to a TypeError when trying to inherit from the resulting
# class.
if not any(issubclass(base, Mixin) for base in bases):
# This should be the the equivalent of setting the superclass
# pointers in Ruby.
bases = (Mixin,) + bases
return super(WithMixins, meta).__new__(meta, classname, bases,
classdict)
return WithMixins
if __name__ == '__main__':
class Mixin1(object):
def b(self): print "b()"
def c(self): print "c()"
class Mixin2(object):
def d(self): print "d()"
def e(self): print "e()"
class Mixin3Base(object):
def f(self): print "f()"
class Mixin3(Mixin3Base): pass
class Foo(object):
__metaclass__ = add_mixins(Mixin1, Mixin2, Mixin3)
def a(self): print "a()"
class Bar(Foo):
def f(self): print "Bar.f()"
def test_class(cls):
print "Testing {0}".format(cls.__name__)
f = cls()
f.a()
f.b()
f.c()
f.d()
f.e()
f.f()
print (issubclass(cls, Mixin1) or
issubclass(cls, Mixin2) or
issubclass(cls, Mixin3))
test_class(Foo)
test_class(Bar)
答案 5 :(得分:0)
你可以装饰班级__getattr__
来检查混音。问题是mixin的所有方法总是需要一个对象作为第一个参数mixin的类型,所以你必须装饰__init__
以创建一个mixin-object。我相信你可以使用class decorator来实现这一点。
答案 6 :(得分:0)
from functools import partial
class Mixin(object):
@staticmethod
def b(self): print "b()"
@staticmethod
def c(self): print "c()"
class Foo(object):
def __init__(self, mixin_cls):
self.delegate_cls = mixin_cls
def __getattr__(self, attr):
if hasattr(self.delegate_cls, attr):
return partial(getattr(self.delegate_cls, attr), self)
def a(self): print "a()"
f = Foo(Mixin)
f.a()
f.b()
f.c()
print issubclass(Foo, Mixin)
这基本上使用Mixin
类作为容器,通过将对象实例(self)作为第一个参数来保存行为类似于方法的 ad-hoc 函数(而不是方法)。 __getattr__
会将缺少的调用重定向到这些类似方法的函数。
这将通过您的简单测试,如下所示。但我不能保证它会做你想要的所有事情。进行更彻底的测试以确保。
$ python mixin.py
a()
b()
c()
False
答案 7 :(得分:0)
组合物?看起来这是处理这个问题的最简单方法:将对象包装在装饰器中,或者只是将方法作为对象导入到类定义本身中。这就是我通常做的事情:将我想要在类之间共享的方法放在文件中,然后导入文件。如果我想覆盖某些行为,我导入一个修改过的文件,其方法名称与同一对象名称相同。这有点草率,但它确实有效。
例如,如果我想要此文件中的init_covers
行为(bedg.py)
import cove as cov
def init_covers(n):
n.covers.append(cov.Cover((set([n.id]))))
id_list = []
for a in n.neighbors:
id_list.append(a.id)
n.covers.append(cov.Cover((set(id_list))))
def update_degree(n):
for a in n.covers:
a.degree = 0
for b in n.covers:
if a != b:
a.degree += len(a.node_list.intersection(b.node_list))
在我的bar类文件中,我会这样做:import bedg as foo
然后如果我想在继承bar的另一个类中更改我的foo行为,我会写
import bild as foo
就像我说的那样,它很草率。