Ability to set properties in the child of an abstract class - python

I've been battling with this for half an hour, so I have passed the try it yourself for half an hour rule and am asking for your help. I am trying to get the Child go the abstract class's setter abstract method, but it just won't work...
#!/usr/bin/env python3
from abc import ABC, abstractmethod
from typing import List
class Indicator(ABC):
def __init__(self, **kwargs):
super().__init__()
pass
#abstractmethod
def calculate(self):
"""
kwargs in children will most likely be date_from, date_to, index
"""
raise NotImplementedError("The calculate method is not implemented!")
#property
#abstractmethod
def db_ids(self):
return self._db_ids
#db_ids.setter
#abstractmethod
def db_ids(self, ids: List[int]):
assert isinstance(ids, list)
assert all(isinstance(id_, int) for id_ in ids)
self._db_ids = ids
#property
#abstractmethod
def name(self):
return self._name
#name.setter
#abstractmethod
def name(self, set_name: str):
assert isinstance(set_name, str)
self._name = set_name
# …………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………
class ValueHistorical(Indicator):
def __init__(self, **kwargs):
if kwargs:
self.kwargs = kwargs
super(ValueHistorical, self).__init__(**kwargs)
self.db_ids = [119, 120, 121, 122]
self.name = 'Value Based on Historical'
#property
def db_ids(self):
return self._db_ids
#property
def name(self):
return self._name
def calculate(self):
pass
ValueHistorical(**{'date_from': '2010-01-01', 'date_to': '2012-01-01'})
arguments here don't matter. And the error I get is AttributeError: can't set the attribute'.
What I want to achieve is inside of ValueHistorical constructor it goes to it's Parent's abstract class's setters for db_ids and name when those are being assigned.

This has actually nothing to do with ABC, but with the fact that you rebound the properties in your child class, but without setters. This:
class ValueHistorical(Indicator):
#property
def db_ids(self):
return self._db_ids
#property
def name(self):
return self._name
Just replaces the parent's properties with the new ones, but defining those properties as read-only since you didn't provide a setter.
Remember that the decorator syntax is only syntactic sugar, so this:
#property
def getter(...): pass
is just a fancier way to write
def getter(...): pass
getter = property(getter)
Since the getter AND the setter are attributes of the property instance, when you redefine a property in a child class, you cannot just redefine the getter, you must also redefine the setter.
A common pattern here is to have the getter and setter (if there's one) delegating to another method, so you don't have to reimplement the whole thing, ie:
class Base(object):
#property
def foo(self):
return self._get_foo()
#foo.setter
def foo(self, value):
self._set_foo(value)
def _get_foo(self):
# ...
def _set_foo(self, value):
# ...
So a child class can override _get_foo and / or _set_foo without having to redefine the property.
Also, applying both property and abstractmethod to a function is totally useless. This:
#property
#abstractmethod
def db_ids(self):
return self._db_ids
is the equivalent of
def db_ids(self):
return self._db_ids
db_ids = property(abstractmethod(db_ids))
So what ABC will see here is the property - the fact that it's getter (and/or setter) have been decorated with abstractmethod is ignored, ABC will not inspect the propertie's getter and setter. And if you put them the other way round ie
db_ids = abstractmethod(property(db_ids))
then you don't define a property at all (actually, it will not work at all - you'll get an exception right from the start with " 'property' object has no attribute 'isabstractmethod'")
FWIW, the abstractmethod decorator is only meant to be used on methods that are NOT defined (empty body) so the child classes must implement them. If you have a default implementation, don't mark it as abstract, else why provide a default implementation at all ?
EDIT:
You mentionned in a comment (on a deleted answer) that:
I basically want ValueHistorical to go to the Abstract class's setter methods for db_ids and name when they are being assigned in the ValueHistorical constructor
Then the simplest solution is the one I explained above: define implementation methods for the getter and/or setter (you can make any of them or both abstract as you see fit) and use a concrete property to call those implementation methods.
Oh ans yes: assert is a developper tool, don't use it for typechecking in production code. If you really want to do typecheking (which sometimes makes sense but is most often than not a complete waste of time), use isinstance and raise a TypeError. As an example, your db_ids setter should look like this:
if not isinstance(ids, list):
raise TypeError("ids should be a list")
if not all(isinstance(id_, int) for id_ in ids)
raise TypeError("ids items should be ints")
Or even better:
# you don't care if it really was a list actually,
# as long as you can build a list out of it, and
# you don't care if it really contains ints as long
# as you can build ints out of them.
#
# No need for typecheck here, if `ids` is not iterable
# or what it yields cannot be used to build an int,
# this will raise, with way enough informations to
# debug the caller.
ids = [int(id) for id in ids)]

