Implementing __getattr__ inside a class method such as __call__ - python

So I've created a module inspired heavily by amoffat's sh module, where I can import shell programs as functions; unlike sh, my module can do something like git(C = path).commit(m = message) directly, by returning the module class itself as a partial: return partial(bakery, self.program). However, I've lost the ability to run something like ls() without a placeholder method like ls._(), which doesn't look as good. The code in the latter: return output_as_list(args, kwargs).
from functools import partial
def __getattr__(name):
if name == "__path__":
raise AttributeError
return bakery(name)
class bakery:
def __init__(self, program):
self.program = program
def __getattr__(self, subcommand):
return subcommand
#property
def __call__(self):
return partial(bakery, self.program)
My question is this:
Is there a way to tell __call__ you're accessing a dynamic attribute, using a __getattr__ inside it, for example, to implement both the git(C = path).commit(m = message) and ls() scenarios? Or to conditionally return a partial or an output list depending on whether an attribute of __call__ is being accessed?
Edit:
I was wondering if something similar to this might work?
def __call__(self, *args, **kwargs):
class inner_class:
def __init__(self, *args, **kwargs):
self.args = args
self.kwargs = kwargs
def __getattr__(self, subcommand):
return partial(bakery, self.program)
def __call__(self, *args, **kwargs):
return bakery(*self.args, **self.kwargs)._get_output_as_list(*args, **kwargs)
return inner_class(self.program, *args, **kwargs)
Edit 2:
I suppose I could just convert the individual __call__ functions to subclasses, and import from whichever is necessary.

Related

Class method as decorator in python

So, im writing a library for appium tests.
I have a main class that look like this:
class APP():
def __init__(self):
self.variable1 = 1
self.current_view = "main_screen"
def do_operation_A(self):
self.open_side_menu()
do_something
self.current_view = "side_menu"
def do_operation_B(self):
self.open_side_menu()
do_something_else
self.current_view = "side_menu"
def set_landscape(self):
self.open_settings_menu()
configure_landscape
self.current_view = "settings_menu"
The class has a lot of operations so i can do things like app.do_operation_A() or app.set_landscape() without having to first go to each menu manually (resolved inside the class)
To reduce this i want to implement a decorator to do something like this if possible:
class APP():
def __init__(self):
self.variable1 = 1
self.current_view = "main_screen"
#DEFINE_DECORATOR_HERE
#side_menu
def do_operation_A(self):
do_something
#side_menu
def do_operation_B(self):
do_something_else
#settings_menu
def set_landscape(self):
configure_landscape
So i want to implement this decorators to navigate to the corresponding view and also change that variable that i use to check some things in other functions. I have seen some examples with functools.wraps but is not clear to me of how to implement the decorator inside the class to be able to modify this self variables.
Any help?
Using a decorator means that you "wrap" your other function, i.e. you call the decorator and then call the function from inside the decorator.
E.g.:
import functools
def outer(func):
#functools.wraps(func)
def inner(*args, **kwargs):
return func(*args, **kwargs)
return inner
Upon defining the function, the decorator will be called, returning the inner function.
Whenever you call func, you will in reality call inner, which runs it's own code, including calling the original func function.
So for your use case, you should be able to create decorators similar to:
def settings_menu(func):
#functools.wraps(func)
def inner(self, *args, **kwargs):
self.open_settings_menu()
self.current_view = "settings_menu"
return func(self, *args, **kwargs)
return inner
So a decorator is basically a function that returns another function, right?
def side_menu(func):
def wrapper():
return func()
return wrapper
The wrapper, returned by side_menu, will be called whenever App().do_operationA is called. And whenever that method is called, self is always the first argument. Or rather, the first argument is the instance of App, but whatever. So we could do:
def side_menu(func):
def wrapper(self, *args, **kwargs):
self.open_menu()
func(self, *args, **kwargs)
return wrapper
Now, you don't want the method to present itself as wrapper - you like the name do_operationA. That's where #functools.wraps comes in, it makes things look and work right when decorating.
def side_menu(func):
#functools.wraps
def wrapper(self, *args, **kwargs):
self.open_menu()
func(self, *args, **kwargs)
return wrapper

How to Create a Decorator Class that takes Arguments while Using `functools.wraps` and Preserving Access to the Decorated Instance

