基于mixin扩展类的一个替代方案。
项目描述
管道工
管道工是mixin扩展类的一个替代方案。在动机中,给出了python子类继承的不完整列表限制和/或设计选择,以及管道工的解决方案。管道系统以代码示例详细描述。一些设计选择和正在进行中的讨论被解释。最后,在杂项中,您将找到术语、覆盖率报告、贡献者列表、变更和待办事项。所有非实验性功能都经过全面测试。
动机 - 子类化的局限性
管道是一种基于混入(mixin)的类扩展的替代方案,其动机源于Python子类化的局限性以及/或设计选择。
仅通过混入(mixin)的顺序来控制优先级。
混入(mixin)通常用于扩展具有预定义行为的类:第一个混入(mixin)上的属性会覆盖所有后续混入(mixin)和基类中具有相同名称的属性。
>>> class Mixin1(object):
... a = 1
>>> class Mixin2(object):
... a = 2
... b = 2
>>> Base = dict
>>> class MixedClass(Mixin1, Mixin2, Base):
... pass
>>> MixedClass.a
1
>>> MixedClass.b
2
>>> MixedClass.keys
<method 'keys' of 'dict' objects>
在链中的后期混入(mixin)无法优先于早期混入(mixin)。
解决方案:plumber提供了3个装饰器来允许更精细的优先级控制(default,override,finalize)。
无法为基类中缺失的部分提供默认值。
至少需要像字典一样提供存储的 __getitem__,__setitem__,__delitem__ 和 __iter__ 方法,所有其他字典方法都可以在这些方法之上构建。将存储转换为完整字典的混入(mixin)需要能够提供默认方法,如果基类没有提供(更高效)的实现,则采用这些方法。
解决方案:plumber提供了 default 装饰器,以允许此类默认值。
super - 链在类创建期间不会被验证。
可以使用 super 来构建方法链:Mixin1 在传递之前将键转换为小写,Mixin2 在返回之前将结果乘以2,并且两者都会在开始/停止时进行详细说明。
>>> class Mixin1(object):
... def __getitem__(self, key):
... print('Mixin1 start')
... key = key.lower()
... ret = super(Mixin1, self).__getitem__(key)
... print('Mixin1 stop')
... return ret
>>> class Mixin2(object):
... def __getitem__(self, key):
... print('Mixin2 start')
... ret = super(Mixin2, self).__getitem__(key)
... ret = 2 * ret
... print('Mixin2 stop')
... return ret
>>> Base = dict
>>> class MixedClass(Mixin1, Mixin2, Base):
... pass
>>> mc = MixedClass()
>>> mc['abc'] = 6
>>> mc['ABC']
Mixin1 start
Mixin2 start
Mixin2 stop
Mixin1 stop
12
dict.__getitem__ 是链的终点,因为它返回一个值,而不委托给链中的后续方法(使用 super)。如果没有终点,则在运行时而不是类创建期间引发 AttributeError。
>>> class Mixin1(object):
... def foo(self):
... super(Mixin1, self).foo()
>>> class MixedClass(Mixin1, Base):
... pass
>>> mc = MixedClass()
>>> mc.foo()
Traceback (most recent call last):
...
AttributeError: 'super' object has no attribute 'foo'
解决方案:Plumber提供了 plumb 装饰器,通过嵌套闭包构建类似的链。这些是在类创建期间创建和验证的。
没有条件性的 super - 链。
具有子类化的混入(mixin)必须与基类完全匹配,无法根据基类是否提供方法有条件性地挂钩到方法调用。
解决方案:Plumber提供了 plumbifexists 装饰器,它类似于 plumb,如果存在终点。
文档字符串(docstrings)不会被累积。
使用混入(mixin)的类的文档字符串不是从混入(mixin)的文档字符串构建的。
解决方案:Plumber通过一个特殊的标记 __plbnext__ 使文档字符串管道化,该标记被替换为下一个“混入”(mixin)的文档字符串。如果没有标记,则文档字符串会连接起来。
管道系统
plumber 元类根据管道行为中找到的说明创建管道类。首先,收集所有说明,然后分两个阶段应用它们:阶段1:扩展和阶段2:管道、文档字符串和可选的 zope.interfaces。存在一个类装饰器 plumbing,从plumber 1.3开始应优先使用它,而不是直接设置元类。
管道行为提供说明
管道行为对应于混入,但更强大和灵活。一个管道行为需要从 plumber.Behavior 继承,并声明属性以及如何使用它们的指令,以下是一个示例(更多内容稍后说明)。
>>> from plumber import Behavior
>>> from plumber import default
>>> class Behavior1(Behavior):
... a = default(True)
...
... @default
... def foo(self):
... return 42
>>> class Behavior2(Behavior):
... @default
... @property
... def bar(self):
... return 17
指令以行为赋值(a = default(None))或装饰器(@default)的形式给出。
管道声明定义了 plumber 作为元类,以及一个或多个从左到右处理的管道行为。此外,它还可以声明类似于每个正常类的属性,它们将被视为隐式的 finalize 指令(见阶段 1:扩展)。
>>> from plumber import plumbing
>>> Base = dict
>>> @plumbing(Behavior1, Behavior2)
... class Plumbing(Base):
...
... def foobar(self):
... return 5
结果是按照管道声明创建的管道类。
>>> plb = Plumbing()
>>> plb.a
True
>>> plb.foo()
42
>>> plb.bar
17
>>> plb.foobar()
5
>>> plb['a'] = 1
>>> plb['a']
1
管道类可以像正常类一样被继承。
>>> class Sub(Plumbing):
... a = 'Sub'
>>> Sub.a
'Sub'
>>> Sub().foo()
42
>>> Sub().bar
17
>>> Sub().foobar()
5
plumber 收集说明
管道声明通过 plumbing 装饰器提供行为列表。行为提供两个阶段要应用的指令
阶段 1
通过 default、override 和 finalize 扩展。这是通过在创建实际类之前操作类字典来实现的。
阶段 2
通过 plumb 和 plumbifexists 创建管道。
文档字符串的管道。
如果可用,从 zope.interface 实现的接口。
阶段 2 指令在类对象上应用。
管道迭代从左到右(行为顺序)的 Behavior 列表,并收集要应用的指令。同时保留所有已看到指令的历史。
>>> pprint(Plumbing.__plumbing_stacks__.stage1)
{'a': <default 'a' of <class 'Behavior1'> payload=True>,
'bar': <default 'bar' of <class 'Behavior2'> payload=<property object at ...>>,
'foo': <default 'foo' of <class 'Behavior1'> payload=<function Behavior1.foo at ...>>}
>>> pprint(Plumbing.__plumbing_stacks__.stage2)
{'__interfaces__': <_implements '__interfaces__' of None payload=()>}
>>> pprint(Plumbing.__plumbing_stacks__.history)
[<_implements '__interfaces__' of None payload=()>,
<default 'a' of <class 'Behavior1'> payload=True>,
<default 'foo' of <class 'Behavior1'> payload=<function Behavior1.foo at ...>>,
<_implements '__interfaces__' of None payload=()>,
<default 'bar' of <class 'Behavior2'> payload=<property object at ...>>]
每个指令属于一个类成员名称,如果存在,则会与该名称的先前指令进行比较。然后要么直接采用,要么丢弃、合并,或者引发 PlumbingCollision。以下将详细说明。
收集所有指令后,将考虑管道类和基类的声明来应用它们。
阶段 1 - 扩展
扩展阶段为第2阶段创建的管道创建端点。如果没有管道使用端点,它将像普通属性一样存在于管道类的字典中。
扩展装饰器
- finalize
finalize 是最强大的扩展指令。它将覆盖基类和其他扩展指令(override 和 default)的声明。作为管道声明行为声明的属性是隐含的 finalize 声明。对于同一属性名有两个 finalize 将会发生冲突并在类创建期间引发 PlumbingCollision。
- override
override 比finalize 弱,它将覆盖基类和 default 声明。对于同一属性名的两个 override 指令不会发生冲突,而是第一个将被使用。
- default
default 是最弱的扩展指令。它甚至不会覆盖基类的声明。第一个 default 比后续的 default 具有优先级。
交互 - finalize、管道声明和基类
在代码中。
>>> from plumber import finalize
>>> class Behavior1(Behavior):
... N = finalize('Behavior1')
...
>>> class Behavior2(Behavior):
... M = finalize('Behavior2')
>>> class Base(object):
... K = 'Base'
>>> @plumbing(Behavior1, Behavior2)
... class Plumbing(Base):
... L = 'Plumbing'
>>> for x in ['K', 'L', 'M', 'N']:
... print('%s from %s' % (x, getattr(Plumbing, x)))
K from Base
L from Plumbing
M from Behavior2
N from Behavior1
总结
K-Q: 由行为、管道类和基类定义的属性
f: finalize 声明
x: 管道类或基类上的声明
?: 基类声明不相关
Y: 选定的端点
冲突: 表示无效的组合,将引发 PlumbingCollision
Attr |
Behavior1 |
Behavior2 |
Plumbing |
Base |
ok? |
---|---|---|---|---|---|
K |
x |
||||
L |
x |
? |
|||
M |
f |
? |
|||
N |
f |
? |
|||
O |
f |
x |
? |
冲突 |
|
P |
f |
x |
? |
冲突 |
|
Q |
f |
f |
? |
冲突 |
冲突。
>>> class Behavior1(Behavior):
... O = finalize(False)
>>> @plumbing(Behavior1)
... class Plumbing(object):
... O = True
Traceback (most recent call last):
...
plumber.exceptions.PlumbingCollision:
Plumbing class
with:
<finalize 'O' of <class 'Behavior1'> payload=False>
>>> class Behavior2(Behavior):
... P = finalize(False)
>>> @plumbing(Behavior2)
... class Plumbing(object):
... P = True
Traceback (most recent call last):
...
plumber.exceptions.PlumbingCollision:
Plumbing class
with:
<finalize 'P' of <class 'Behavior2'> payload=False>
>>> class Behavior1(Behavior):
... Q = finalize(False)
>>> class Behavior2(Behavior):
... Q = finalize(True)
>>> @plumbing(Behavior1, Behavior2)
... class Plumbing(object):
... pass
Traceback (most recent call last):
...
plumber.exceptions.PlumbingCollision:
<finalize 'Q' of <class 'Behavior1'> payload=False>
with:
<finalize 'Q' of <class 'Behavior2'> payload=True>
交互 - override、管道声明和基类
在代码中。
>>> from plumber import override
>>> class Behavior1(Behavior):
... K = override('Behavior1')
... M = override('Behavior1')
>>> class Behavior2(Behavior):
... K = override('Behavior2')
... L = override('Behavior2')
... M = override('Behavior2')
>>> class Base(object):
... K = 'Base'
... L = 'Base'
... M = 'Base'
>>> @plumbing(Behavior1, Behavior2)
... class Plumbing(Base):
... K = 'Plumbing'
>>> for x in ['K', 'L', 'M']:
... print('%s from %s' % (x, getattr(Plumbing, x)))
K from Plumbing
L from Behavior2
M from Behavior1
总结
K-M: 由行为、管道类和基类定义的属性
e: override 声明
x: 管道类或基类上的声明
?: 基类声明不相关
Y: 选定的端点
Attr |
Behavior1 |
Behavior2 |
Plumbing |
Base |
---|---|---|---|---|
K |
e |
e |
x |
? |
L |
e |
? |
||
M |
e |
e |
? |
交互 - default、管道声明和基类
在代码中。
>>> class Behavior1(Behavior):
... N = default('Behavior1')
>>> class Behavior2(Behavior):
... K = default('Behavior2')
... L = default('Behavior2')
... M = default('Behavior2')
... N = default('Behavior2')
>>> class Base(object):
... K = 'Base'
... L = 'Base'
>>> @plumbing(Behavior1, Behavior2)
... class Plumbing(Base):
... L = 'Plumbing'
>>> for x in ['K', 'L', 'M', 'N']:
... print('%s from %s' % (x, getattr(Plumbing, x)))
K from Base
L from Plumbing
M from Behavior2
N from Behavior1
总结
K-N: 由行为、管道类和基类定义的属性
d = default 声明
x = 管道类或基类上的声明
? = 基类声明不相关
Y = 选定的端点
Attr |
Behavior1 |
Behavior2 |
Plumbing |
Base |
---|---|---|---|---|
K |
d |
x |
||
L |
d |
x |
? |
|
M |
d |
|||
N |
d |
d |
交互 - finalize 优于 override
在代码中。
>>> class Behavior1(Behavior):
... K = override('Behavior1')
... L = finalize('Behavior1')
>>> class Behavior2(Behavior):
... K = finalize('Behavior2')
... L = override('Behavior2')
>>> class Base(object):
... K = 'Base'
... L = 'Base'
>>> @plumbing(Behavior1, Behavior2)
... class Plumbing(Base):
... pass
>>> for x in ['K', 'L']:
... print('%s from %s' % (x, getattr(Plumbing, x)))
K from Behavior2
L from Behavior1
总结
K-L: 由行为、管道类和基类定义的属性
e = override 声明
f = finalize 声明
? = 基类声明不相关
Y = 选定的端点
Attr |
Behavior1 |
Behavior2 |
Plumbing |
Base |
---|---|---|---|---|
K |
e |
f |
? |
|
L |
f |
e |
? |
交互 - finalize 优于 default
在代码中。
>>> class Behavior1(Behavior):
... K = default('Behavior1')
... L = finalize('Behavior1')
>>> class Behavior2(Behavior):
... K = finalize('Behavior2')
... L = default('Behavior2')
>>> class Base(object):
... K = 'Base'
... L = 'Base'
>>> @plumbing(Behavior1, Behavior2)
... class Plumbing(Base):
... pass
>>> for x in ['K', 'L']:
... print('%s from %s' % (x, getattr(Plumbing, x)))
K from Behavior2
L from Behavior1
总结
K-L: 由行为、管道类和基类定义的属性
d = default 声明
f = finalize 声明
? = 基类声明不相关
Y = 选定的端点
Attr |
Behavior1 |
Behavior2 |
Plumbing |
Base |
---|---|---|---|---|
K |
d |
f |
? |
|
L |
f |
d |
? |
交互 - override 胜过 default ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~+~~~~~~~~~~~~~
在代码中。
>>> class Behavior1(Behavior):
... K = default('Behavior1')
... L = override('Behavior1')
>>> class Behavior2(Behavior):
... K = override('Behavior2')
... L = default('Behavior2')
>>> class Base(object):
... K = 'Base'
... L = 'Base'
>>> @plumbing(Behavior1, Behavior2)
... class Plumbing(Base):
... pass
>>> for x in ['K', 'L']:
... print('%s from %s' % (x, getattr(Plumbing, x)))
K from Behavior2
L from Behavior1
总结
K-L: 由行为、管道类和基类定义的属性
d = default 声明
e = override 声明
? = 基类声明不相关
Y = 选定的端点
Attr |
Behavior1 |
Behavior2 |
Plumbing |
Base |
---|---|---|---|---|
K |
d |
e |
? |
|
L |
e |
d |
? |
子类化行为
在代码中。
>>> class Behavior1(Behavior):
... J = default('Behavior1')
... K = default('Behavior1')
... M = override('Behavior1')
>>> class Behavior2(Behavior1):
... J = default('Behavior2') # overrides ``J`` of ``Behavior1``
... L = default('Behavior2')
... M = default('Behavior2') # this one wins, even if ``M`` on
... # superclass is ``override`` instruction.
... # due to ordinary inheritance behavior.
>>> @plumbing(Behavior2)
... class Plumbing(object):
... pass
>>> plb = Plumbing()
>>> plb.J
'Behavior2'
>>> plb.K
'Behavior1'
>>> plb.L
'Behavior2'
>>> plb.M
'Behavior2'
阶段 2 - 管道、文档字符串和 zope.interface 指令
在阶段1中设置了管道类的属性,这些属性可以作为第2阶段构建的管道的端点。管道管道对应于 super-链。行为、管道中的方法和属性中的文档字符串是累积的。管道器了解 zope.interface 并从行为中获取实现的接口(如果可以导入)。
一般管道
使用 plumb 和 plumbifexists 装饰器声明管道管道的元素
- plumb
标记一个方法作为管道管道的行为使用。此类管道方法的签名是 def foo(next_, self, *args, **kw)。通过 next_ 它传递下一个要调用的管道方法。 self 是管道类的实例,而不是行为。
- plumbifexists
类似于 plumb,但仅在存在端点时使用。
管道类用户不知道应该传递哪个next_。因此,在构建完管道后,为每个管道生成一个入口方法,该方法将第一个管道方法作为参数传递正确的next_。每个next_方法都是管道其余部分的入口。
管道按照行为顺序构建,跳过未定义具有相同属性名的管道元素的行为了。
+---+-----------+-----------+-----------+----------+ | | Behavior1 | Behavior2 | Behavior3 | ENDPOINT | +---+-----------+-----------+-----------+----------+ | | ---------------------------------> | | E | x | | | x | | N | <--------------------------------- | + T +-----------+-----------+-----------+----------+ | R | ----------> ---------------------> | | A | y | y | | y | | N | <---------- <--------------------- | + C +-----------+-----------+-----------+----------+ | E | | | ---------> | | S | | | z | z | | | | | <--------- | +---+-----------+-----------+-----------+----------+
方法管道
两个管道行为和一个dict作为基类。Behavior1在传递之前将键转换为小写,Behavior2在返回结果之前将它们相乘。
>>> from plumber import plumb
>>> class Behavior1(Behavior):
... @plumb
... def __getitem__(next_, self, key):
... print('Behavior1 start')
... key = key.lower()
... ret = next_(self, key)
... print ('Behavior1 stop')
... return ret
>>> class Behavior2(Behavior):
... @plumb
... def __getitem__(next_, self, key):
... print('Behavior2 start')
... ret = 2 * next_(self, key)
... print('Behavior2 stop')
... return ret
>>> Base = dict
>>> @plumbing(Behavior1, Behavior2)
... class Plumbing(Base):
... pass
>>> plb = Plumbing()
>>> plb['abc'] = 6
>>> plb['AbC']
Behavior1 start
Behavior2 start
Behavior2 stop
Behavior1 stop
12
管道管道需要端点。如果没有端点,则会引发AttributeError。
>>> class Behavior1(Behavior):
... @plumb
... def foo(next_, self):
... pass
>>> @plumbing(Behavior1)
... class Plumbing(object):
... pass
Traceback (most recent call last):
...
AttributeError: type object 'Plumbing' has no attribute 'foo'
如果没有端点并且行为不关心这一点,可以使用plumbifexists来仅在端点可用时进行管道连接。
>>> from plumber import plumbifexists
>>> class Behavior1(Behavior):
... @plumbifexists
... def foo(next_, self):
... pass
...
... @plumbifexists
... def bar(next_, self):
... return 2 * next_(self)
>>> @plumbing(Behavior1)
... class Plumbing(object):
...
... def bar(self):
... return 6
>>> hasattr(Plumbing, 'foo')
False
>>> Plumbing().bar()
12
这允许一种行为实现,例如为字典发送事件,用于实现__getitem__和__setitem__的读写字典以及仅实现__getitem__而不实现__setitem__的只读字典。
属性管道
只读属性的管道。
>>> class Behavior1(Behavior):
... @plumb
... @property
... def foo(next_, self):
... return 2 * next_(self)
>>> @plumbing(Behavior1)
... class Plumbing(object):
...
... @property
... def foo(self):
... return 3
>>> plb = Plumbing()
>>> plb.foo
6
可以扩展属性,以设置尚未设置的getter/setter/deleter。
>>> class Behavior1(Behavior):
... @plumb
... @property
... def foo(next_, self):
... return 2 * next_(self)
>>> class Behavior2(Behavior):
... def set_foo(self, value):
... self._foo = value
... foo = plumb(property(
... None,
... override(set_foo),
... ))
>>> @plumbing(Behavior1, Behavior2)
... class Plumbing(object):
...
... @property
... def foo(self):
... return self._foo
>>> plb = Plumbing()
>>> plb.foo = 4
>>> plb.foo
8
子类化行为
与第1阶段的指令不同,这些指令通过普通子类化的规则扩展了类并相互覆盖,管道指令是聚合的。
>>> class Behavior1(Behavior):
...
... @plumb
... def foo(next_, self):
... return 'Behavior1 ' + next_(self)
...
... @plumb
... def bar(next_, self):
... return 'Behavior1 ' + next_(self)
>>> class Behavior2(Behavior1):
...
... @plumb
... def foo(next_, self):
... return 'Behavior2 ' + next_(self)
>>> @plumbing(Behavior2)
... class Plumbing(object):
...
... def foo(self):
... return 'foo'
...
... def bar(self):
... return 'bar'
>>> plb = Plumbing()
>>> plb.foo()
'Behavior2 Behavior1 foo'
>>> plb.bar()
'Behavior1 bar'
在同一个管道中混合方法和属性是不可能的
在管道中,所有元素都需要是同一类型,无法混合属性和方法。
>>> class Behavior1(Behavior):
... @plumb
... def foo(next_, self):
... return next_(self)
>>> @plumbing(Behavior1)
... class Plumbing(object):
...
... @property
... def foo(self):
... return 5
Traceback (most recent call last):
...
plumber.exceptions.PlumbingCollision:
<plumb 'foo' of <class 'Behavior1'> payload=<function Behavior1.foo at 0x...>>
with:
<class 'Plumbing'>
类、方法和属性的文档字符串
管道声明和行为类的正常文档字符串、管道方法以及管道属性通过以管道声明开始的换行符连接,然后是反向顺序的行为。
>>> class P1(Behavior):
... """P1
... """
... @plumb
... def foo(self):
... """P1.foo
... """
... bar = plumb(property(None, None, None, 'P1.bar'))
>>> class P2(Behavior):
... @override
... def foo(self):
... """P2.foo
... """
... bar = plumb(property(None, None, None, 'P2.bar'))
>>> @plumbing(P1, P2)
... class Plumbing(object):
... """Plumbing
... """
... bar = property(None, None, None, 'Plumbing.bar')
>>> print(Plumbing.__doc__)
Plumbing
<BLANKLINE>
P1
<BLANKLINE>
>>> print(Plumbing.foo.__doc__)
P2.foo
<BLANKLINE>
P1.foo
<BLANKLINE>
>>> print(Plumbing.bar.__doc__)
Plumbing.bar
<BLANKLINE>
P2.bar
<BLANKLINE>
P1.bar
文档字符串的累积是一个实验性功能,可能会更改。
槽和管道
管道类可以像普通类一样有__slots__。
>>> class P1(Behavior):
... @default
... def somewhing_which_writes_to_foo(self, foo_val):
... self.foo = foo_val
>>> @plumbing(P1)
... class WithSlots(object):
... __slots__ = 'foo'
>>> WithSlots.__dict__['foo']
<member 'foo' of 'WithSlots' objects>
>>> ob = WithSlots()
>>> ob.somewhing_which_writes_to_foo('foo')
>>> assert(ob.foo == 'foo')
zope.interface(如果可用)
管道工不依赖于zope.interface,但它对此有所了解。这意味着它将尝试导入它,如果可用,将检查管道行为以实现接口,并将管道也实现它们。
>>> from zope.interface import Interface
>>> from zope.interface import implementer
一个接口类将作为管道的基类。
>>> class IBase(Interface):
... pass
>>> @implementer(IBase)
... class Base(object):
... pass
>>> IBase.implementedBy(Base)
True
两个具有相应接口的行为,一个基类也实现了接口。
>>> class IBehavior1(Interface):
... pass
>>> @implementer(IBehavior1)
... class Behavior1(Behavior):
... blub = 1
>>> class IBehavior2Base(Interface):
... pass
>>> @implementer(IBehavior2Base)
... class Behavior2Base(Behavior):
... pass
>>> class IBehavior2(Interface):
... pass
>>> @implementer(IBehavior2)
... class Behavior2(Behavior2Base):
... pass
>>> IBehavior1.implementedBy(Behavior1)
True
>>> IBehavior2Base.implementedBy(Behavior2Base)
True
>>> IBehavior2Base.implementedBy(Behavior2)
True
>>> IBehavior2.implementedBy(Behavior2)
True
基于Base的管道,使用Behavior1和Behavior2,并实现IPlumbingClass。
>>> class IPlumbingClass(Interface):
... pass
>>> @implementer(IPlumbingClass)
... @plumbing(Behavior1, Behavior2)
... class PlumbingClass(Base):
... pass
直接声明和继承的接口得到实现。
>>> IPlumbingClass.implementedBy(PlumbingClass)
True
>>> IBase.implementedBy(PlumbingClass)
True
行为实现的接口也得到实现。
>>> IBehavior1.implementedBy(PlumbingClass)
True
>>> IBehavior2.implementedBy(PlumbingClass)
True
>>> IBehavior2Base.implementedBy(PlumbingClass)
True
类的实例提供接口。
>>> plb = PlumbingClass()
>>> IPlumbingClass.providedBy(plb)
True
>>> IBase.providedBy(plb)
True
>>> IBehavior1.providedBy(plb)
True
>>> IBehavior2.providedBy(plb)
True
>>> IBehavior2Base.providedBy(plb)
True
管道元类钩子
如果编写需要在创建时进行类操作的管道行为,提供了一个装饰器来注册回调,这些回调在应用第1阶段和第2阶段指令之后执行。
>>> from plumber import plumber
>>> class IBehaviorInterface(Interface):
... pass
>>> @plumber.metaclasshook
... def test_metclass_hook(cls, name, bases, dct):
... if not IBehaviorInterface.implementedBy(cls):
... return
... cls.hooked = True
>>> @implementer(IBehaviorInterface)
... class MetaclassConsideredBehavior(Behavior):
... pass
>>> @plumbing(MetaclassConsideredBehavior)
... class Plumbing(object):
... pass
>>> Plumbing.hooked
True
杂项
命名法
- 管道工
元类,根据管道行为上声明的指令创建管道。指令由装饰器提供:default、override、finalize、plumb和plumbifexists。
- 管道
管道是装饰了plumbing装饰器的类,它将应用的行为作为参数传递,例如@plumbing(Behavior1, Behavior2)。除了行为之外,还会考虑基类上的声明以及请求管道的类。一旦创建,管道看起来就像任何其他类一样,可以像通常一样进行子类化。
- 管道行为
管道行为提供属性(函数、属性和平值)以及如何使用它们的说明。说明通过装饰器提供:default、override、finalize、plumb 和 plumbifexists(参见第1阶段:… 和第2阶段:…)。
- 管道管道
具有相同名称的管道方法/属性组成管道。入口和出口点具有正常方法的签名:def foo(self, *args, **kw)。管道管道是一系列嵌套闭包(参见 next_)。
- 入口(方法)
具有正常签名的类方法。即期望 self 作为第一个参数,用于进入管道。它是一个 next_ 函数。在类上声明具有相同名称的方法将被覆盖,但在管道中作为最内层方法,即出口点。
- ``next_`` 函数
next_ 函数用于在管道中调用下一个方法:在管道方法的情况下,它是一个包装器,将正确的下一个 next_ 作为第一个参数传递,在出口点的情况下,只是出口点方法本身。
- 出口点(方法)
在将入口方法设置在类上之前,从管道类中检索的方法。
如果您觉得缺少某些内容,请告诉我们或写一段简短的相关文本。
Python 版本
Python 2.7, 3.7+
可能与其他版本兼容(未经测试)
贡献者
Florian Friesdorf
Robert Niederreiter
Jens W. Klein
Marco Lempen
Attila Oláh
变更
1.7 (2022-03-17)
不再运行 Python 2.6 和 Python < 3.7 的测试套件。[rnix, 2022-03-16]
将 Bases 包装器替换为包含 derived_members 的 set,以避免多次迭代基类。[rnix, 2022-02-13]
解析行为时,只保留 Stacks 上的最新指令。[rnix, 2022-02-13]
简化 Stacks 对象。[rnix, 2022-02-13]
在 plumber.__new__ 中应用第1阶段和第2阶段指令,而不是在 plumber.__init__ 中应用第2阶段指令。[rnix, 2022-02-13]
1.6
使用原始字符串进行正则表达式。[rnix, 2020-05-28]
放弃对 Python 2.6 的支持。[rnix, 2019-03-25]
1.5
引入 plumber.metaclasshook 装饰器。[rnix, 2017-06-16]
1.4
不再使用“私有”模块名称。[rnix, 2017-05-21]
支持 Python 3。[rnix, 2017-05-18]
1.3.1
避免使用已废弃的 dict.has_key。[rnix, 2015-10-05]
1.3
引入 plumbing 装饰器。[rnix, 2014-07-31]
移除已废弃的 plumber.extend 和 plumber.Part。[rnix, 2014-07-31]
1.2
废弃 plumber.extend。使用 plumber.override 代替。[rnix, 2012-07-28]
废弃 plumber.Part。使用 plumber.Behavior 代替。[rnix, 2012-07-28]
1.1
使用 zope.interface.implementer 代替 zope.interface.implements。[rnix, 2012-05-18]
1.0
.. plbnext:: 而不是 .. plb_next:: [chaoflow 2011-02-02]
在 __new__ 中执行 stage1,在 __init__ 中执行 stage2,现在设置 __name__ 正确。[chaoflow 2011-01-25]
指令识别相等的指令。[chaoflow 2011-01-24]
基类中的指令现在像子类继承一样。[chaoflow 2011 [chaoflow 2011-01-24]
doctest 顺序现在为 plumbing 顺序:P1,P2,PlumbingClass,原来是 PlumbingClass,P1,P2 [chaoflow 2011-01-24]
将 docstring 指令合并到 plumb 中 [chaoflow 2011-01-24]
plumber 而不是 Plumber [chaoflow 2011-01-24]
管道方法不再是部分的类方法 [chaoflow 2011-01-24]
完整重写 [chaoflow 2011-01-22]
prt 而不是 cls [chaoflow, rnix 2011-01-19
default,extend,plumb [chaoflow, rnix 2011-01-19]
初始 [chaoflow,2011-01-04]
许可证
版权所有 (c) 2011-2021,BlueDynamics Alliance,奥地利,德国,瑞士 版权所有 (c) 2021-2022,Node贡献者 所有权利保留。
在满足以下条件的情况下,允许重新分发和使用源代码和二进制形式,无论是否修改
源代码的重新分发必须保留上述版权声明、本许可列表和以下免责声明。
二进制形式的重新分发必须在文档和/或其他提供的材料中重新生产上述版权声明、本许可列表和以下免责声明。
本软件由版权所有者和贡献者提供,“按原样”并提供,任何明示或暗示的保证,包括但不限于对适销性和特定用途的适用性的暗示保证,均被放弃。在任何情况下,版权所有者或贡献者不对任何直接、间接、偶然、特殊、示范性或后果性损害(包括但不限于替代货物或服务的采购;使用、数据或利润的损失;或业务中断)承担责任,无论这些损害是由于何种原因引起的,无论是在合同、严格责任还是在侵权(包括疏忽或其他)中产生的,即使被告知了此类损害的可能性。
项目详情
下载文件
下载适用于您平台文件的文件。如果您不确定选择哪一个,请了解更多关于 安装软件包 的信息。
源代码发行版
构建发行版
plumber-1.7.tar.gz 的哈希值
算法 | 哈希摘要 | |
---|---|---|
SHA256 | bde8d6323a8b3bdfc49ae3fa054c0801408a09a2c37e319ae9f0a4d49e4d5a23 |
|
MD5 | cd105aabc442e9ec91973b155f643698 |
|
BLAKE2b-256 | 94f6dba35af235a1fa6faa2dd84a67b0ecc49ac1b5c41eb44c354282367fb7f3 |
plumber-1.7-py3-none-any.whl 的哈希值
算法 | 哈希摘要 | |
---|---|---|
SHA256 | 4e734ce006b2b4367ce6f7e22c854f434f09eebfe9679b91758745c6184ddc11 |
|
MD5 | 58005c8e6c31e37d3272ac3da5f20193 |
|
BLAKE2b-256 | 8453e24f1e2e4263e9db0044c95c4302b7bc7f9f53c7cfe30bd19873199c02d1 |