I read in https://pymotw.com/2/abc/
To use the decorator syntax does with read/write abstract properties, the methods to get and set the value should be named the same.
Don't think there's any way you can do this without requiring the setter. But IMO it's cleaner than using the super class setter logic with fset
from abc import ABC, abstractmethod, abstractproperty
from typing import List
class Indicator(ABC):
def __init__(self, **kwargs):
super().__init__()
#abstractproperty
def db_ids(self):
return self._db_ids
#db_ids.setter
#abstractmethod
def db_ids(self, ids: List[int]):
self._db_ids = ids
class ValueHistorical(Indicator):
def __init__(self, **kwargs):
if kwargs:
self.kwargs = kwargs
super(ValueHistorical, self).__init__(**kwargs)
self.db_ids = [119, 120, 121, 122]
#property
def db_ids(self):
return self._db_ids
#db_ids.setter
def db_ids(self, ids: List[int]):
self._db_ids = ids
i = ValueHistorical(**{'date_from': '2010-01-01', 'date_to': '2012-01-01'})
print(i.db_ids)

Related

How to decorate property in Python

I'm trying to add extra decorator for magic method (__get__) in descriptor class.
I'm able to do it when I use #property but not when I use descriptor class.
I check range because my object set registers on the bus and some registers can take only specific range of values:
import functools
def check_range(min, max):
def decorator(f):
#functools.wraps(f)
def wrap(self, value):
if value not in range(min, max+1):
return
return f(self, value)
return wrap
return decorator
This works:
class Foo:
def __init__(self):
self.device.init_smth('my_object')
#property
def my_object(self):
return self.device.get_value('my_object')
#my_object.setter
#check_range(0,1)
def my_object(self, value):
self.device.set_value('my_object', value)
a = Foo()
print(a.my_object)
a.my_object = 1
print(a.my_object)
a.myobject = -1
And in this example everything works the same but check_range is not invoked:
class Register:
def __init__(self, name, device):
self.name = name
device.init_smth(name)
def __get__(self, instance, owner):
return instance.device.get_value(self.name)
#check_range(0,1)
def __set__(self, instance, value):
instance.device.set_value(self.name, value)
class Foo:
def __init__(self):
self.my_object = Register('my_object', self.device)
a = Foo()
print(a.my_object)
a.my_object = 1
print(a.my_object)
a.myobject = -1
I may be wrong, but most probably your descriptor not invoked at all, decorator is not the problem. Descriptors meant to be used like
class Foo2:
my_object = Register('my_object', 'init_value')
— you're defining it like class attribute. And python will execute all machinery with __get__/__set__/__del__ if your class attribute supports it (i.e. it is descriptor).
This is why there is an "instance" argument in descriptor methods — you're defining descriptor as class variable, but i.e. __set__ method will receive actual instance of your class, so you can manage per-instance data, like your device

Python abstract setters and getters

