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'