Home > Enterprise >  Is there a getter/setter like functionality for class functions in python?
Is there a getter/setter like functionality for class functions in python?

Time:12-16

I'm working on a project where the people who will be using one of my class methods will expect to have the method have different names (since the users are coming from multiple different research areas). So ideally, I would like to have some way of "aliasing" the class method so it would work under different names, as well as, if one of the users changes the method for a specific name, that change would also be seen if the method was called from a different name. For an example:

class GeneralClass:

   def foo(self, x):
       print('Hello', x)


   # Define aliases for the method
   bar = foo
   spam = foo
   eggs = foo

g = GeneralClass()
g.foo('world')  # Output: 'Hello world'
g.bar('world')  # Output: 'Hello world'

# Redefine one of the methods
g.bar = lambda x: print('Goodbye', x)

# This breaks the aliasing
g.foo('world')  # Output: 'Hello world'
g.bar('world')  # Output: 'Goodbye world'

I tried to define foo using the @property decorator and using a getter and setter:

class GeneralClassWithProperty:

    def _universal_name(self):
        print('Hello')

    @property
    def universal_name(self):
        return self._universal_name

    @universal_name.setter
    def universal_name(self, new_method):
        self._universal_name = new_method

   # Define aliases for the method
    foo = universal_name
    bar = universal_name
    spam = universal_name
    eggs = universal_name

g_property = GeneralClassWithProperty()

However, I've since learned that getters/setters are just for class attributes, so you cannot pass in variables to the getter function. Thus if you call g_property.foo('test') you get an error that the _universal_name() missing 1 required positional argument: 'x'. I confirmed this aliasing approach works if we don't require the getter to take in any parameters (proof below), so is there any way we can use getter/setters for class methods? (Or, is there another way of getting this synchronized aliasing I'm looking for?)

(Also, please don't suggest "Just have your users use universal_name, since sadly that's out of the question.) Thank you!


Proof of getter/setter working for synchronized aliasing of class attributes

class GeneralClass:

    def _universal_name(self):
        print('Hello')

    @property
    def universal_name(self):
        return self._universal_name

    @universal_name.setter
    def universal_name(self, new_method):
        self._universal_name = new_method

   # Define aliases for the method
    foo = universal_name
    bar = universal_name
    spam = universal_name
    eggs = universal_name

g = GeneralClass()
g.foo()  # Output: 'Hello'
g.bar()  # Output: 'Hello'

# Redefine one of the methods
g.bar = lambda: print('Goodbye')

# This breaks the aliasing
g.foo()  # Output: 'Goodbye'
g.bar()  # Output: 'Goodbye'

CodePudding user response:

However, I've since learned that getters/setters are just for class attributes, so you cannot pass in variables to the getter function. Thus if you call g_property.foo('test') you get an error that the _universal_name() missing 1 required positional argument: 'x'.

No it doesn't. Here:

In [1]: class GeneralClass:
   ...:
   ...:     def _universal_name(self):
   ...:         print('Hello')
   ...:
   ...:     @property
   ...:     def universal_name(self):
   ...:         return self._universal_name
   ...:
   ...:     @universal_name.setter
   ...:     def universal_name(self, new_method):
   ...:         self._universal_name = new_method
   ...:
   ...:    # Define aliases for the method
   ...:     foo = universal_name
   ...:     bar = universal_name
   ...:     spam = universal_name
   ...:     eggs = universal_name
   ...:

In [2]: g = GeneralClass()

In [3]: g.foo()
Hello

In [4]: g.eggs()
Hello

In [5]: g.universal_name = lambda x: f"new {x}"

In [6]: g.foo('foo')
Out[6]: 'new foo'

In [7]: g.eggs('foo')
Out[7]: 'new foo'

But note, property isn't really doing anything here! Your propety getter/setters are completely pointless, the property should simply not exist:

You could just use:

class GeneralClass:

   def universal(self):
       return f"Hello"

   def _universal_wrapper(self, *args, **kwargs):
       return self.universal(*args, **kwargs)


   # Define aliases for the method
   foo = _universal_wrapper
   bar = _universal_wrapper
   spam = _universal_wrapper
   eggs = _universal_wrapper

g = GeneralClass()
print(g.foo())
g.universal = lambda x: f"goodbye, my dear {x}"
print(g.foo('Watson'))

CodePudding user response:

If I correctly understand your task I would implement it like this:

class GenericClass:
    # Aliases tuple.
    # If this tuple will be defined in __init__ then AttributeError
    # will be raised, because __setattr__ is called before __init__
    # and self.names won't be defined.
    names = ("foo", "bar", "eggs")
    def __init__(self):
        self.foo = self._wrapper
        self.bar = self._wrapper
        self.foobar = 1

    def _wrapper(self, *args, **kwargs):
        return self._inner(*args, **kwargs)

    def _inner(self, val):
        """
        The method that will be actually called.
        """
        print(f"Hello, {val}")

    def __setattr__(self, name, value):
        # This method is called when an attribute assignment is attempted.

        # Checking whether __setattr__ is called for one of the aliases.
        if name in self.names:
            # __dict__ is a dictionary or other mapping
            # object used to store an object’s (writable) attributes.
            # If the __dict__ doesn't contain alias name this
            # mean it is the first we set value to it (i.e. assignment
            # in __init__).
            # 
            # Setting value as is
            if name not in self.__dict__:
                self.__dict__[name] = value
            # If the alias name is in the __dict__ then update
            # '_inner' value.
            else:
                super().__setattr__("_inner", value)
        # If __setattr__ is called for non-alias name then
        # set value for this name as is.
        else:
            super().__setattr__(name, value)


In [46]: g = GenericClass()

In [47]: g.foo("world")
Hello, world

In [48]: g.bar("world")
Hello, world


In [49]: g.bar = lambda val: f"Goodbye, {val}"

In [50]: g.foo("world")
Out[50]: 'Goodbye, world'

In [51]: g.bar("world")
Out[51]: 'Goodbye, world'

__setattr__, __dict__, super()

  • Related