I want to write abstract class that will force inheriting classes to implement all methods AND properties in my abstract class.
Additionally I want to use of setters and getters for my abstract property to make my code uncluttered and looking nicely
However, current implementation:
import abc
class Component(metaclass=abc.ABCMeta):
#property
#abc.abstractmethod
def status(self):
pass
#property
#status.setter
#abc.abstractmethod
def status(self, value):
pass
does enforce inheriting class to implement getter for my abstract property getter, but does not enforce creating a property setter (what is exactly what I want)
How can I achieve this behavior without loosing all benefits from application of further mentioned method (aka writing new methods and executing them in my abstract class setter) ?
from abc import ABCMeta, abstractmethod
class Base(object):
__metaclass__ = ABCMeta
def __init__(self, val):
self._foo = val
#abstractmethod
def _doStuff(self, signals):
print ('Base does stuff')
#abstractmethod
def _get_foo(self):
return self._foo
#abstractmethod
def _set_foo(self, val):
self._foo = val + 'r'
foo = property(_get_foo, _set_foo)
class floor_1(Base):
__metaclass__ = ABCMeta
def __init__(self, val):
self._foo = val
super(floor_1, self).__init__(val)
def _doStuff(self, signals):
print ('floor_1 does stuff')
def _get_foo(self):
return self._foo
def _set_foo(self, val):
#self._foo = val + 'r'
super()._set_foo(val + 'r')
foo = property(_get_foo, _set_foo)
class floor_2(floor_1):
#property
def foo(self):
return self._foo
#foo.setter
def foo(self, val):
self._foo = val + 'r'
#super()._set_foo(val + 'r')
b1 = floor_1('bar')
# b1 = floor_2('bar')
print(b1.foo)
b1.foo = 'bar'
print(b1.foo)
The problem is that neither the getter nor the setter is a method of your abstract class; they are attributes of the property, which is a (non-callable) class attribute. Consider this equivalent definition:
def status_getter(self):
pass
def status_setter(self, value):
pass
class Component(metaclass=abc.ABCMeta):
# status = property(...)
# status.__isabstractmethod__ = True
status = abstractmethod(property(status_getter, status_setter))
Inheriting a property is quite different from inheriting a method. You are basically replacing the property, because your class itself does not have a reference to either the getter or the setter. Despite the name, abstractmethod does not actually make the property a method; it really does nothing more than add an attribute to whatever it is applied to and return the original value.
So, to ensure that a subclass provides a read/write property, what are you to do? Skip the decorator syntax, define the getter and setter as explicit abstract methods, then define the property explicitly in terms of those private methods.
class Component(metaclass=abc.ABCMeta):
#abstractmethod
def _get_status(self):
pass
#abstractmethod
def _set_status(self, v):
pass
status = property(lambda self: self._get_status(), lambda self, v: self._set_status(self, v))
Or, you can make use of __init_subclass__ (which postdates abc; its purpose is to allow class initialization that is otherwise only possible via a metaclass).
class Component:
def __init_subclass(cls, **kwargs):
super().__init_subclass__(**kwargs)
try:
p = cls.status
except AttributeError:
raise ValueError("Class does not define 'status' attribute")
if not isinstance(p, property):
raise ValueError("'status' is not a property")
if p.fget is None:
raise ValueError("'status' has no getter")
if p.fset is None:
raise ValueError("'status' has no setter")
This is actually an improvement over abc, in my opinion. If a subclass fails to define a read/write status property, an exception will be raised when the class is defined, not just when you attempt to instantiate the class.

Using #classmethod with #property [duplicate]

