0

Preamble: I have objects, some of them could be created by default constructor and left without modifications, so such objects could be considered as "empty". Sometimes I need to verify whether some object is "empty" or not. It could be done in the following way (majik methods are implemented in the base class Animal):

>>> a = Bird()
>>> b = Bird()
>>> a == b
True
>>> a == Bird()
True

So the question: is it possible (and if yes then how) to achieve such syntax:

>>> a == Bird.default
True

At least this one (but the previous is sweeter):

>>> a == a.default
True

But: with implementation of default in the base class Animal (to not repeat it in all derived classes):

class Animal(object):
   ... tech stuff ...
        - obj comparison
        - obj representation
        - etc

class Bird(Animal):
   ... all about birds ...

class Fish(Animal):
   ... all about fishes ...

Of course I don't need solutions to have Bird() calling in Animal class :)

I'd like to have a kind of templating implemented in base class which will stamp out derived class default instance and store its unique copy in the derived class or instance property. I think it could be achieved by playing with metaclasses or so, but don't know how.

Class default instance could be considered as any object instantiated by __init__() of its class (without further object modification of course).

 

UPDATE

The system is flooded with objects and I just want to have a possibility to separate circulating of freshly (by default) created objects (which are useless to display for example) from already somehow modified one. I do it by:

if a == Bird():
    . . .

I don't want creation of new object for comparison, intuitevly, I'd like to have one instance copy as etalon for the instances of this class to compare with. Objects are JSON-like and contain only properties (besides implicit __str__, __call__, __eq__ methods), so I'd like to keep such style of using built-in Python features and avoid the using explicitly defined methods like is_empty() for example. It's like entering an object in the interactive shell and it prints it out calling __str__, it is implicit, but fun.

rook
  • 5,880
  • 4
  • 39
  • 51

2 Answers2

1

To achieve the first solution you should use a metaclass.

For example:

def add_default_meta(name, bases, attrs):
    cls = type(name, bases, attrs)
    cls.default = cls()
    return cls

And use it as(assuming python3. In python2 set the __metaclass__ attribute in the class body):

class Animal(object, metaclass=add_default_meta):
    # stuff

class NameClass(Animal, metaclass=add_default_meta):
    # stuff

Note that you have repeat the metaclass=... for every subclass of Animal.

If instead of a function you use a class and its __new__ method to implement the metaclass, it can be inherited, i.e:

class AddDefaultMeta(type):
    def __new__(cls, name, bases, attrs):
        cls = super(AddDefaultMeta, cls).__new__(cls, name, bases, attrs)
        cls.default = cls()
        return cls

A different way to achieve the same effect is to use a class decorator:

def add_default(cls):
    cls.default = cls()
    return cls


@add_default
class Bird(Animal):
   # stuff

Again, you must use the decorator for every subclass.

If you want to achieve the second solution, i.e. to check a == a.default, then you can simply reimplement Animal.__new__:

class Animal(object):
    def __new__(cls, *args, **kwargs):
        if not (args or kwargs) and not hasattr(cls, 'default'):
            cls.default = object.__new__(cls)
            return cls.default
        else:
            return object.__new__(cls)

This will create the empty instance whenever the first instance of the class is created and it is stored in the default attribute.

This means that you can do both:

a == a.default

and

a == Bird.default

But accessing Bird.default gives AttributeError if you didn't create any Bird instance.


Style note: Bird.Default looks very bad to me. Default is an instance of Bird not a type, hence you should use lowercase_with_underscore according to PEP 8.

In fact the whole thing looks fishy for me. You could simply have an is_empty() method. It's pretty easy to implement:

class Animal(object):
    def __init__(self, *args, **kwargs):
        # might require more complex condition
        self._is_empty = not (bool(args) or bool(kwargs))
    def is_empty(self):
        return self._is_empty

Then when the subclasses create an empty instance that doesn't pass any arguments to the base class the _is_empty attribute will be True and hence the inherited method will return True accordingly, while in the other cases some argument would be passed to the base class which would set _is_empty to False.

You can play around with this in order to obtain a more robust condition that works better with your subclasses.

Community
  • 1
  • 1
Bakuriu
  • 98,325
  • 22
  • 197
  • 231
  • Thanks for the style hint. You soultion: at first glance it works, but it makes all instances the same... – rook Sep 04 '13 at 15:02
  • @rook Which solution? If you mean the `add_default_meta` as function then, as I wrote in the answer, you *must* specify the `metaclass=add_default_meta` when creating subclasses, otherwise the metaclass wont be called. Using the "class version" allows to avoid repeating this, since `Animal.__class__` will be the metaclass and not `type`. – Bakuriu Sep 04 '13 at 15:38
  • I tested the second solution with `__new__` method. I prefered that one, because all was packed down in my long-suffering base class :) [grc](http://stackoverflow.com/questions/18613496/create-current-class-default-instance-in-its-base-class/18614965#18614965) idea was good enough to avoid decorating in subclasses, but it makes all instances of the same type. – rook Sep 04 '13 at 16:04
  • @rook you mean the one that implements `Animal.__new__`? I just tested on my machine and it works properly: `Bird.default` is a `Bird` instance, `Dog.default` a `Dog` instance etc. also creating the instances passing arguments correctly creates the objects. (I made a small fix to make it work correctly in python3 where a `TypeError` was raised due to different semantics of `object.__new__`) – Bakuriu Sep 04 '13 at 16:13
  • yes, that part works perfectly, but now all instances are the same objects, i.e. just references to one object. – rook Sep 04 '13 at 16:28
  • @rook You mean all *empty* instances? From your question I thought that it didn't matter if the empty object was always the same instance. Anyway I'll fix it now. However in this case you *must* also implement `__eq__` in `Animal`, otherwise differnt "empty" instances will compare unequal. – Bakuriu Sep 04 '13 at 16:33
  • I already have `__eq__` in my `Animal`, my objects are fine comparable between each other. The issue with your solution that now all objects are the same instance, e.g. `a = Bird()` then `b = getBird('eagle')` makes `a.name` and `b.name` equal to `eagle`(but `a` is just created by `Bird()` and "empty"). – rook Sep 04 '13 at 16:47
1

Another possible metaclass:

class DefaultType(type):
    def __new__(cls, name, bases, attrs):
        new_cls = super(DefaultType, cls).__new__(cls, name, bases, attrs)
        new_cls.default = new_cls()
        return new_cls

You only need to set the metaclass attribute for the Animal class, as all derived classes will inherit it:

class Animal(object):
    __metaclass__ = DefaultType
    # ...

class Bird(Animal):
    # ...

This allows you to use both:

a == Bird.default

and:

a == a.default
grc
  • 22,885
  • 5
  • 42
  • 63