跳转到主要内容

基于mixin扩展类的一个替代方案。

项目描述

Latest PyPI version Number of PyPI downloads Test plumber

管道工

管道工是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个装饰器来允许更精细的优先级控制(defaultoverridefinalize)。

无法为基类中缺失的部分提供默认值。

至少需要像字典一样提供存储的 __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

  • 通过 defaultoverridefinalize 扩展。这是通过在创建实际类之前操作类字典来实现的。

阶段 2

  • 通过 plumbplumbifexists 创建管道。

  • 文档字符串的管道。

  • 如果可用,从 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 是最强大的扩展指令。它将覆盖基类和其他扩展指令(overridedefault)的声明。作为管道声明行为声明的属性是隐含的 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 并从行为中获取实现的接口(如果可以导入)。

一般管道

使用 plumbplumbifexists 装饰器声明管道管道的元素

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的管道,使用Behavior1Behavior2,并实现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

杂项

命名法

管道工

元类,根据管道行为上声明的指令创建管道。指令由装饰器提供:defaultoverridefinalizeplumbplumbifexists

管道

管道是装饰了plumbing装饰器的类,它将应用的行为作为参数传递,例如@plumbing(Behavior1, Behavior2)。除了行为之外,还会考虑基类上的声明以及请求管道的类。一旦创建,管道看起来就像任何其他类一样,可以像通常一样进行子类化。

管道行为

管道行为提供属性(函数、属性和平值)以及如何使用它们的说明。说明通过装饰器提供:defaultoverridefinalizeplumbplumbifexists(参见第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_membersset,以避免多次迭代基类。[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.extendplumber.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 (40.4 kB 查看哈希值)

上传时间 源代码

构建发行版

plumber-1.7-py3-none-any.whl (25.3 kB 查看哈希值)

上传时间 Python 3

由以下支持

AWS AWS 云计算和安全赞助商 Datadog Datadog 监控 Fastly Fastly CDN Google Google 下载分析 Microsoft Microsoft PSF 赞助商 Pingdom Pingdom 监控 Sentry Sentry 错误日志 StatusPage StatusPage 状态页面