This question already has answers here:
Using property() on classmethods
(19 answers)
Closed 3 years ago.
In python I can add a method to a class with the #classmethod decorator. Is there a similar decorator to add a property to a class? I can better show what I'm talking about.
class Example(object):
the_I = 10
def __init__( self ):
self.an_i = 20
#property
def i( self ):
return self.an_i
def inc_i( self ):
self.an_i += 1
# is this even possible?
#classproperty
def I( cls ):
return cls.the_I
#classmethod
def inc_I( cls ):
cls.the_I += 1
e = Example()
assert e.i == 20
e.inc_i()
assert e.i == 21
assert Example.I == 10
Example.inc_I()
assert Example.I == 11
Is the syntax I've used above possible or would it require something more?
The reason I want class properties is so I can lazy load class attributes, which seems reasonable enough.
Here's how I would do this:
class ClassPropertyDescriptor(object):
def __init__(self, fget, fset=None):
self.fget = fget
self.fset = fset
def __get__(self, obj, klass=None):
if klass is None:
klass = type(obj)
return self.fget.__get__(obj, klass)()
def __set__(self, obj, value):
if not self.fset:
raise AttributeError("can't set attribute")
type_ = type(obj)
return self.fset.__get__(obj, type_)(value)
def setter(self, func):
if not isinstance(func, (classmethod, staticmethod)):
func = classmethod(func)
self.fset = func
return self
def classproperty(func):
if not isinstance(func, (classmethod, staticmethod)):
func = classmethod(func)
return ClassPropertyDescriptor(func)
class Bar(object):
_bar = 1
#classproperty
def bar(cls):
return cls._bar
#bar.setter
def bar(cls, value):
cls._bar = value
# test instance instantiation
foo = Bar()
assert foo.bar == 1
baz = Bar()
assert baz.bar == 1
# test static variable
baz.bar = 5
assert foo.bar == 5
# test setting variable on the class
Bar.bar = 50
assert baz.bar == 50
assert foo.bar == 50
The setter didn't work at the time we call Bar.bar, because we are calling
TypeOfBar.bar.__set__, which is not Bar.bar.__set__.
Adding a metaclass definition solves this:
class ClassPropertyMetaClass(type):
def __setattr__(self, key, value):
if key in self.__dict__:
obj = self.__dict__.get(key)
if obj and type(obj) is ClassPropertyDescriptor:
return obj.__set__(self, value)
return super(ClassPropertyMetaClass, self).__setattr__(key, value)
# and update class define:
# class Bar(object):
# __metaclass__ = ClassPropertyMetaClass
# _bar = 1
# and update ClassPropertyDescriptor.__set__
# def __set__(self, obj, value):
# if not self.fset:
# raise AttributeError("can't set attribute")
# if inspect.isclass(obj):
# type_ = obj
# obj = None
# else:
# type_ = type(obj)
# return self.fset.__get__(obj, type_)(value)
Now all will be fine.
If you define classproperty as follows, then your example works exactly as you requested.
class classproperty(object):
def __init__(self, f):
self.f = f
def __get__(self, obj, owner):
return self.f(owner)
The caveat is that you can't use this for writable properties. While e.I = 20 will raise an AttributeError, Example.I = 20 will overwrite the property object itself.
[answer written based on python 3.4; the metaclass syntax differs in 2 but I think the technique will still work]
You can do this with a metaclass...mostly. Dappawit's almost works, but I think it has a flaw:
class MetaFoo(type):
#property
def thingy(cls):
return cls._thingy
class Foo(object, metaclass=MetaFoo):
_thingy = 23
This gets you a classproperty on Foo, but there's a problem...
print("Foo.thingy is {}".format(Foo.thingy))
# Foo.thingy is 23
# Yay, the classmethod-property is working as intended!
foo = Foo()
if hasattr(foo, "thingy"):
print("Foo().thingy is {}".format(foo.thingy))
else:
print("Foo instance has no attribute 'thingy'")
# Foo instance has no attribute 'thingy'
# Wha....?
What the hell is going on here? Why can't I reach the class property from an instance?
I was beating my head on this for quite a while before finding what I believe is the answer. Python #properties are a subset of descriptors, and, from the descriptor documentation (emphasis mine):
The default behavior for attribute access is to get, set, or delete the
attribute from an object’s dictionary. For instance, a.x has a lookup chain
starting with a.__dict__['x'], then type(a).__dict__['x'], and continuing
through the base classes of type(a) excluding metaclasses.
So the method resolution order doesn't include our class properties (or anything else defined in the metaclass). It is possible to make a subclass of the built-in property decorator that behaves differently, but (citation needed) I've gotten the impression googling that the developers had a good reason (which I do not understand) for doing it that way.
That doesn't mean we're out of luck; we can access the properties on the class itself just fine...and we can get the class from type(self) within the instance, which we can use to make #property dispatchers:
class Foo(object, metaclass=MetaFoo):
_thingy = 23
#property
def thingy(self):
return type(self).thingy
Now Foo().thingy works as intended for both the class and the instances! It will also continue to do the right thing if a derived class replaces its underlying _thingy (which is the use case that got me on this hunt originally).
This isn't 100% satisfying to me -- having to do setup in both the metaclass and object class feels like it violates the DRY principle. But the latter is just a one-line dispatcher; I'm mostly okay with it existing, and you could probably compact it down to a lambda or something if you really wanted.
If you use Django, it has a built in #classproperty decorator.
from django.utils.decorators import classproperty
For Django 4, use:
from django.utils.functional import classproperty
I think you may be able to do this with the metaclass. Since the metaclass can be like a class for the class (if that makes sense). I know you can assign a __call__() method to the metaclass to override calling the class, MyClass(). I wonder if using the property decorator on the metaclass operates similarly.
Wow, it works:
class MetaClass(type):
def getfoo(self):
return self._foo
foo = property(getfoo)
#property
def bar(self):
return self._bar
class MyClass(object):
__metaclass__ = MetaClass
_foo = 'abc'
_bar = 'def'
print MyClass.foo
print MyClass.bar
Note: This is in Python 2.7. Python 3+ uses a different technique to declare a metaclass. Use: class MyClass(metaclass=MetaClass):, remove __metaclass__, and the rest is the same.
As far as I can tell, there is no way to write a setter for a class property without creating a new metaclass.
I have found that the following method works. Define a metaclass with all of the class properties and setters you want. IE, I wanted a class with a title property with a setter. Here's what I wrote:
class TitleMeta(type):
#property
def title(self):
return getattr(self, '_title', 'Default Title')
#title.setter
def title(self, title):
self._title = title
# Do whatever else you want when the title is set...
Now make the actual class you want as normal, except have it use the metaclass you created above.
# Python 2 style:
class ClassWithTitle(object):
__metaclass__ = TitleMeta
# The rest of your class definition...
# Python 3 style:
class ClassWithTitle(object, metaclass = TitleMeta):
# Your class definition...
It's a bit weird to define this metaclass as we did above if we'll only ever use it on the single class. In that case, if you're using the Python 2 style, you can actually define the metaclass inside the class body. That way it's not defined in the module scope.
def _create_type(meta, name, attrs):
type_name = f'{name}Type'
type_attrs = {}
for k, v in attrs.items():
if type(v) is _ClassPropertyDescriptor:
type_attrs[k] = v
return type(type_name, (meta,), type_attrs)
class ClassPropertyType(type):
def __new__(meta, name, bases, attrs):
Type = _create_type(meta, name, attrs)
cls = super().__new__(meta, name, bases, attrs)
cls.__class__ = Type
return cls
class _ClassPropertyDescriptor(object):
def __init__(self, fget, fset=None):
self.fget = fget
self.fset = fset
def __get__(self, obj, owner):
if self in obj.__dict__.values():
return self.fget(obj)
return self.fget(owner)
def __set__(self, obj, value):
if not self.fset:
raise AttributeError("can't set attribute")
return self.fset(obj, value)
def setter(self, func):
self.fset = func
return self
def classproperty(func):
return _ClassPropertyDescriptor(func)
class Bar(metaclass=ClassPropertyType):
__bar = 1
#classproperty
def bar(cls):
return cls.__bar
#bar.setter
def bar(cls, value):
cls.__bar = value
bar = Bar()
assert Bar.bar==1
Bar.bar=2
assert bar.bar==2
nbar = Bar()
assert nbar.bar==2
I happened to come up with a solution very similar to #Andrew, only DRY
class MetaFoo(type):
def __new__(mc1, name, bases, nmspc):
nmspc.update({'thingy': MetaFoo.thingy})
return super(MetaFoo, mc1).__new__(mc1, name, bases, nmspc)
#property
def thingy(cls):
if not inspect.isclass(cls):
cls = type(cls)
return cls._thingy
#thingy.setter
def thingy(cls, value):
if not inspect.isclass(cls):
cls = type(cls)
cls._thingy = value
class Foo(metaclass=MetaFoo):
_thingy = 23
class Bar(Foo)
_thingy = 12
This has the best of all answers:
The "metaproperty" is added to the class, so that it will still be a property of the instance
Don't need to redefine thingy in any of the classes
The property works as a "class property" in for both instance and class
You have the flexibility to customize how _thingy is inherited
In my case, I actually customized _thingy to be different for every child, without defining it in each class (and without a default value) by:
def __new__(mc1, name, bases, nmspc):
nmspc.update({'thingy': MetaFoo.services, '_thingy': None})
return super(MetaFoo, mc1).__new__(mc1, name, bases, nmspc)
If you only need lazy loading, then you could just have a class initialisation method.
EXAMPLE_SET = False
class Example(object):
#classmethod
def initclass(cls):
global EXAMPLE_SET
if EXAMPLE_SET: return
cls.the_I = 'ok'
EXAMPLE_SET = True
def __init__( self ):
Example.initclass()
self.an_i = 20
try:
print Example.the_I
except AttributeError:
print 'ok class not "loaded"'
foo = Example()
print foo.the_I
print Example.the_I
But the metaclass approach seems cleaner, and with more predictable behavior.
Perhaps what you're looking for is the Singleton design pattern. There's a nice SO QA about implementing shared state in Python.

