代码之家  ›  专栏  ›  技术社区  ›  Brent Writes Code

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

  •  16
  • Brent Writes Code  · 技术社区  · 14 年前

    我正在编写一个python库API,我经常遇到这样的情况:我的用户希望对相同的函数和变量使用多个不同的名称。

    如果我有一个带有函数的python类 foo() 我想给它化名为 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.x d.xValue 总是 打印同样的东西。如果 D.X 改变,它应该改变 D.X值 同样(反之亦然)。

    我可以想出很多方法来做到这一点,但没有一种方法像我希望的那样顺畅:

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

    以下哪种方法最好?还是有别的办法?我禁不住觉得,如果为函数创建别名很容易,那么对任意变量来说也应该很容易……

    仅供参考:我使用的是python 2.7.x,而不是python 3.0,所以我需要一个与python2.7.x兼容的解决方案(不过如果python3.0能直接解决这个问题,我会感兴趣)。

    谢谢!

    5 回复  |  直到 9 年前
        1
  •  15
  •   Brent Writes Code    14 年前

    您可以提供 __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
    
        2
  •  6
  •   smargh    9 年前

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

    例如,

    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__ 覆盖。

        3
  •  5
  •   unutbu    14 年前

    当一半的用户决定使用 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
    

    但是由于上面提到的原因,我不认为这是一个好的设计。它 使虚拟对象更难阅读、理解和使用。对于每个用户,您将 用户必须知道的API的大小才能理解虚拟。

    更好的选择是使用 Adapter design pattern . 这可以让你保持假人的优美、紧凑、简洁:

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

    而子域中希望使用不同词汇表的用户可以这样做 通过使用适配器类:

    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中的每个方法和属性,只需连接类似的方法和属性 把重物吊到假人身上。

    它可能有更多的代码行,但它将允许您为虚拟对象保留一个干净的设计,更容易维护、记录和单元测试。人们将编写有意义的代码,因为类将限制可用的API,并且根据他们选择的类,每个概念只有一个名称。

        4
  •  3
  •   martineau    14 年前

    您可以使用ActiveState python食谱中显示的一些想法,标题为 Caching and aliasing with descriptors . 这里有一个简洁的代码版本,它提供了您需要的功能。

    编辑: 包含 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'
    
        5
  •  -1
  •   Ignacio Vazquez-Abrams    14 年前

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