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>]
Related
I am trying to create a Singleton class in Python using this code:
class Singleton(type):
_instances = {}
def __call__(cls, *args, **kwargs):
if cls not in cls._instances:
cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs)
return cls._instances[cls]
def clear(cls):
cls._instances = {}
class MyClass(metaclass=Singleton):
def my_attribute(*args):
if len(args) == 1:
MyClass.i_attribute = args[0]
elif len(args) == 0:
try:
return MyClass.i_attribute
except:
MyClass.i_attribute = 0
but the clear() method does not seem to work:
MyClass.my_attribute(42)
MyClass.clear()
MyClass.my_attribute() # still returns 42, but I expect 0
How do I delete the instance of MyClass so that I am back to 0 instances?
The singleton metaclass collects in the _instances attribute all the instantiated children. Therefore, if you want to clear the _instances attributes only for a specific class, you can:
Redefine the Singleton class, to make _instances an attribute of the class instantiated by the metaclass:
class Singleton(type):
"""
Singleton metaclass, which stores a single instance of the children class in the children class itself.
The metaclass exposes also a clearing mechanism, that clear the single instance:
* clear: use as follows 'ClassToClear.clear()
"""
def __init__(cls, name, bases, methods):
cls._instance = None
super().__init__(name, bases, methods)
def __call__(cls, *args, **kwargs):
if cls._instance:
return cls._instance
cls._instance = super().__call__(*args, **kwargs)
return cls._instance
def clear(cls):
cls._instance = None
Using this new definition of the singleton, you can write a clear method that can be called by any of the classes initialized with the Singleton metaclass and it will clear the _instance attribute.
So in your case, MyClass.clear() would reset the _instance attribute to None.
Add a clear method, which removes only the children class from the Singleton._instances dictionary:
class SingletonRegistry(type):
"""
Singleton metaclass, which implements a registry of all classes that are created through this metaclass and
the corresponding instance of that class (added at the first creation).
The metaclass exposes also a clearing mechanism, that clears a specific class from the registry:
* clear: use as follows 'ClassToClear.clear()
* clear_all: use as follows 'SingletonRegistry.clear_all()
"""
_instances = {}
def __call__(cls, *args, **kwargs):
if cls not in cls._instances:
cls._instances[cls] = super(SingletonRegistry, cls).__call__(*args, **kwargs)
return cls._instances[cls]
def clear(cls):
_ = cls._instances.pop(cls, None)
def clear_all(*args, **kwargs):
SingletonRegistry._instances = {}
In this case, if you would like to clear only one specific child class, then you could write MyClass.clear(), which will cause the MyClass key to be removed from Singleton._instances dictionary.
This structure allows also to clear all key in the _instances dictionary by writing SingletonRegistry.clear_all().
user2357112 is right. Here is the correct code:
class Singleton(type):
_instances = {}
def __call__(cls, *args, **kwargs):
if cls not in cls._instances:
cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs)
return cls._instances[cls]
def clear(cls):
cls._instances = {}
class MyClass(metaclass=Singleton):
def my_attribute(*args):
my = MyClass()
if len(args) == 1:
my.i_attribute = args[0]
elif len(args) == 0:
try:
return my.i_attribute
except:
my.i_attribute = 0
return my.i_attribute
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
I'm wondering how to create a metaclass in Python that can create other classes that:
Store their instances in an array automatically
Have a special instance, NonMetaClass.all, whose properties:
When set, set all the class's instances with the same key to the same value (e.g., Foo.all.num = 3 makes all instances of Foo have a num of 3)
When accessed (get), returns an array of all of the class's instances's key values (e.g., Foo.all.num returns [5, 3, 2])
Cannot be deleted.
When called (if the attribute is a function), call that method on all the instances of a class.
In Python terms, I would like to turn a class that is like this:
class Foo(object):
BAR = 23
def __init__(self):
self.a = 5
def pointless():
print 'pointless.'
def change_a(self):
self.a = 52
Into this:
class Foo(object):
BAR = 23
instances = []
all = # Some black magic to create the special "all" instance
def __init__(self):
self.a = 5
Foo.instances.append(self)
def pointless(self):
print 'pointless.'
def change_a(self):
self.a = 52
And be able to use it like this:
>>> Foo()
>>> Foo.instances[0]
<__main__.Foo instance at 0x102ff5758>
>>> Foo()
>>> len(Foo.instances)
2
>>> Foo.all.a = 78
78
>>> Foo.all.a
[78, 78]
>>> Foo.all.change_a()
>>> Foo.all.a
[52, 52]
>>>
The only thing a metaclass is needed for there is actually quite easy:
exactly creating the intances and all attributes.
All it have to do is to insert those into the namespace. Ah, it will also have to wrap the class __new__ method to insert new instances into the instances list.
The part that is the behavior wanted from all is interesting, and that can be implemented using the descriptor protocol, and attribute access control, so we have to craft a couple special classes, that will return the appropriate objects when requested after the ".".
"All" is the class that will be instantiated as "all" - it just needs a __get__ method to return another special object, from the AllAttr class, already bound to the parent class.
"AllAttr" is a special object that on any attribute access, perform your requirements on the members of the owner class "instance" attribute.
And "CallAllList" is a special list subclass that is callable, and calls all its members in turn. It is used by AllAttr if the required attribute from the owner class is callable itself.
class CallAllList(list):
def __call__(self, *args, **kwargs):
return [instance(*args, **kwargs) for instance in self]
class AllAttr(object):
def __init__(self, owner):
self._owner = owner
def __getattr__(self, attr):
method = getattr(self._owner, attr, None)
cls = CallAllList if callable(method) else list
return cls(getattr(obj, attr) for obj in self._owner.instances)
def __setattr__(self, attr, value):
if attr == "_owner":
return super(AllAttr, self).__setattr__(attr, value)
for obj in self._owner.instances:
setattr(obj, attr, value)
class All(object):
def __get__(self, instance, owner):
return AllAttr(owner)
def __repr__(self):
return "Representation of all instances of '{}'".format(self.__class__.__name__)
class MetaAll(type):
def __new__(metacls, name, bases, namespace):
namespace["all"] = All()
namespace["instances"] = []
cls = super(MetaAll, metacls).__new__(metacls, name, bases, namespace)
original_new = getattr(cls, "__new__")
def __new__(cls, *args, **kwargs):
instance = original_new(cls, *args, **kwargs)
cls.instances.append(instance)
return instance
cls.__new__ = __new__
return cls
class Foo(metaclass=MetaAll):
pass
The code above is written so that it is Python 3 and Python 2 compatible, since you appear to still be using Python2 given your "print" example.
The only thing that cannot be written compatible with both forms is the metaclass using declaration itself - just declare a __metaclass__ = MetaAll inside the body of your Foo class if you are using Python 2. But you should not really be using Python2, just change to Python 3 as soon as you can.
update
It happens that Python 2 has the "unbound method" figure, and the special casing of __new__ does not work like in Python 3: you can't just attribute a function named __new__ to the class. In order to get the correct __new__ method from the superclasses, the easiest way is to create a disposable class, so that it can be searched linearly. Otherwise, one would have to reimplement the MRO algorithm to get the proper __new__ method.
So, for Python 2, the metaclass should be this:
class MetaAll(type):
def __new__(metacls, name, bases, namespace):
namespace["all"] = All()
namespace["instances"] = []
if "__new__" in namespace:
original_new = namespace["__new__"]
def __new__(cls, *args, **kwargs):
instance = original_new(cls, *args, **kwargs)
cls.instances.append(instance)
return instance
else:
# We create a disposable class just to get the '__mro__'
stub_cls = super(MetaAll, metacls).__new__(metacls, name, bases, {})
for parent in stub_cls.__mro__[1:]:
if "__new__" in parent.__dict__:
original_new = parent.__dict__["__new__"]
break
def __new__(cls, *args, **kwargs):
instance = original_new(cls, *args, **kwargs)
cls.instances.append(instance)
return instance
namespace["__new__"] = __new__
final_cls = super(MetaAll, metacls).__new__(metacls, name, bases, namespace)
return final_cls
class Foo(object):
__metaclass__ = MetaAll
(now, again, this thing is ancient. Just settle for Python 3.6)
Ok, I figured out how to do this for Python 2.7 on my own. This is what I believe to be the best solution though it may not be the only one. It allows you to set, get, and function call on attributes of Class.all. I've named the metaclass InstanceUnifier, but please comment if you think there's a better (shorter, more descriptive) name you can think of.
class InstanceUnifier(type):
'''
What we want: A metaclass that can give a class an array of instances and provide a static Class.all object, that, when a method is called on it, calls the same method on every instance of the class.
'''
def __new__(cls, name, base_classes, dct):
dct['all'] = None
dct['instances'] = []
return type.__new__(cls, name, base_classes, dct)
def __init__(cls, name, base_classes, dct):
class Accessor(object):
def __getattribute__(self, name):
array = [getattr(inst, name) for inst in cls.instances]
if all([callable(item) for item in array]):
def proxy_func(*args, **kwargs):
for i in range(len(cls.instances)):
this = cls.instances[i]
func = array[i]
func(*args, **kwargs)
return proxy_func
elif all([not callable(item) for item in array]):
return array
else:
raise RuntimeError('Some objects in class instance array for key "'+name+'" are callable, some are not.')
def __setattr__(self, name, value):
[setattr(inst, name, value) for inst in cls.instances]
def __delattr__(self, name):
[delattr(inst, name) for inst in cls.instances]
cls.all = Accessor()
return type.__init__(cls, name, base_classes, dct)
def __call__(cls, *args, **kwargs):
inst = type.__call__(cls, *args, **kwargs)
cls.instances.append(inst)
return inst
I have a number of classes with code like this. Each __init__ starts a thread and a logger with the name of the class. How do I get the name of the current class in its own definition, as a string, inside __init__? Note that self may not be an instance of the current class, so the following is not quite foolproof.
from threading import Thread
import logging
def myClassName(myclass):
myclass._class_name = myclass.__name__
return myclass
#myClassName
class SomeClass(object):
def __init__(self):
class_name = type(self)._class_name
print "My class name in __init__ is", class_name
self.thread = Thread(name=class_name)
self.logger = logging.getLogger(class_name)
Update:
To clarify:
I want the name of the class being defined, not the class of the
object passed in.
I don't want to hard code the name of the class.
I want to make it easy to copy/paste an example from one script to
another, and the fewer mentions of the unique class name, the better.
(Inheritance isn't really efficient, as there are enough custom
differences to make it awkward. But accidentally leaving in the name
of the wrong class is a hard bug to find.)
In Python 3 this is pretty straight forward, we can use the __class__ cell variable to get the current class.
In Python 2 we can achieve something similar by injecting class's name in functions globals scope using a metaclass and later cleaning it up.
from functools import wraps
from types import FunctionType
def decorate(func, class_name):
#wraps(func)
def wrapper(*args, **kwargs):
sentinel = object()
actual_value = func.__globals__.get('__class__', sentinel)
func.__globals__['__class__'] = class_name
try:
result = func(*args, **kwargs)
finally:
if actual_value is sentinel:
del func.__globals__['__class__']
else:
func.__globals__['__class__'] = actual_value
return result
return wrapper
class Meta(type):
def __new__(cls, name, bases, attrs):
for k, v in attrs.items():
if isinstance(v, FunctionType):
attrs[k] = decorate(v, name)
return type.__new__(cls, name, bases, attrs)
class A:
__metaclass__ = Meta
def func(self):
print(__class__)
print('Inside A')
class B(A):
def func(self):
print(__class__)
print('Inside B')
super(B, self).func()
B().func()
Output:
B
Inside B
A
Inside A
To get the __class__ variable as the class object itself we can make few changes:
def decorate(func, cls):
#wraps(func)
def wrapper(*args, **kwargs):
sentinel = object()
actual_value = func.__globals__.get('__class__', sentinel)
func.__globals__['__class__'] = cls
try:
result = func(*args, **kwargs)
finally:
if actual_value is sentinel:
del func.__globals__['__class__']
else:
func.__globals__['__class__'] = actual_value
return result
return wrapper
class Meta(type):
def __new__(cls, name, bases, attrs):
cls = type.__new__(cls, name, bases, attrs)
for k, v in attrs.items():
if isinstance(v, FunctionType):
setattr(cls, k, decorate(v, cls))
return cls
Now output would be:
<class '__main__.B'>
Inside B
<class '__main__.A'>
Inside A
You can retrieve the name of the class of an an object thus:
obj.__class__.__name__
Example:
class SomeClass(object):
def __init__(self):
print("I am a %s"%self.__class__.__name__)
class Derived(SomeClass):
pass
x = SomeClass()
y = Derived()
Result:
$ python x.py
I am a SomeClass
I am a Derived
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).