如何使Python类中的非功能成员属性成为一个别名?

发布于 2025-02-09 23:32:36 字数 826 浏览 3 评论 0 原文

我正在编写python库API中,经常遇到我的用户需要多个不同名称的情况,以便相同的功能和变量。

如果我有一个带有函数 foo()的python类,并且我想对其制作一个名为 bar()的别名,那非常容易:

class Dummy:
   
   def __init__(self):
      pass

   def foo(self):
      pass

   bar = foo

现在我可以使用没问题:

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

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

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

我想要 dx d.xvalue 始终打印同一件事。如果 dx 更改,则应更改 d.xvalue (以及Vice-vices-vice-code)。

我可以想到多种方法,但是它们看起来都不如我想要的那样流畅:

  • 编写自定义注释
  • 使用 @property 注释,并与setter混在一起
  • 覆盖 __ setAttr __ 类函数

这些方法最好?还是还有另一种方式?我忍不住觉得,如果使功能的别名如此容易,那么对于任意变量来说,它应该很容易...

I'm in the midst of writing a Python library API and I often run into the scenario where my users want multiple different names for the same functions and variables.

If I have a Python class with the function foo() and I want to make an alias to it called bar(), that's super easy:

class Dummy:
   
   def __init__(self):
      pass

   def foo(self):
      pass

   bar = foo

Now I can do this with no problem:

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

What I'm wondering is what is the best way to do this with a class attribute that is a regular variable (e.g. a string) rather than a function? If I had this piece of code:

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

I want d.x and d.xValue to always print the same thing. If d.x changes, it should change d.xValue also (and vice-versa).

I can think of a number of ways to do this, but none of them seem as smooth as I'd like:

  • Write a custom annotation
  • Use the @property annotation and mess with the setter
  • Override the __setattr__ class functions

Which of these ways is best? Or is there another way? I can't help but feel that if it's so easy to make aliases for functions, it should be just as easy for arbitrary variables...

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

情绪 2025-02-16 23:32:36

这可以与类方法完全相同的方式解决。例如:

class Dummy:
    def __init__(self):
        self._x = 17

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

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

    @x.deleter
    def x(self):
        del self._x

    # 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)

两个值将始终保持同步。您可以使用所需的属性名称编写实际的属性代码,然后用所需的任何遗留名称来校正。

This can be solved in exactly the same way as with class methods. For example:

class Dummy:
    def __init__(self):
        self._x = 17

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

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

    @x.deleter
    def x(self):
        del self._x

    # 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)

The two values will always stay in sync. You write the actual property code with the attribute name you prefer, and then you alias it with whatever legacy name(s) you need.

小糖芽 2025-02-16 23:32:36

您可以提供

class Dummy:
    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 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

You can provide a __setattr__ and __getattr__ that reference an aliases map:

class Dummy:
    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 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
我ぃ本無心為│何有愛 2025-02-16 23:32:36

当您的一半用户决定使用 dx 和另一半 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大小才能了解虚拟。

更好的选择是使用适配器设计模式
这使您可以保持虚拟,紧凑,简洁的效果:

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    

对于虚拟中的每种方法和属性,您只需连接类似的方法和属性
将繁重的举重委托给虚拟的实例。

它可能是更多的代码行,但是它可以让您保留虚拟设计,更容易维护,文档和单位测试。人们会编写有意义的代码,因为该类将限制API可用的内容,并且给定他们选择的班级只有一个概念的名称。

What are you going to do when half your users decide to use d.x and the other half d.xValue? What happens when they try to share code? Sure, it will work, if you know all the aliases, but will it be obvious? Will it be obvious to you when you put away your code for a year?

In the end, I think this kind of niceness or luxury is an evil trap that will eventually cause more confusion than good.


It's mostly because my scripting API
is used across multiple subsystems &
domains, so the default vocabulary
changes. What's known as "X" in one
domain is known as "Y" in another
domain.

You could make aliases with properties this way:

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

But for the reasons mentioned above, I don't think this is a good design. It
makes Dummy harder to read, understand and use. For each user you've doubled the
size of the API the user must know in order to understand Dummy.

A better alternative is to use the Adapter design pattern.
This allows you to keep Dummy nice, compact, succinct:

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

While those users in the subdomain who wish to use a different vocabulary can do so
by using an Adaptor class:

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    

For each method and attribute in Dummy, you simply hook up similar methods and properties
which delegate the heavy lifting to an instance of Dummy.

It might be more lines of code, but it will allow you to preserve a clean design for Dummy, easier to maintain, document, and unit test. People will write code that makes sense because the class will restrict what API is available, and there will be only one name for each concept given the class they've chosen.

自找没趣 2025-02-16 23:32:36

您可以使用Activestate Python食谱中显示的一些想法,标题为 与描述符 的缓存和混叠。这是其中显示的代码的简洁版本,可提供您寻求的功能。

编辑:一个包含别名属性的类可以自动删除您 del> del 一个(and vice-vices-vices-vices-vice-vice-vice-vice-vice-vice-vice-vice-vices-a)自动删除任何关联的目标属性。我的答案的代码现在说明了一种简单的方法,可以使用便利的类装饰器来完成此操作,该级别的 __ 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' method.
        for attrname in dir(self):
            attr = getattr(cls, attrname)
            if isinstance(attr, Alias) and attr.name == name:
                delattr(cls, 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')

You could use some of ideas shown in the ActiveState Python recipe titled Caching and aliasing with descriptors. Here's a concise version of the code shown there which provides the functionality you seek.

Edit: A class containing Alias attributes could be made to automatically delete any associated target attributes when you del one (and vice-versa). The code for my answer now illustrates one easy way this could be done using a convenient class decorator which adds a custom __delattr__() to do the specialized deletion management when attribute Alias's could be involved.

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' method.
        for attrname in dir(self):
            attr = getattr(cls, attrname)
            if isinstance(attr, Alias) and attr.name == name:
                delattr(cls, 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')

醉南桥 2025-02-16 23:32:36

此函数将属性名称作为参数,并返回作为用于获取和设置的别名的属性。

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'

This function takes a attribute name as a param and return a property that work as an alias for getting and setting.

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

Example:

>>> 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'
半﹌身腐败 2025-02-16 23:32:36

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

Override the __getattr__() method and return the appropriate value.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文