12

I have been reading documentation describing class inheritance, abstract base classes and even python interfaces. But nothing seams to be exactly what I want. Namely, a simple way of building virtual classes. When the virtual class gets called, I would like it to instantiate some more specific class based on what the parameters it is given and hand that back the calling function. For now I have a summary way of rerouting calls to the virtual class down to the underlying class.

The idea is the following:

class Shape:
    def __init__(self, description):
        if   description == "It's flat":  self.underlying_class = Line(description)
        elif description == "It's spiky": self.underlying_class = Triangle(description)
        elif description == "It's big":   self.underlying_class = Rectangle(description)
    def number_of_edges(self, parameters):
        return self.underlying_class(parameters)

class Line:
    def __init__(self, description):
        self.desc = description
    def number_of_edges(self, parameters):
        return 1

class Triangle:
    def __init__(self, description):
        self.desc = description
    def number_of_edges(self, parameters):
        return 3

class Rectangle:
    def __init__(self, description):
        self.desc = description
    def number_of_edges(self, parameters):
        return 4

shape_dont_know_what_it_is = Shape("It's big")
shape_dont_know_what_it_is.number_of_edges(parameters)

My rerouting is far from optimal, as only calls to the number_of_edges() function get passed on. Adding something like this to Shape doesn't seam to do the trick either:

def __getattr__(self, *args):
    return underlying_class.__getattr__(*args)

What I am doing wrong ? Is the whole idea badly implemented ? Any help greatly appreciated.

martineau
  • 119,623
  • 25
  • 170
  • 301
xApple
  • 6,150
  • 9
  • 48
  • 49
  • `__getattr__` works only for new-style classes. This means your classes have to be subclasses of `object`. – Georg Schölly Jun 19 '10 at 18:07
  • What you're trying to do is also referred to as a class having virtual constructor (rather than "virtual classes"). See related question [_What exactly is a Class Factory?_](http://stackoverflow.com/questions/2526879/what-exactly-is-a-class-factory) – martineau Dec 02 '16 at 03:03

4 Answers4

18

I agree with TooAngel, but I'd use the __new__ method.

class Shape(object):
    def __new__(cls, *args, **kwargs):
        if cls is Shape:                            # <-- required because Line's
            description, args = args[0], args[1:]   #     __new__ method is the
            if description == "It's flat":          #     same as Shape's
                new_cls = Line
            else:
                raise ValueError("Invalid description: {}.".format(description))
        else:
            new_cls = cls
        return super(Shape, cls).__new__(new_cls, *args, **kwargs)

    def number_of_edges(self):
        return "A shape can have many edges…"

class Line(Shape):
    def number_of_edges(self):
        return 1

class SomeShape(Shape):
    pass

>>> l1 = Shape("It's flat")
>>> l1.number_of_edges()
1
>>> l2 = Line()
>>> l2.number_of_edges()
1
>>> u = SomeShape()
>>> u.number_of_edges()
'A shape can have many edges…'
>>> s = Shape("Hexagon")
ValueError: Invalid description: Hexagon.
Community
  • 1
  • 1
Georg Schölly
  • 124,188
  • 49
  • 220
  • 267
  • 1
    nice - I also thought about something like that, but didn't know the python syntax for it – TooAngel Jun 19 '10 at 18:28
  • 3
    @Daniel: I don't think so. Metaclasses usually change the way a class works. In Objective-C this would be called a *Class Cluster*. I'm not sure what the proper name for it is in Python. – Georg Schölly Jun 19 '10 at 19:22
  • 1
    I implemented this solution and discovered that is suffers from a flaw: the __init__ method of the class Shape will be called twice when creating an instance. – xApple May 10 '11 at 14:30
  • I can confirm this solution is not appropriate. To fix it see http://stackoverflow.com/questions/5953759. – xApple May 12 '11 at 15:01
16

I would prefer doing it with a factory:

def factory(description):
    if   description == "It's flat":  return Line(description)
    elif description == "It's spiky": return Triangle(description)
    elif description == "It's big":   return Rectangle(description)

or:

def factory(description):
    classDict = {"It's flat":Line("It's flat"), "It's spiky":Triangle("It's spiky"), "It's big":Rectangle("It's big")}
    return classDict[description]

and inherit the classes from Shape

class Line(Shape):
    def __init__(self, description):
        self.desc = description
    def number_of_edges(self, parameters):
        return 1
Trang Oul
  • 134
  • 1
  • 8
TooAngel
  • 873
  • 6
  • 13
  • 8
    Your second factory function constructs an object of every type every time it's called, even though you only return one of them. Better to store classes in the dictionary than objects: `class_dict = {"It's flat": Line, ...}` then `return class_dict[description](description)`. – Jim Oldfield Sep 21 '17 at 10:45
1

Python doesn't have virtual classes out of the box. You will have to implement them yourself (it should be possible, Python's reflection capabilities should be powerful enough to let you do this).

However, if you need virtual classes, then why don't you just use a programming language which does have virtual classes like Beta, gBeta or Newspeak? (BTW: are there any others?)

In this particular case, though, I don't really see how virtual classes would simplify your solution, at least not in the example you have given. Maybe you could elaborate why you think you need virtual classes?

Don't get me wrong: I like virtual classes, but the fact that only three languages have ever implemented them, only one of those three is still alive and exactly 0 of those three are actually used by anybody is somewhat telling …

Jörg W Mittag
  • 363,080
  • 75
  • 446
  • 653
  • Interesting! I didn't know that. Do you have a link where I can read up on virtual classes in C++? According to Wikipedia, in C++, nested classes are static members of the enclosing class. In other words: they aren't actually *true* nested classes (which are members of an enclosing *instance* of the enclosing class). And considering that virtual classes are a special case of true nested classes (namely true nested classes which be overridden in a subclass just like any other virtual member), this seems to preclude C++ from having virtual classes. – Jörg W Mittag Feb 21 '16 at 11:07
  • Mmm, AFAIK C++ sort of *defines* what virtual classes are, because the language is so popular and make such heavy use of them. A [virtual/abstract class](https://en.m.wikipedia.org/wiki/Virtual_method_table#Example) cannot be instantiated, only inherited, so in a way it's more like a template for other classes to feed off of. – user1717828 Feb 21 '16 at 12:41
  • I'm not sure what you mean. The term *virtual class* is well-defined and has a precise meaning that is unrelated to C++ or any other language in particular. What you describe has absolutely nothing to do with virtual classes. What you describe is an *abstract base class*. Google has exactly 0 hits for virtual classes in C++. There are virtual *base* classes in C++, which are related to virtual *inheritance*, but none of them are related to virtual *classes*. Like I said: a virtual class is a class that is a member of an instance and can be overridden in a subclass, just like a virtual method … – Jörg W Mittag Feb 22 '16 at 23:48
  • … is a member of an instance and can be overridden in a subclass. In fact, in a language like Python, where classes are first-class objects, you can implement virtual classes by using methods which return classes. I believe you when you say that C++ has virtual classes, I don't know enough about C++ to judge that, but neither your example nor your link show virtual classes in C++, there are no Google hits about virtual classes in C++, and the Wikipedia article on C++ says that nested classes are static members, which conflicts with virtual classes where nested classes are non-static members. – Jörg W Mittag Feb 22 '16 at 23:49
  • It appears you're right; I never knew I was mixing up those two concepts. Thanks for the catch. – user1717828 Feb 23 '16 at 11:08
0

You can change the class with object.__class__, but it's much better to just make a function that returns an instance of an arbitrary class.

On another note, all class should inherit from object unless you use using Python 3, like this, otherwise you end up with an old-style class:

class A(object):
    pass