I am trying to write a method which calls another method in another instance. I do not know which combination of args and kwargs i get.
So i wrote the following method. But that looks not very elegant and not very pythonic to me.
Is there a better way to implement this?
def __call_generic_remote_function(self, rfunc_name, rargs=None, rkwargs=None):
try:
lfunc_name = getattr(self.inst_to_wrap, rfunc_name)
except AttributeError:
return f"Method {rfunc_name} is not existing!"
if rargs is None and rkwargs is None:
result = lfunc_name()
elif rargs is not None and rkwargs is not None:
result = lfunc_name(*rargs, **rkwargs)
elif rargs is None:
result = lfunc_name(**rkwargs)
else:
result = lfunc_name(*rargs)
return result
This question is probably off topic as it is more opinion based but I would write it something like the following. Can also be a standalone function.
def call_generic_remote_function(object_instance, func_name, *args, **kwargs):
try:
func_to_call = getattr(object_instance, func_name)
except AttributeError:
return f"Method {func_name} does not exist!"
return func_to_call(*args, **kwargs)
Tested Via:
class Foo:
def bar(self, a, b, k):
print(a, b, k)
def bar2(self):
print("Called")
f = Foo()
call_generic_remote_function(f, 'bar', 1, 2, k=3)
call_generic_remote_function(f, 'bar2')
print(call_generic_remote_function(f, 'bar3'))
Output:
1 2 3
Called
Method bar3 does not exist!
Related
I'm experimenting with decorators, and I've hit a stumbling block. I have the following code:
class Decorator:
def __init__(self, *whatever):
self.functor = None
def __call__(self, functor):
self.functor = functor
return self.wrapper
def wrapper(self, *args, **kwargs):
return self.functor(*args, **kwargs)
class Foo:
#Decorator(5, 4, 3, 2, 1)
def bar(self, a, b):
return a+b
if __name__ == '__main__':
f = Foo()
f.bar(1, 2)
I expect f.bar to print "1 2" and return 3. However, instead, I am getting the following error:
TypeError: bar() missing 1 required positional argument: 'b'.
Invoking f.bar by using Foo.bar(f, 1, 2) instead results in the correct behavior.
Why is this the case, and how can I modify my decorator to fix it?
Based upon my understanding, *args should include self, but this seemingly isn't the case.
I am using Python 3.11.
What is the standard way of making a class comparable in Python 3? (For example, by id.)
To make classes comparable, you only need to implement __lt__ and decorate the class with functools.total_ordering. You should also provide an __eq__ method if possible. This provides the rest of the comparison operators so you don't have to write any of them yourself.
For a full set of comparison functions I have used the following mixin, which you could put in say for example a mixin.py in your module.
class ComparableMixin(object):
def _compare(self, other, method):
try:
return method(self._cmpkey(), other._cmpkey())
except (AttributeError, TypeError):
# _cmpkey not implemented, or return different type,
# so I can't compare with "other".
return NotImplemented
def __lt__(self, other):
return self._compare(other, lambda s, o: s < o)
def __le__(self, other):
return self._compare(other, lambda s, o: s <= o)
def __eq__(self, other):
return self._compare(other, lambda s, o: s == o)
def __ge__(self, other):
return self._compare(other, lambda s, o: s >= o)
def __gt__(self, other):
return self._compare(other, lambda s, o: s > o)
def __ne__(self, other):
return self._compare(other, lambda s, o: s != o)
To use the mixin above you need to implement a _cmpkey() method that returns a key of objects that can be compared, similar to the key() function used when sorting. The implementation could look like this:
>>> from .mixin import ComparableMixin
>>> class Orderable(ComparableMixin):
...
... def __init__(self, firstname, lastname):
... self.first = firstname
... self.last = lastname
...
... def _cmpkey(self):
... return (self.last, self.first)
...
... def __repr__(self):
... return "%s %s" % (self.first, self.last)
...
>>> sorted([Orderable('Donald', 'Duck'),
... Orderable('Paul', 'Anka')])
[Paul Anka, Donald Duck]
The reason I use this instead of the total_ordering recipe is this bug. It's fixed in Python 3.4, but often you need to support older Python versions as well.
Not sure if this is complete, but you'd want to define:
__eq__, __gt__, __ge__, __lt__, __le__
As agf said, I'm missing:
__ne__
You said you are trying to do this:
max((f(obj), obj) for obj in obj_list)[1]
You should simply do this:
max(f(obj) for obj in obj_list)
EDIT: Or as gnibbler said: max(obj_list, key=f)
But you told gnibbler you need an object reference to the max object. I think this is simplest:
def max_obj(obj_list, max_fn):
if not obj_list:
return None
obj_max = obj_list[0]
f_max = max_fn(obj)
for obj in obj_list[1:]:
if max_fn(obj) > f_max:
obj_max = obj
return obj_max
obj = max_obj(obj_list)
Of course you might want to let it raise an exception rather than return none if you try to find the max_obj() of an empty list.
I just thought of a really hackish way to do it. This is in the same spirit as what you were originally trying to do. It does not require adding any functions to the class object; it works for any class.
max(((f(obj), obj) for obj in obj_list), key=lambda x: x[0])[1]
I really don't like that, so here's something less terse that does the same thing:
def make_pair(f, obj):
return (f(obj), obj)
def gen_pairs(f, obj_list):
return (make_pair(f, obj) for obj in obj_list)
def item0(tup):
return tup[0]
def max_obj(f, obj_list):
pair = max(gen_pairs(f, obj_list), key=item0)
return pair[1]
Or, you could use this one-liner if obj_list is always an indexable object like a list:
obj_list[max((f(obj), i) for i, obj in enumerate(obj_list))[1]]
This has the advantage that if there are multiple objects such that f(obj) returns an identical value, you know which one you will get: the one with the highest index, i.e. the latest one in the list. If you wanted the earliest one in the list, you could do that with a key function.
I have a class consisting of a "list" of static methods, A. I want to change its behavior with a class-decorator, Meta, which acts on a specific static method, in this example content, by performing the method m.
My original attempt, CASE=2, didn't work as expected, so I started I case study. I introduced a new class B, which has slightly different implementation of an other method, info but raised a funny error, and a new class C just without the method, info.
case 2: the greedy case
d[i] = classmethod(lambda cls, *args: mcs.m( getattr(target_cls, i)(*args)) ) it doesn't work properly, maybe too many nested dynamic expressions?
case 1: it essentially case 2 but the expression is divided in two lines, and it works
o = getattr(target_cls, i)
d[i] = classmethod(lambda cls, *args: mcs.m(o(*args)))
Here the code
class Meta:
def __new__(mcs, target_cls):
if CASE == 1:
print('case 1')
d = {}
for i in dir(target_cls):
if i == 'content':
o = getattr(target_cls, i)
d[i] = classmethod(lambda cls, *args: mcs.m(o(*args)))
if CASE == 2:
print('case 2')
d = {}
for i in dir(target_cls):
if i == 'content':
d[i] = classmethod(lambda cls, *args: mcs.m( getattr(target_cls, i)(*args)) )
return type('AAA', (target_cls,), d)
#classmethod
def m(mcs, p):
return '--> ', p
class A:
#staticmethod
def content(response):
return 'static_method', response
#staticmethod
def info(response):
return response
class B:
#staticmethod
def content(response):
return 'static_method', response
#staticmethod
def info(response):
response.sort()
return response
class C:
#staticmethod
def content(response):
return 'static_method', response
# call the "content" class-method of each class for all different cases
for cls in (A, B, C):
print(cls.__name__)
for case in range(1,3):
CASE = case
R = Meta(cls)
try:
print(R.content('ppp'))
except Exception as e: print(e)
print()
Output
A
case 1
('--> ', ('static_method', 'ppp'))
case 2
('--> ', 'ppp') # no decoration
B
case 1
('--> ', ('static_method', 'ppp'))
case 2
'str' object has no attribute 'sort' # <- complained about the other method
C # <- this is ok BUT I removed the other method!
case 1
('--> ', ('static_method', 'ppp'))
case 2
('--> ', ('static_method', 'ppp')) # <- here the decoration took place
The question is why case 2 doesn't work, if it is a limitation of the language then of what kind?
Extra question: how to explain the error of class B case 2
I guess that the issue is caused by the loop and the origin is the fact that each statement has not its own scope (in the loop). By passing i as a key parameter of the lambda fixed the problem.
class Meta:
def __new__(mcs, target_cls):
d = {}
for i in dir(target_cls):
if i == 'content':
d[i] = classmethod(lambda cls, *args, m_name=i: mcs.m( getattr(target_cls, m_name)(*args)) )
return type('AAA', (target_cls,), d)
#classmethod
def m(mcs, p):
return '--> ', p
class A:
#staticmethod
def content(response):
return 'static_method', response
#staticmethod
def info(response):
return response
print(A.content)
print(Meta(A).content)
print(Meta(A).content('a'))
print(Meta(A).info)
Output
<function A.content at 0x7f04500740d0> # original static method
<bound method Meta.__new__.<locals>.<lambda> of <class '__main__.AAA'>> # class method
('--> ', ('static_method', 'a'))
<function A.info at 0x7f0450074040>
I am trying to assign dictionary keys to object functions but for some reason it won't work inside of decorators. When I try to call a.run(), self doesn't seem to be passed into the dictionary func. I also don't have access to f.self in decorator so I know it has to be something wrong in there. I have written a simple example of my code. I want it to be something similar to app.route in flask being that it init the mapping between endpoints and functions.
ERROR:
Traceback (most recent call last):
File "main.py", line 27, in <module>
a.run()
File "main.py", line 14, in run
self.rmap[k](data)
TypeError: one_way() missing 1 required positional argument: 'data'
CODE:
class A (object):
def __init__(self):
self.rmap = {}
def route(self, r):
def decorator(f):
self.rmap[r] = f
return f
return decorator
def run(self):
data = [1,2,3]
for k in self.rmap.keys():
self.rmap[k](data)
a = A()
class B (object):
def __init__(self):
pass
#a.route('/one/way')
def one_way (self, data):
print('A WAY:{}'.format(self))
b = B()
a.run()
At the time it's being decorated, one_way() is a plain function, not a method - it only becomes a method when looked up on a B instance. IOW, you need to explicitely provide a B instance when calling it from A().run() (the fact you have a global b instance in your code is irrelevant - the function object stored in a.rmap knows absolutely nothing about it, nor even about the B class FWIW.
To make a long story short, your current design cannot work as is. If you only ever intend to decorate methods (well, functions) from one single class and call them on one single instance of this class, you could pass an instance of this class to a.run() ie:
class A():
# ...
def run(self, obj):
data = [1,2,3]
for k in self.rmap.keys():
self.rmap[k](obj, data)
b = B()
a.run(b)
but this would be of very limited use.
Or you could just use the decorator to "mark" functions to be used for routing (together with the effective route), add some register() methdo to A and explicitely pass B or whatever else instance to this method ie
def route(r):
def decorator(f):
f._A_route = r
return f
return decorator
class A (object):
def __init__(self):
self.rmap = {}
def register(self, *objects):
for obj in objects:
self._register(obj)
def _register(self, obj):
for name in dir(obj):
if name.startswith("_"):
continue
attr = getattr(obj, name)
if callable(attr) and hasattr(attr, "_A_route"):
self.rmap[attr._A_route] = attr
def run(self):
data = [1,2,3]
for k in self.rmap.keys():
self.rmap[k](data)
class B (object):
def __init__(self):
pass
#route('/one/way')
def one_way (self, data):
print('A WAY:{}'.format(self))
if __name__ == "__main__":
a = A()
b = B()
a.register(b)
a.run()
Now there might be better solutions for your concrete use case, but it's impossible to tell without knowing about the whole context etc.
When calling self.rmap[k](data) you are not passing in the self parameter. This has to be an instance of class B in order to work.
Normally you'd just pass on the parameters with which the decorated function was called, but you seem to want to use your decorated function differently. In your case what would work is:
def run(self):
data = [1,2,3]
b = B()
for k in self.rmap.keys():
self.rmap[k](b, data)
You could of course also instantiate the B instance somewhere else if you want to reuse it between calls.
I am new to learning python, I know this kind questions asked before but i am not able to find any solutions for it. Please check my code and correct me about decorator's functionality, Thank you.
def uppercase(func_one):
func_one = func_one()
return func_one.upper()
def split(func_two):
func_two = func_two()
return func_two.split()
#split
#uppercase
def CallFunction():
return "my string was in lower case"
res = CallFunction()
print(res)
Decorators are confusing and probably should be avoided till you are super experienced with python. That being said, chaining decorators is even more tricky:
from functools import wraps
def split(fn): # fn is the passed in function
#wraps(fn) # This means we can grabs its args and kwargs
def wrapped(*args, **kwargs): # This is the new function declaration
return fn(*args, **kwargs).split()
return wrapped
def uppercase(fn):
#wraps(fn)
def wrapped(*args, **kwargs):
return fn(*args, **kwargs).upper()
return wrapped
# Order matters. You can't call .upper() on a list
#split
#uppercase
def CallFunction():
return "my string was in lower case"
res = CallFunction()
print(res)
Alternatively if you don't want the order of these two decorators to matter than you need to handle the list case:
def uppercase(fn):
#wraps(fn)
def wrapped(*args, **kwargs):
result = fn(*args, **kwargs)
if isinstance(result, list):
return [x.upper() for x in result]
return result.upper()
return wrapped
Reference: How to make a chain of function decorators?
You don't even need functools, you just need to grab the args you are passing.
Here's what you are missing: Add the args being passed inside a wrapper and define that wrapper to accept the args passed. Happy coding!
def uppercase(func_one):
def wrapper(*args):
x = func_one()
return x.upper()
return wrapper
def split(func_two):
def wrapper(*args):
y = func_two()
return y.split()
return wrapper
#split
#uppercase
def CallFunction():
return "my string was in lower case"
res = CallFunction()
print(res)