如何在Python类中为非函数成员属性创建别名?

时间:2010-10-25 18:19:44

标签: python class alias

我正在编写Python库API,我经常遇到我的用户想要相同函数和变量的多个不同名称的场景。

如果我有一个带有函数foo()的Python类,并且我想为它创建一个名为bar()的别名,那就太容易了:

class Dummy(object):

   def __init__(self):
      pass

   def foo(self):
      pass

   bar = foo

现在我可以毫无问题地做到这一点:

d = Dummy()
d.foo()
d.bar()

我想知道的是,使用作为常规变量(例如字符串)的类属性而不是函数的最佳方法是什么?如果我有这段代码:

d = Dummy()
print d.x
print d.xValue

我希望d.xd.xValue 始终打印相同的内容。如果d.x发生变化,则也应更改d.xValue(反之亦然)。

我可以想到很多方法可以做到这一点,但没有一个像我想的那样顺利:

  • 编写自定义注释
  • 使用@property注释并使用setter
  • 覆盖__setattr__类函数

哪种方式最好?或者还有另一种方式吗?我不禁觉得,如果为函数创建别名这么容易,对任意变量来说应该很容易......

仅供参考:我使用的是Python 2.7.x,而不是Python 3.0,因此我需要一个兼容Python 2.7.x的解决方案(尽管如果Python 3.0能够直接解决这个问题,我会感兴趣)。

谢谢!

6 个答案:

答案 0 :(得分:15)

您可以提供引用别名地图的__setattr____getattr__

class Dummy(object):
    aliases = {
        'xValue': 'x',
        'another': 'x',
        }

    def __init__(self):
        self.x = 17

    def __setattr__(self, name, value):
        name = self.aliases.get(name, name)
        object.__setattr__(self, name, value)

    def __getattr__(self, name):
        if name == "aliases":
            raise AttributeError  # http://nedbatchelder.com/blog/201010/surprising_getattr_recursion.html
        name = self.aliases.get(name, name)
        #return getattr(self, name) #Causes infinite recursion on non-existent attribute
        return object.__getattribute__(self, name)


d = Dummy()
assert d.x == 17
assert d.xValue == 17
d.x = 23
assert d.xValue == 23
d.xValue = 1492
assert d.x == 1492

答案 1 :(得分:7)

除非我误解了这个问题,否则这可以用与Class方法几乎完全相同的方式解决。

例如,

class Dummy(object):

    def __init__(self):
        self._x = 17

    @property
    def x(self):
        return self._x

    @x.setter
    def x(self, inp):
        self._x = inp

    # Alias
    xValue = x

d = Dummy()
print(d.x, d.xValue)
#=> (17, 17)
d.x = 0
print(d.x, d.xValue)
#=> (0, 0)
d.xValue = 100
print(d.x, d.xValue)
#=> (100, 100)

这两个值将始终保持同步。您使用您喜欢的属性名称编写实际属性代码,然后使用您需要的任何旧名称将其别名。

在我看来,这个代码比所有__setattr____getattr__覆盖更容易阅读和理解。

答案 2 :(得分:5)

当一半用户决定使用d.x而另一半d.xValue时,您打算做什么?当他们尝试共享代码时会发生什么?当然,它会起作用,如果你知道所有的别名,但它会显而易见吗?当你把你的代码放了一年时,你会明白吗?

最后,我认为这种善意或奢侈是一种邪恶的陷阱,最终会导致更多的混乱而不是好的。


  

这主要是因为我的脚本API   用于多个子系统和   域,所以默认词汇   变化。什么被称为“X”在一个   域名在另一个域名称为“Y”   域。

您可以通过以下方式创建属性别名:

class Dummy(object):
   def __init__(self):
      self.x=1
   @property
   def xValue(self):
      return self.x
   @xValue.setter
   def xValue(self,value):
      self.x=value