Method accessible only from class descendants in python

Let's say I have the following two classes
class A:
def own_method(self):
pass
def descendant_method(self):
pass
class B(A):
pass
and I want descendant_method to be callable from instances of B, but not of A, and own_method to be callable from everywhere.
I can think of several solutions, all unsatisfactory:
Check some field and manually raise NotImplementedError:
class A:
def __init__(self):
self.some_field = None
def own_method(self):
pass
def descendant_method(self):
if self.some_field is None:
raise NotImplementedError
class B(A):
def __init__(self):
super(B, self).__init__()
self.some_field = 'B'
pass
But this is modifying the method's runtime behaviour, which I don't want to do
Use a mixin:
class A:
def own_method(self):
pass
class AA:
def descendant_method(self):
pass
class B(AA, A):
pass
This is nice as long as descendant_method doesn't use much from A, or else we'll have to inherit AA(A) and this defies the whole point
make method private in A and redefine it in a metaclass:
class A:
def own_method(self):
pass
def __descendant_method(self):
pass
class AMeta(type):
def __new__(mcs, name, parents, dct):
par = parents[0]
desc_method_private_name = '_{}__descendant_method'.format(par.__name__)
if desc_method_private_name in par.__dict__:
dct['descendant_method'] = par.__dict__[desc_method_private_name]
return super(AMeta, mcs).__new__(mcs, name, parents, dct)
class B(A, metaclass=AMeta):
def __init__(self):
super(B, self).__init__()
This works, but obviously looks dirty, just like writing self.descendant_method = self._A__descendant_method in B itself.
What would be the right "pythonic" way of achieving this behaviour?
UPD: putting the method directly in B would work, of course, but I expect that A will have many descendants that will use this method and do not want to define it in every subclass.
What is so bad about making AA inherit from A? It's basically an abstract base class that adds additional functionality that isn't meant to be available in A. If you really don't want AA to ever be instantiated then the pythonic answer is not to worry about it, and just document that the user isn't meant to do that. Though if you're really insistent you can define __new__ to throw an error if the user tries to instantiate AA.
class A:
def f(self):
pass
class AA(A):
def g(self):
pass
def __new__(cls, *args, **kwargs):
if cls is AA:
raise TypeError("AA is not meant to be instansiated")
return super().__new__(cls)
class B(AA):
pass
Another alternative might be to make AA an Abstract Base Class. For this to work you will need to define at least one method as being abstract -- __init__ could do if there are no other methods you want to say are abstract.
from abc import ABCMeta, abstractmethod
class A:
def __init__(self, val):
self.val = val
def f(self):
pass
class AA(A, metaclass=ABCMeta):
#abstractmethod
def __init__(self, val):
super().__init__(val)
def g(self):
pass
class B(AA):
def __init__(self, val):
super().__init__(val)
Very finally, what's so bad about having the descendant method available on A, but just not using it. You are writing the code for A, so just don't use the method... You could even document the method that it isn't meant to be used directly by A, but is rather meant to be available to child classes. That way future developers will know your intentions.
As far as I can tell, this may be the most Pythonic way of accomplishing what you want:
class A:
def own_method(self):
pass
def descendant_method(self):
raise NotImplementedError
class B(A):
def descendant_method(self):
...
Another option could be the following:
class A:
def own_method(self):
pass
def _descendant_method(self):
pass
class B(A):
def descendant_method(self):
return self._descendant_method(self)
They're both Pythonic because it's explicit, readable, clear and concise.
It's explicit because it's not doing any unnecessary magic.
It's readable because
one can tell precisely what your doing, and what your intention was
at first glance.
It's clear because the leading single underscore is
a widely used convention in the Python community for private
(non-magic) methods—any developer that uses it should know to tread
with caution.
Choosing between one of these approaches will depend on how you intend on your use case. A more concrete example in your question would be helpful.
Try to check the class name using __class__.__name__ .
class A(object):
def descendant_method(self):
if self.__class__.__name__ == A.__name__:
raise NotImplementedError
print 'From descendant'
class B(A):
pass
b = B()
b.descendant_method()
a = A()
a.descendant_method()

