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
Related
I need to keep tracks of instances of some classes (and do other stuff with those classes). I would like to not have to declare any extra code in the classes in question, thus everything should ideally be handled in the metaclass.
What I can't figure out is how to add a weak reference to each new instance of those classes. For example:
class Parallelizable(type):
def __new__(cls, name, bases, attr):
meta = super().__new__(cls, name, bases, attr)
# storing the instances in this WeakSet
meta._instances = weakref.WeakSet()
return meta
#property
def instances(cls):
return [x for x in cls._instances]
class Foo(metaclass=Parallelizable)
def __init__(self, name):
super().__init__()
self.name = name
# I would like to avoid having to do that - instead have the metaclass manage it somehow
self._instances.add(self)
Any ideas? I can't seem to find a hook on the metaclass side to get into the __init__ of Foo....
The method on the metaclass that is called when each new instance of its "afiliated" classes is __call__. If you put the code to record the instances in there, that is all the work you need:
from weakref import WeakSet
# A convenient class-level descriptor to retrieve the instances:
class Instances:
def __get__(self, instance, cls):
return [x for x in cls._instances]
class Parallelizable(type):
def __init__(cls, name, bases, attrs, **kw):
super().__init__(name, bases, attrs, **kw)
cls._instances = WeakSet()
cls.instances = Instances()
def __call__(cls, *args, **kw):
instance = super().__call__(*args, **kw)
cls._instances.add(instance)
return instance
The same code will work without the descriptor at all - it is just a nice way to have a class attribute that would report the instances. But if the WeakSet is enough, this code suffices:
from weakref import WeakSet
class Parallelizable(type):
def __init__(cls, name, bases, attrs, **kw):
super().__init__(name, bases, attrs, **kw)
cls.instances = WeakSet()
def __call__(cls, *args, **kw):
instance = super().__call__(*args, **kw)
cls.instances.add(instance)
return instance
You could decorate the attrs['__init__'] method in Parallizable.__new__:
import weakref
import functools
class Parallelizable(type):
def __new__(meta, name, bases, attrs):
attrs['__init__'] = Parallelizable.register(attrs['__init__'])
cls = super().__new__(meta, name, bases, attrs)
cls._instances = weakref.WeakSet()
return cls
#classmethod
def register(cls, method):
#functools.wraps(method)
def newmethod(self, *args, **kwargs):
method(self, *args, **kwargs)
self._instances.add(self)
return newmethod
#property
def instances(cls):
return [x for x in cls._instances]
class Foo(metaclass=Parallelizable):
def __init__(self, name):
"Foo.__init__ doc string"
super().__init__()
self.name = name
# Notice that Foo.__init__'s docstring is preserved even though the method has been decorated
help(Foo.__init__)
# Help on function __init__ in module __main__:
#
# __init__(self, name)
# Foo.__init__ doc string
stilton = Foo('Stilton')
gruyere = Foo('Gruyere')
print([inst.name for inst in Foo.instances])
# ['Gruyere', 'Stilton']
del stilton
print([inst.name for inst in Foo.instances])
# ['Gruyere']
How about this, its a class to inherit from, instead of a metaclass. I think its simpler but achieves the same point:
class AutoDiscovered:
instances = []
def __new__(cls, *args, **kwargs):
obj = super().__new__(cls)
cls.instances.append(obj)
return obj
Usage:
class Foo(AutoDiscovered):
pass
a = Foo()
b = Foo()
print(Foo.instances) # [<__main__.Foo object at 0x7fdabd345430>, <__main__.Foo object at 0x7fdabd345370>]
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.
I have a class that I want it to accept an instance of that same class as initialization; in such case, it will simply return that instance.
The reason is that I want this class to accept a myriad of initialization values and then the proceeding code can use this as an object with known properties, independent on how it was initialized.
I have thought of something like:
class c(object):
def __new__(cls, *args, **kwargs):
if isinstance(args[0], c):
return args[0]
else:
return super(c, cls).__new__(cls, *args, **kwargs)
The problem is that I don't want __init__() to be called when initialized in this manner. Is there any other way?
Thanks!
You probably want to use a factory (f.e. see this question for details or google).
Or just use a class method for what you want, f.e.:
class C(object):
#classmethod
def new(cls, *args, **kwargs):
if isinstance(args[0], cls):
return args[0]
else:
return cls(*args, **kwargs)
obj = C.new()
obj2 = C.new(obj)
The standard way to do this is to simply not do your initialization in __init__. Do it in __new__.
You can use a metaclass
class InstanceReturnMeta(type): # You should probably think of a better name
def __call__(cls, *args, **kwargs):
if args and isinstance(args[0], cls):
return args[0]
instance = cls.__new__(cls, *args, **kwargs)
instance.__init__(*args, **kwargs)
return instance
class Test(object):
__metaclass__ = InstanceReturnMeta
def __init__(self, value):
self.value = value
Let's test it
In [3]: instance1 = Test(0)
In [4]: instance2 = Test(instance1)
In [5]: print id(instance1) == id(instance2)
Out[5]: True
The ids are identical, hence both variables reference the same instance.
P.S. I assume you are on Python 2, since your class explicitly inherits from object.
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.
Here's an example of what I mean:
class MyDecorator(object):
def __call__(self, func):
# At which point would I be able to access the decorated method's parent class's instance?
# In the below example, I would want to access from here: myinstance
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
return wrapper
class SomeClass(object):
##self.name = 'John' #error here
name="John"
#MyDecorator()
def nameprinter(self):
print(self.name)
myinstance = SomeClass()
myinstance.nameprinter()
Do I need to decorate the actual class?
class MyDecorator(object):
def __call__(self, func):
def wrapper(that, *args, **kwargs):
## you can access the "self" of func here through the "that" parameter
## and hence do whatever you want
return func(that, *args, **kwargs)
return wrapper
Please notice in this context that the use of "self" is just a convention, a method just uses the first argument as a reference to the instance object:
class Example:
def __init__(foo, a):
foo.a = a
def method(bar, b):
print bar.a, b
e = Example('hello')
e.method('world')
The self argument is passed as the first argument. Also your MyDecorator is a class emulating a function. Easier to make it an actual function.
def MyDecorator(method):
def wrapper(self, *args, **kwargs):
print 'Self is', self
return method(self, *args, **kwargs)
return wrapper
class SomeClass(object):
#MyDecorator
def f(self):
return 42
print SomeClass().f()