1. My Requirements
The Decorator Class should use functools.wraps so it has proper introspection and organization for later.
Access to the decorated instance should be possible.
In the example below, I do it by passing a wrapped_self argument to the __call__ method.
As the title states, the Decorator Class must have parameters that you can tune for for each method.
2. An Example of What It Would Look Like
The ideal situation should look something like this:
class A():
def __init__(self):
...
#LoggerDecorator(logger_name='test.log')
def do_something(self):
...
with the Decorator Class being, so far (basic logger decorator based on a recipe coming from David Beazley's Python Cookbook):
class LoggerDecorator():
def __init__(self, func, logger_name):
wraps(func)(self)
self.logger_name = logger_name
def config_logger(self):
... # for example, uses `self.logger_name` to configure the decorator
def __call__(self, wrapped_self, *args, **kwargs):
self.config_logger()
wrapped_self.logger = self.logger
func_to_return = self.__wrapped__(wrapped_self, *args, **kwargs)
return func_to_return
def __get__(self, instance, cls):
if instance is None:
return self
else:
return types.MethodType(self, instance)
3. How Do I Fix It?
The error I'm getting refers to __init__ not recognizing a third argument apparently:
TypeError: __init__() missing 1 required positional argument: 'func'
It has been suggested to me that I should be putting func in the __call__ method. However, if I put it there as a parameter, wrapped_self isn't properly read as a parameter and I get this error:
__call__() missing 1 required positional argument: 'wrapped_self'
I've tried many things to fix this issue, including: putting wraps(func)(self) inside __call__; and many variations of this very close but not quite filling all of the requirements solution (the problem with it is that I can't seem to be able to access wrapped_self anymore).
Since you're implementing a decorator that takes parameters, the __init__ method of LoggerDecorator should take only the parameters that configures the decorator, while the __call__ method instead should become the actual decorator that returns a wrapper function:
class LoggerDecorator():
def __init__(self, logger_name):
self.logger_name = logger_name
self.config_logger()
def __call__(self, func):
#wraps(func)
def wrapper(wrapped_self, *args, **kwargs):
wrapped_self.logger = self.logger
func_to_return = func(wrapped_self, *args, **kwargs)
return func_to_return
return wrapper
from functools import wraps
class LoggerDecorator:
def __init__(self, logger):
self.logger = logger
def __call__(self, func, *args, **kwargs):
print func, args, kwargs
# do processing
return func
#LoggerDecorator('lala')
def a():
print 1
The above should work as expected. If you're planning to call the decorator using keyword arguments you can remove the logger from __init__ and use **kwargs which will return a dict of the passed keywork arguments.

Why does a decorated class looses its docstrings?

I'm currently implementing an API on which I need to decorate the class Wrapper, but I want it to keep its docstring to make it available to the API user. Take a look at the following minimal working example :
class ClassDecorator:
"""ClassDecorator docstring
"""
def __init__(self, enableCache=True):
self.enableCache = enableCache
def __call__(self, wrapper):
def numericalmathfunction(*args, **kwargs):
func = wrapper(*args, **kwargs)
return func
return numericalmathfunction
#ClassDecorator(enableCache=True)
class Wrapper(object):
"""Wrapper docstring
Instructions on how to use the Wrapper
"""
def __init__(self, p):
self.p = p
model = Wrapper(4)
print model.__doc__
print Wrapper.__doc__
This returns
Wrapper docstring
None
Instances of Wrapper do keep the docstring, which is fine, but Wrapper itself does not. If a user wants to learn how to use Wrapper using help(Wrapper), he won't get what he wants.
I know I could just copy paste the dosctring into numericalmathfunction, but the decorator will be used on several classes with different docstrings.
Any ideas on how to make numericalmathfunction systematically inherit the docstrings of the wrapped class ?
Use functools.wraps() to update the attributes of the decorator:
from functools import wraps
class ClassDecorator:
"""ClassDecorator docstring
"""
def __init__(self, enableCache=True):
self.enableCache = enableCache
def __call__(self, wrapper):
#wraps(wrapper) # <-----------
def numericalmathfunction(*args, **kwargs):
func = wrapper(*args, **kwargs)
return func
return numericalmathfunction
#ClassDecorator(enableCache=True)
class Wrapper(object):
"""Wrapper docstring
Instructions on how to use the Wrapper
"""
def __init__(self, p):
self.p = p
See more standard library documentation for functools.wrap.

Python decorator on instance method

