I have an issue with setattr when using it in inheritance with classes and decorators.
#!/usr/bin/env python3
import inspect
def make_class_decorator(function_decorator):
def class_decorator(cls):
for attr_name in inspect.getmembers(cls, inspect.isroutine):
attr_name = attr_name[0]
if str(attr_name).startswith('__') and str(attr_name).endswith('__'): continue
attr_value = getattr(cls, str(attr_name))
setattr(cls, attr_name, function_decorator(cls, attr_value))
return cls
return class_decorator
#make_class_decorator
def auto_debug_logging(cls, called_function):
def wrapped(*args, **kwargs):
print('wrapped')
return called_function(*args, **kwargs)
wrapped.__name__ = called_function.__name__
return wrapped
def xclassmethod(called_function):
def wrapped(*args, **kwargs):
print('wrappedx: ' + called_function.__name__)
return builtins.classmethod(called_function(*args, **kwargs))
return wrapped
class X:
#xclassmethod
def get_i(cls):
cls._inst = cls()
return cls._inst
#auto_debug_logging
class A(X):
pass
#auto_debug_logging
class B(A):
def test(self):
print('test')
B.get_i().test()
Example code shows that all functions are wrapped, and prints "wrapped" before actual call. However, when I call B.get_i().test I'm getting following error:
AttributeError: 'A' object has no attribute 'test'
It seems that setattr somehow sets the original B class to A, as when I comment out that the issue disappears.
Related
I'm trying to implement privacy modifiers into python using decorators.
My problem is that whenever I decorate a method that has self as an argument, when the method is called using dot notation, it doesn't pass self in automatically.
Public decorator class:
class Public:
def __init__(self, method, *args):
if type(method).__name__ == 'function':
self.method = method
def __call__(self, *args, **kwargs):
return self.method(*args, **kwargs)
Example code:
class Test:
#Public
def test(self):
return "Hello"
class Test1(Test):
def __init__(self):
super().__init__()
print(self.test())
x = Test1()
How do I pass self into Public.__call__?
I tried regularly passing in self:
class Test:
#Public
def test(self):
return "Hello"
class Test1(Test):
def __init__(self):
super().__init__()
print(self.test(self))
x = Test1()
which works but I would much rather not have to do that every time I need to call a method.
I found an answer
Here's how if anyone else is doing something similar:
First I had to create a decorator function that changed the getattribute function in the class
def modifiable(cls):
if isinstance(cls, type):
original_getattr = cls.__getattribute__
def getattr(_self, name):
attr = original_getattr(_self, name)
if isinstance(attr, Modifier):
def wrapper(*args, **kwargs):
return attr(_self, *args, **kwargs)
return wrapper
return attr
cls.__getattribute__ = getattr
return cls
I also created an empty Modifier class that all the privacy modifiers inherit from to make it easier to check if a method is modified.
example code:
#modifiable
class Test:
#Protected
def test(self):
return "Hello"
#modifiable
class Test1(Test):
x = 1
def __init__(self):
super().__init__()
print(self.test())
test = Test1()
print(test.test())
and output:
Hello
Traceback (most recent call last):
File "C:\Users\tyson\OneDrive\Documents\GitHub\better-decorators\src\test.py", line 21, in <module>
print(test.test())
privacy.AccessError: test is a protected method
(privacy.AccessError is a custom error)
I have this class method decorator from https://stackoverflow.com/a/46361833/5316326 that accepts parameters.
How can I list the class methods that use this decorator on the created object?
from functools import update_wrapper, partial
def my_decorator(name):
class MyDecorator(object):
def __init__(self, func):
update_wrapper(self, func)
self.func = func
def __get__(self, obj, objtype):
"""Support instance methods."""
return partial(self.__call__, obj)
def __call__(self, obj, *args, **kwargs):
return self.func(obj, *args, **kwargs)
return MyDecorator
class MyClass(object):
t=12
#my_decorator("hee")
def my_method(self, a):
print(a+self.t)
The following attempt works, but by parsing the partial function to a string, there should be a correct implementation:
def find_my_decorators(cls):
def g():
for name in dir(cls):
attr = getattr(cls, name)
if isinstance(attr, partial) and 'MyDecorator' in str(attr.func):
yield name
return [name for name in g()]
It gives, however, the desired results:
>>> find_my_decorators(MyClass)
['my_method']
>>> m = MyClass()
>>> m.my_method(a=12)
>>> find_my_decorators(m)
['my_method']
It works both on the class as the object.
Background
I wrote a decorator function to modify the __repr__ of a given class, such that when an class instance is called all its attributes are printed to the user. When used in the on the Container class in the example below the decorator __repr__dec behaves as intended.
Input
def __repr__wrapper(self):
"""Show all attributes."""
return "Attributes: "+", ".join(list(self.__dict__.keys()))
def __repr__dec(func):
"""Replaces the __repr__ function of a class with __repr__wrapper"""
def call(*args, **kwargs):
func.__repr__ = __repr__wrapper
result = func(*args, **kwargs)
return result
return call
#__repr__dec
class Container(object):
def __init__(self, *args, **kwargs):
self.metadata = args[0]
for k,v in kwargs.items():
self.__dict__[k] = v
occ = Container(42, how="now")
occ
Output
Attributes: metadata, how
However when trying to subclass Container I receive a TypeError message:
Input
class Handle(Container):
def __init__(self, *args, **kwargs):
Container.__init__(self, *args, **kwargs)
han = Handle(42)
Output
TypeError Traceback (most recent call last)
<ipython-input-17-b4c252411c1f> in <module>()
----> 1 class Handle(Container):
2 def __init__(self, *args, **kwargs):
3 Container.__init__(self, *args, **kwargs)
4
5 han = Handle(42)
TypeError: function() argument 1 must be code, not str
Question
Why does sub-classing Conatainer fail when using the __repr__dec function? Is it possible to fix this?
The problem is that your decorator made Container a function and no longer a class. You can control it very simply:
>>> type(Container)
<class 'function'>
This is because your use of the decorator ends in the following:
declare a undecorated class
class Container:
...
use the decorator on it:
Container = __repr__dec(Container)
As __repr__dec returns a function you have indeed change Container into a function able to return objects having the expected __repr__ member, but it is no longer a class.
Your decorator must return a class if you want to be able to later subclass it:
def repr_dec(cls):
cls.__repr__ = __repr__wrapper
return cls
Then everything is fine:
>>> Container
<class '__main__.Container'>
>>> occ=Container(42, how="now")
>>> occ
Attributes: metadata, how
And you can successfully subclass it:
>>> class Handle(Container):
def __init__(self, *args, **kwargs):
Container.__init__(self, *args, **kwargs)
>>> han = Handle(42, foo="now")
>>> han
Attributes: metadata, foo
Handle class has inherited the __repr__ method from its parent.
def replace_str(cls):
class Wrapper:
def __init__(self, *args, **kargs):
self.wrapped = cls(*args, **kargs)
def __getattr__(self, attrname):
return getattr(self.wrapped, attrname)
def __str__(self):
return "Attributes: " + ", ".join(list(self.wrapped.__dict__.keys()))
return Wrapper
#replace_str
class Container(object):
def __init__(self, *args, **kwargs):
self.metadata = args[0]
for k,v in kwargs.items():
self.__dict__[k] = v
Using a proxy class could easily achieve this.
also, metaclass could do this:
class PrintKey(type):
def __new__(meta, classname, bases, namespace):
namespace['__str__'] = lambda self: "Attributes: " + ", ".join(list(self.__dict__.keys()))
return type.__new__(meta, classname, bases, namespace)
class Container(object, metaclass=PrintKey):
def __init__(self, *args, **kwargs):
self.metadata = args[0]
for k,v in kwargs.items():
self.__dict__[k] = v
import inspect
import functools
def for_all_test_methods(decorator):
def decorate(cls):
for name, value in inspect.getmembers(cls, inspect.isroutine):
if name.startswith('test'):
setattr(cls, name, test_decorator(getattr(cls, name)))
return cls
return decorate
def test_decorator(func):
#functools.wraps(func)
def wrapper(*args, **kwargs):
print(func.__name__, args, kwargs)
res = func(*args, **kwargs)
return res
return wrapper
#for_all_test_methods(test_decorator)
class Potato(object):
def test_method(self):
print('in method')
class Spud(Potato):
def test_derived(self):
print('in derived')
Now if I create a spud instance the test_method which it has inherited remains decorated, but it has an undecorated method test_derived. Unfortunately, if I add the class decorator onto Spud aswell, then his test_method gets decorated twice!
How do I correctly propagate decorators from the parent class onto the children?
You cannot avoid decorating derived classes; you can find subclasses of a class after subclasses have been decorated, but not auto-decorate them. Use a metaclass instead of you need that sort of behaviour.
You can do one of two things:
Detect already-decorated methods; if there is a __wrapped__ attribute you have a wrapper:
def for_all_test_methods(decorator):
def decorate(cls):
for name, value in inspect.getmembers(cls, inspect.isroutine):
if name.startswith('test') and not hasattr(value, '__wrapped__'):
setattr(cls, name, test_decorator(getattr(cls, name)))
return cls
return decorate
Limit the class decorator to direct methods only:
def for_all_test_methods(decorator):
def decorate(cls):
for name, value in cls.__dict__.iteritems():
if name.startswith('test') and inspect.isroutine(value)):
setattr(cls, name, test_decorator(getattr(cls, name)))
return cls
return decorate
Here is how you can accomplish this by using a metaclass instead of decorating the class:
import inspect
import functools
def test_decorator(func):
#functools.wraps(func)
def wrapper(*args, **kwargs):
print(func.__name__, args, kwargs)
res = func(*args, **kwargs)
return res
return wrapper
def make_test_deco_type(decorator):
class TestDecoType(type):
def __new__(cls, clsname, bases, dct):
for name, value in dct.items():
if name.startswith('test') and inspect.isroutine(value):
dct[name] = decorator(value)
return super().__new__(cls, clsname, bases, dct)
return TestDecoType
class Potato(object, metaclass=make_test_deco_type(test_decorator)):
def test_method(self):
print('in method')
class Spud(Potato):
def test_derived(self):
print('in derived')
On Python 2.x you would use __metaclass__ = make_test_deco_type(test_decorator) as the first line of the class body instead of having the metaclass=... portion of the class statement. You would also need to replace super() with super(TestDecoType, cls).
I have a decorator declared as a class:
class predicated(object):
def __init__(self, fn):
self.fn = fn
self.fpred = lambda *args, **kwargs: True
def predicate(self, predicate):
self.fpred = predicate
return self
def validate(self, *args, **kwargs):
return self.fpred(*args, **kwargs)
def __call__(self, *args, **kwargs):
if not self.validate(*args, **kwargs):
raise PredicateNotMatchedError("predicate was not matched")
return self.fn(*args, **kwargs)
... and when I use it to wrap a method in a class, calling that method does not seem to set the instance of the object as the first argument. While this behavior is not exactly unexpected, how would I go about getting self to be frozen when the method becomes an instance method?
Simplified example:
class test_decorator(object):
def __init__(self, fn):
self.fn = fn
def __call__(self, *args, **kwargs):
return self.fn(*args, **kwargs)
class Foo(object):
#test_decorator
def some_method(self):
print(self)
Foo().some_method()
Expected instance of foo, instead get an error saying 0 arguments were passed.
Figured it out - needed to define a __get__ method in order to create a MethodType binding like so:
def __get__(self, obj, objtype=None):
return MethodType(self, obj, objtype)
which creates a MethodType object when invoking the method on an object that freezes the self argument.