python: defining registry in base class

I'm implementing enumeration using a base class that defines a variety of methods. The actual enumerations are subclasses of that, with no additional methods or attributes. (Each subclass is populated with its own values using the constructor defined in the base class).
I use a registry (a class attribute that stores all the instances of that class). Ideally, I'd like to avoid defining it in each subclass. Unfortunately, if I define it in the base class, all the subclasses will end up sharing the same registry.
What's a good approach here?
Below is the implementation in case it helps (it's based on #jchl comment in python enumeration class for ORM purposes).
class IterRegistry(type):
def __iter__(cls):
return iter(cls._registry.values())
class EnumType(metaclass = IterRegistry):
_registry = {}
_frozen = False
def __init__(self, token):
if hasattr(self, 'token'):
return
self.token = token
self.id = len(type(self)._registry)
type(self)._registry[token] = self
def __new__(cls, token):
if token in cls._registry:
return cls._registry[token]
else:
if cls._frozen:
raise TypeError('No more instances allowed')
else:
return object.__new__(cls)
#classmethod
def freeze(cls):
cls._frozen = True
def __repr__(self):
return self.token
#classmethod
def instance(cls, token):
return cls._registry[token]
class Enum1(EnumType): pass
Enum1('a')
Enum1('b')
for i in Enum1:
print(i)
# not going to work properly because _registry is shared
class Enum2(EnumType): pass
As you already have a metaclass you might as well use it to put a add a separate _registry attribute to each subclass automatically.
class IterRegistry(type):
def __new__(cls, name, bases, attr):
attr['_registry'] = {} # now every class has it's own _registry
return type.__new__(cls, name, bases, attr)
Marty Alchin has a very nice pattern for this: see his blog entry.
What if you share the same registry, but with sub-registries per class, i.e.
if cls.__name__ not in self._registry:
self._registry[cls.__name__] = {}
self._registry[cls.__name__][token] = cls
You actually don't even need cls.__name__, you should be able to use cls itself as key.

Categories

Resources