Anyone know what is wrong with this code?
def paginated_instance_method(default_page_size=25):
def wrap(func):
#functools.wraps(func)
def inner(self, page=1, page_size=default_page_size, *args, **kwargs):
objects = func(self=self, *args, **kwargs)
return _paginate(objects, page, page_size)
return inner
return wrap
class Event(object):
...
#paginated_instance_method
def get_attending_users(self, *args, **kwargs):
return User.objects.filter(pk__in=self.attending_list)
I get the following error:
Traceback (most recent call last):
File "<console>", line 1, in <module>
File "/Users/zarathustra/Virtual_Envs/hinge/hinge_services/hinge/api/decorators.py", line 108, in wrap
def inner(self, page=1, page_size=default_page_size, *args, **kwargs):
File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/functools.py", line 33, in update_wrapper
setattr(wrapper, attr, getattr(wrapped, attr))
AttributeError: 'Event' object has no attribute '__name__'
The reason why I thought this would work is because, through trial and error, I got the following decorator working like a charm for classmethods:
def paginated_class_method(default_page_size=25):
def wrap(func):
#functools.wraps(func)
def inner(cls, page=1, page_size=default_page_size, *args, **kwargs):
objects = func(cls=cls, *args, **kwargs)
return _paginate(objects, page, page_size)
return inner
return wrap
paginated_instance_method is not a decorator, it is a function that returns a decorator. So
#paginated_instance_method()
def get_attending_users(self, *args, **kwargs):
(Note the parentheses.)
Your decorator has an extra level of indirection which is throwing things off. When you do this:
#paginated_instance_method
def get_attending_users(self, *args, **kwargs):
return User.objects.filter(pk__in=self.attending_list)
You are doing this:
def get_attending_users(self, *args, **kwargs):
return User.objects.filter(pk__in=self.attending_list)
get_attending_users = paginated_instance_method(get_attending_users)
That is what decorators do. Note that paginated_instance_method is called with get_attending_users as its argument. That means that in your decorator, the argument default_page_size is set to the function get_attending_users. Your decorator returns the function wrap, so get_attending_users is set to that wrap function.
Then when you then call Event().get_attending_users() it calls wrap(self), where self is your Event instance. wrap is expecting the argument to be a function, and tries to return a new function wrapping that function. But the argument isn't a function, it's an Event object, so functools.wrap fails when trying to wrap it.
I have a hunch that what you're trying to do is this:
#paginated_instance_method()
def get_attending_users(self, *args, **kwargs):
return User.objects.filter(pk__in=self.attending_list)
That is, you want paginated_instance_method to take an argument. But even if you want to use the default value of that argument, you still have to actually call paginated_instance_method. Otherwise you just pass the method as the argument, which is not what paginated_instance_method is expecting.
The reason it "worked" for a classmethod is that a classmethod takes the class as the first argument, and a class (unlike an instance) does have a __name__ attribute. However, I suspect that if you test it further you'll find it's not really doing what you want it to do, as it's still wrapping the class rather than the method.
This is really easy, but tricky at first view. Look at pep 318.
#dec2
#dec1
def func(arg1, arg2, ...):
pass
This is equivalent to:
def func(arg1, arg2, ...):
pass
func = dec2(dec1(func))
You have an extra wrapper, which takes a decorator's args to use it in the wrapped functions (closure design pattern). So your decorator will look like this:
#dec(arg=True)
def func(arg1, arg2, ...):
pass
Equivalent to:
def func(arg1, arg2, ...):
pass
func = dec(arg=True)(func)

Creating a method that is simultaneously an instance and class method

In Python, I'd like to be able to create a function that behaves both as a class function and an instance method, but with the ability to change behaviors. The use case for this is for a set of serializable objects and types. As an example:
>>> class Thing(object):
#...
>>> Thing.to_json()
'A'
>>> Thing().to_json()
'B'
I know that given the definition of classmethod() in funcobject.c in the Python source, this looks like it'd be simple with a C module. Is there a way to do this from within python?
Thanks!
With the hint of descriptors, I was able to do it with the following code:
class combomethod(object):
def __init__(self, method):
self.method = method
def __get__(self, obj=None, objtype=None):
#functools.wraps(self.method)
def _wrapper(*args, **kwargs):
if obj is not None:
return self.method(obj, *args, **kwargs)
else:
return self.method(objtype, *args, **kwargs)
return _wrapper
Thank you Alex!
Sure, you just need to define your own descriptor type. There's an excellent tutorial on Python descriptors here.

Categories