在Python中,可以在不使用继承的情况下实现mixin行为吗?
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 行为。
Is there a reasonable way in Python to implement mixin behavior similar to that found in Ruby -- that is, without using inheritance?
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)
I had a vague idea to do this with a class decorator, but my attempts led to confusion. Most of my searches on the topic have led in the direction of using inheritance (or in more complex scenarios, multiple inheritance) to achieve mixin behavior.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(8)
输出:
output:
您可以将方法添加为函数:
You can add the methods as functions:
我对 Python 不太熟悉,但根据我对 Python 元编程的了解,您实际上可以像在 Ruby 中一样完成它。
在 Ruby 中,模块基本上由两部分组成:指向方法字典的指针和指向常量字典的指针。一个类由三部分组成:指向方法字典的指针、指向常量字典的指针和指向超类的指针。
当您将模块
M
混合到类C
中时,会发生以下情况:α
(这称为 include class)α
的方法字典和常量字典指针设置为等于M
的α
的超类指针设置为等于C
的C
的超类指针设置为α
换句话说:一个与mixin 被注入到继承层次结构中。所以,Ruby 实际上确实使用继承来进行 mixin 组合。
我在上面遗漏了一些细节:首先,该模块实际上并没有作为
C
的超类插入,而是作为C
的超类插入'(这是C
的单例类)超类。其次,如果 mixin 本身已混合在其他 mixin 中,那么这些也会被包装到直接插入到α
上方的伪类中,并且此过程会递归地应用,在如果混合的 mixin 又具有 mixin。基本上,整个 mixin 层次结构被展平为一条直线并拼接到继承链中。
AFAIK,Python 实际上允许您在事后更改类的超类(Ruby 不允许您这样做),并且它还允许您访问类的
dict< /code> (同样,这在 Ruby 中是不可能的),所以您应该能够自己实现它。
I am not that familiar with Python, but from what I know about Python metaprogramming, you could actually do it pretty much the same way it is done in Ruby.
In Ruby, a module basically consists of two things: a pointer to a method dictionary and a pointer to a constant dictionary. A class consists of three things: a pointer to a method dictionary, a pointer to a constant dictionary and a pointer to the superclass.
When you mix in a module
M
into a classC
, the following happens:α
is created (this is called an include class)α
's method dictionary and constant dictionary pointers are set equal toM
'sα
's superclass pointer is set equal toC
'sC
's superclass pointer is set toα
In other words: a fake class which shares its behavior with the mixin is injected into the inheritance hierarchy. So, Ruby actually does use inheritance for mixin composition.
I left out a couple of subleties above: first off, the module doesn't actually get inserted as
C
's superclass, it gets inserted asC
's superclasses' (which isC
's singleton class) superclass. And secondly, if the mixin itself has mixed in other mixins, then those also get wrapped into fake classes which get inserted directly aboveα
, and this process is applied recursively, in case the mixed in mixins in turn have mixins.Basically, the whole mixin hierarchy gets flattened into a straight line and spliced into the inheritance chain.
AFAIK, Python actually allows you to change a class's superclass(es) after the fact (something which Ruby does not allow you to do), and it also gives you access to a class's
dict
(again, something that is impossible in Ruby), so you should be able to implement this yourself.编辑:修复了可能(并且可能应该)被解释为错误的内容。现在它构建一个新的字典,然后从类的字典中更新它。这可以防止 mixin 覆盖直接在类上定义的方法。
该代码尚未经过测试,但应该可以工作。我的 ATM 很忙,所以我稍后会测试它。除了语法错误之外,它运行良好。回想起来,我决定我不喜欢它(即使在我进一步改进之后)并且更喜欢 我的其他解决方案,即使它更复杂。该测试代码也适用于此处,但我不会重复它。您可以使用元类工厂:
然后像这样使用它:
EDIT: Fixed what could (and probably should) be construed as a bug. Now it builds a new dict and then updates that from the class's dict. This prevents mixins from overwriting methods that are defined directly on the class.
The code is still untested but should work. I'm busy ATM so I'll test it later.It worked fine except for a syntax error. In retrospect, I decided that I don't like it (even after my further improvements) and much prefer my other solution even if it is more complicated. The test code for that one applies here as well but I wont duplicate it.You could use a metaclass factory:
then use it like:
这是基于 ruby 中的完成方式 由 Jörg W Mittag 解释。
if __name__=='__main__'
之后的所有代码都是测试/演示代码。实际上只有 13 行真正的代码。This one is based on the way it's done in ruby as explained by Jörg W Mittag. All of the wall of code after
if __name__=='__main__'
is test/demo code. There's actually only 13 lines of real code to it.您可以装饰类
__getattr__
来签入 mixin。问题是 mixin 的所有方法总是需要 mixin 类型的对象作为其第一个参数,因此您还必须装饰 __init__ 来创建 mixin 对象。我相信您可以使用 类装饰器 来实现此目的。You could decorate the classes
__getattr__
to check in the mixin. The problem is that all methods of the mixin would always require an object the type of the mixin as their first parameter, so you would have to decorate__init__
as well to create a mixin-object. I believe you could achieve this using a class decorator.这基本上使用 Mixin 类作为容器来保存临时函数(而不是方法),这些函数的行为类似于方法,将对象实例(self)作为第一个参数。
__getattr__
会将缺失的调用重定向到这些类似方法的函数。这通过了您的简单测试,如下所示。但我不能保证它会做你想做的所有事情。进行更彻底的测试以确定。
This basically uses the
Mixin
class as a container to hold ad-hoc functions (not methods) that behave like methods by taking an object instance (self) as the first argument.__getattr__
will redirect missing calls to these methods-alike functions.This passes your simple tests as shown below. But I cannot guarantee it will do all the things you want. Make more thorough test to make sure.
作品?这似乎是处理此问题的最简单方法:要么将对象包装在装饰器中,要么将方法作为对象导入到类定义本身中。这就是我通常所做的:将我想要在类之间共享的方法放在一个文件中,然后导入该文件。如果我想覆盖某些行为,我会导入一个修改后的文件,其方法名称与对象名称相同。虽然有点草率,但确实有效。
例如,如果我想要此文件 (bedg.py) 中的
init_covers
行为,在我的 bar 类文件中,我会执行以下操作:
import bedg as foo
然后如果我想要在继承 bar 的另一个类中更改我的 foo 行为,我写
import bild as foo
就像我说的,它很草率。
Composition? It seems like that would be the simplest way to handle this: either wrap your object in a decorator or just import the methods as an object into your class definition itself. This is what I usually do: put the methods that I want to share between classes in a file and then import the file. If I want to override some behavior I import a modified file with the same method names as the same object name. It's a little sloppy, but it works.
For example, if I want the
init_covers
behavior from this file (bedg.py)In my bar class file I would do:
import bedg as foo
and then if I want to change my foo behaviors in another class that inherited bar, I write
import bild as foo
Like I say, it is sloppy.