d=Dummy()
print(d.x)
# 1
d.xValue=2
print(d.x)
# 2

但由于上述原因,我认为这不是一个好的设计。它 使Dummy更难阅读,理解和使用。对于每个用户,你加倍了 用户必须知道的API大小才能理解Dummy。

更好的选择是使用Adapter design pattern。 这使您可以保持Dummy漂亮,紧凑,简洁:

class Dummy(object):
   def __init__(self):
      self.x=1

虽然子域中希望使用不同词汇表的用户可以这样做 使用Adapter类:

class DummyAdaptor(object):
   def __init__(self):
      self.dummy=Dummy()
   @property
   def xValue(self):
      return self.dummy.x
   @xValue.setter
   def xValue(self,value):
      self.dummy.x=value    

对于Dummy中的每个方法和属性,您只需连接类似的方法和属性 它将繁重的工作委托给了Dummy的一个实例。

它可能包含更多代码行,但它可以让您为Dummy保留一个干净的设计,更易于维护,文档和单元测试。人们会编写有意义的代码,因为类会限制可用的API,并且在给定他们选择的类的情况下,每个概念只有一个名称。

答案 3 :(得分:3)

您可以使用标题为Caching and aliasing with descriptors的ActiveState Python配方中显示的一些想法。这里显示的代码的简洁版本提供了您所寻求的功能。

编辑:当您Alias一个属性时,可以使包含del属性的类自动删除任何关联的目标属性(反之亦然)。我的答案的代码现在说明了一种简单的方法,可以使用方便的类装饰器来完成,该装饰器可以在涉及属性__delattr__()时添加自定义Alias's来执行专门的删除管理。

class Alias(object):
    """ Descriptor to give an attribute another name. """
    def __init__(self, name):
        self.name = name
    def __get__(self, inst, cls):
        if inst is None:
            return self  # a class attribute reference, return this descriptor
        return getattr(inst, self.name)
    def __set__(self, inst, value):
        setattr(inst, self.name, value)
    def __delete__(self, inst):
        delattr(inst, self.name)

def AliasDelManager(cls):
    """ Class decorator to auto-manage associated Aliases on deletion. """
    def __delattr__(self, name):
        """ Deletes any Aliases associated with a named attribute, or
            if attribute is itself an Alias, deletes the associated target.
        """
        super(cls, self).__delattr__(name) # use base class's method
        for attrname in dir(self):
            attr = getattr(Dummy, attrname)
            if isinstance(attr, Alias) and attr.name == name:
                delattr(Dummy, attrname)

    setattr(cls, '__delattr__', __delattr__)
    return cls

if __name__=='__main__':
    @AliasDelManager
    class Dummy(object):
        def __init__(self):
            self.x = 17
        xValue = Alias('x')  # create an Alias for attr 'x'

    d = Dummy()
    assert d.x == 17
    assert d.xValue == 17
    d.x = 23
    assert d.xValue == 23
    d.xValue = 1492
    assert d.x == 1492
    assert d.x is d.xValue
    del d.x  # should also remove any associated Aliases
    assert 'xValue' not in dir(d)
    print 'done - no exceptions were raised'

答案 4 :(得分:0)

此函数采用属性名称并返回一个属性,该属性用作获取和设置的别名。

def alias_attribute(field_name: str) -> property:
    """
    This function takes the attribute name of field to make a alias and return
    a property that work to get and set.
    """
    field = property(lambda self: getattr(self, field_name))
    field = field.setter(lambda self, value: setattr(self, field_name, value))
    return field

示例:

>>> class A:
...     name_alias = alias_attribute('name')
...     def __init__(self, name):
...         self.name = name
... a = A('Pepe')

>>> a.name
'Pepe'

>>> a.name_alias
'Pepe'

>>> a.name_alias = 'Juan'

>>> a.name
'Juan'

答案 5 :(得分:-1)

覆盖__getattr__()方法并返回适当的值。