Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I make an alias to a non-function member attribute in a Python class?

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...

like image 400
Brent Writes Code Avatar asked Oct 25 '10 18:10

Brent Writes Code


People also ask

Which keyword is used to create an alias in Python *?

The as keyword is used to create an alias.

Does Python have alias?

Python, like C++ and Java, does allow aliasing because having multiple references to a million-element list is a lot more efficient than copying it over and over again.

Can I add an attribute to a class Python?

Adding attributes to a Python class is very straight forward, you just use the '. ' operator after an instance of the class with whatever arbitrary name you want the attribute to be called, followed by its value.

How do you instantiate an object in Python?

Instantiating a class in Python is simple. To instantiate a class, we simply call the class as if it were a function, passing the arguments that the __init__ method defines. The return value will be the newly created object.


3 Answers

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.

like image 54
smargh Avatar answered Oct 19 '22 10:10

smargh


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
like image 23
Ned Batchelder Avatar answered Oct 19 '22 10:10

Ned Batchelder


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.

like image 26
unutbu Avatar answered Oct 19 '22 12:10

unutbu