这是基本的组合/委托。这里的规范方法是使被委托者私有(在 Python 中:在它的名称前加上一个前导下划线 - 这只是一个约定,但它是一个非常强大的约定)并编写公共方法来委托操作,即:
# naming convention: class names should be CamelCase
class Rect():
def __init__(self, x, y, w, h):
# naming convention: the single leading underscore
# denotes an implementation attribute or method
self._pos = pygame.Rect(x, y, w, h)
# explicit delegation
def move(self, x, y):
self._pos.move(x, y)
r = Rect(10,10,10,10)
r.move(10,10)
现在如果你有几十个方法要委托,Python 还提供了一个自动委托的钩子:the __getattr__(self, name) magic method:
class Rect():
def __init__(self, x, y, w, h):
self._pos = pygame.Rect(x, y, w, h)
# implicit delegation:
def __getattr__(self, name):
try:
return getattr(self._pos, name)
except AttributeError:
# here we hide the fact we're delegating to another object
raise AttributeError(
"'{}'.object has no attribute '{}'".format(
type(self).__name__, name
))
r = Rect(10,10,10,10)
r.move(10,10)
这种自动委托的缺点是 1/ 它可以提供对您不想公开的某些受委托者属性的访问,2/ 委托方法既不显式可见,也无法通过检查发现,3/ 您会获得一些额外的开销。
第一个问题可以通过保留您希望允许访问的受委托人属性的白名单来解决。唉,第二点没有简单的解决方案,第三点根本没有解决方案,所以最好还是使用显式委托(至少对于重要部分)并为通用代理类等保持自动委托。
注意:如果这是您的代码中重复出现的模式,您仍然可以使用自定义描述符或自定义元类或类装饰器设置一些“半自动”委托系统,即(自定义描述符示例):
class delegate_to():
def __init__(self, delegatee, attr=None):
self._delegatee = delegatee
self._attr = attr
self._name = None
self._owner = None
def __set_name__(self, owner, name):
self._owner = owner
self._name = name
if self._attr is None:
self._attr = name
def _raise(self):
msg = "'{}' object has no attribute '{}'".format(
self._owner.__name__,
self._attr
)
raise AttributeError(msg)
def __get__(self, obj=None, cls=None):
if obj is None:
return self
delegatee = getattr(obj, self._delegatee)
try:
return getattr(delegatee, self._attr)
except AttributeError:
self._raise(obj, cls)
def __set__(self, value):
raise AttributeError("Attribute is read-only")
def __repr__(self):
return "<Delegatee {} ({}) object for {}>".format(
self._name, self._attr, self._owner.__name__
)
# exemple use :
class Delegatee():
def __init__(self, x, y):
self.x = x
self.y = y
def move_to(self, x, y):
self.x = x
self.y = y
def move_by(self, x, y):
self.x += x
self.y += y
@property
def position(self):
return self.x, self.y
class Composite():
def __init__(self, x, y):
self._delegatee = Delegatee(x, y)
move_to = delegate_to("_delegatee")
move_by = delegate_to("_delegatee")
position = delegate_to("_delegatee")
您可以使用将创建delegate_to 描述符的类装饰器进一步自动化:
class delegator():
def __init__(self, delegatee, *names):
if not names:
raise ValueError("needs one or more name to delegate")
self.delegatee = delegatee
self.names = names
def create_delegator(self, owner, name, attrname=None):
if not attrname:
attrname = name
delegator = delegate_to(self.delegatee, attrname)
delegator.__set_name__(owner, name)
return delegator
def __call__(self, cls):
for name in self.names:
if isinstance(name, tuple):
name, attr = name
else:
attr = name
delegator = self.create_delegator(name, attr)
setattr(cls, name, delegator)
return cls
@delegator("_delegatee", "move_to", "move_by", ("pos", "position"))
class Composite2():
def __init__(self, x, y):
self._delegatee = Delegatee(x, y)
现在对于基本上非常简单的事情来说,有很多代码和间接级别,所以只有当你真的有很多个委托要设置时才有意义 - 根据经验,最简单的代码,越容易阅读、理解、测试和调试(python zen:“简单胜于复杂”),而“智能”解决方案从长远来看往往会变成 PITA(来过这里,做过那个,现在我知道了)更好)。