2010-10-25 56 views
13

我正在编写一个Python库API,我经常遇到这样的场景,我的用户需要为相同的函数和变量指定多个不同的名称。如何在Python类中为非函数成员属性创建别名?

如果我有一个功能foo() Python类,我想打一个别名,它叫bar(),这是超级简单:

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

class Dummy(object): 

    def __init__(self): 
     pass 

    def foo(self): 
     pass 

    bar = foo 

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

我想知道的是什么是最好的方式来做到这一点与类属性是一个常规变量(如字符串)而不是一个函数?如果我有这样一段代码:

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

我想d.xd.xValueALWAYS打印同样的事情。如果d.x发生变化,它也应该更改d.xValue(反之亦然)。

我能想到的一些方法可以做到这一点,但他们都不顺利,因为我想:

  • 编写自定义注释
  • 使用@property注释和乱用二传手
  • 重写__setattr__类功能

以下哪种方法是最好的?还是有另一种方式?我不禁感到,如果为函数设置别名是如此容易,对任意变量应该也是一样容易......

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

谢谢!

+6

考虑:“应该有一个 - 最好只有一个 - 明显的方法来做到这一点。” - Python的禅宗 – delnan 2010-10-25 18:28:39

+2

为什么你的用户想要这个糟糕的坏事? – 2010-10-25 21:06:46

+1

这主要是因为我的脚本API跨多个子系统和域使用,所以默认词汇表会改变。一个域中称为“X”的在另一个域中被称为“Y”。另外,因为他们是不好的人。 :-) – 2010-10-25 21:45:45

回答

12

您可以提供__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 
+0

感谢http://nedbatchelder.com/blog/201010/surprising_getattr_recursion.html ;这是一个非常有趣的阅读! – unutbu 2010-10-25 18:53:26

+0

这与我所描绘的内容很接近,但我第一次没有提出别名词典。这是避免if/else梯形图和long,verbose setattr/getattr函数的好方法。 – 2010-10-26 14:53:27

+1

重要附录:在'__getattr__'中使用'getattr'实际上会导致无限递归存在不存在的属性(例如,对于上面的示例,请尝试执行'print d.asdf')。我相信使用'object .__ getattribute__'来解决问题。 – 2010-10-26 16:49:45

-1

覆盖__getattr__()方法并返回相应的值。

+0

我不认为我看到'__getattr__'将解决当用户说'dx = 5'与'd.xValue = 6'时传播更改的问题。 – 2010-10-25 21:49:03

+0

你从未提及任何关于* setting *'d.xValue'的内容。当他们要求'd.xValue'的值时,只需查找并返回'd.x'。 – 2010-10-26 02:34:39

+0

最初的问题是“如果dx发生了变化,它也应该改变d.xValue(反之亦然)。”我试图弄清楚我希望能够执行'dx = 5'或'd'的事实。 x值= 5'。如果我不清楚,道歉。 – 2010-10-26 14:56:03

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 

但对于上述原因,我不认为这是一个不错的设计。它使012更难以阅读,理解和使用。对于每个用户,您已将用户必须知道的API的大小加倍,以便了解Dummy。

更好的选择是使用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,并且在给定所选课程的每个概念中只会有一个名称。

+2

我并不一定不同意你的看法,但是我已经为那场战斗而战,失败了。我能做的下一个最好的事情就是尽可能实施和记录它,因此它可以在一年后尽可能清晰。 – 2010-10-25 21:51:00

3

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

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

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' 
+0

这真的很聪明。唯一让我误解的是,如果我执行'del d.x',然后执行'dir(d)',xValue'仍然会显示在结果列表中。 – 2010-10-26 14:51:58

+0

@Brent Nash:我已经添加了一个类装饰器来处理你指出的属性删除问题。 – martineau 2010-10-30 18:55:40

4

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

例如,

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__更容易阅读和理解。

+0

我同意。这段代码更容易阅读。 – 2016-12-23 16:48:02

+0

作品对我来说似乎很简单。 – BrendanSimon 2018-01-16 03:54:44

相关问题