11

In Scala I could define an abstract class and implement it with an object:

abstrac class Base {
    def doSomething(x: Int): Int
}

object MySingletonAndLiteralObject extends Base {
    override def doSomething(x: Int) = x*x
}

My concrete example in Python:

class Book(Resource):

    path = "/book/{id}"

    def get(request):
        return aBook

Inheritance wouldn't make sense here, since no two classes could have the same path. And only one instance is needed, so that the class doesn't act as a blueprint for objects. With other words: no class is needed here for a Resource (Book in my example), but a base class is needed to provide common functionality.

I'd like to have:

object Book(Resource):

    path = "/book/{id}"

    def get(request):
        return aBook

What would be the Python 3 way to do it?

deamon
  • 89,107
  • 111
  • 320
  • 448
  • 2
    Why is path a class variable and not an instance variable? – S.Lott Aug 19 '10 at 10:17
  • `path` is a class variable, because the path is valid for the whole class and not limited to a specific instance. But since there is only one instance it doesn't matter anyway. – deamon Aug 19 '10 at 10:23
  • @deamon: actually, I think @S.Lott has a point. Why can't you make a generic `Book` class, instantiate it once, and use that instance as your book? – Katriel Aug 19 '10 at 11:01
  • @katrielalex: I could do so, but I'd like to rule out the possibility that derived classes had the same `path`. For this purpose it doesn't matter whether it is a class or an instance variable. – deamon Aug 19 '10 at 11:16
  • 2
    @deamon: Yes, it does! If you instantiate a `Book( )` then you get a `Book` with that path. If you make a `PaperbackBook( Book )`, it wouldn't automatically get the *instance variable* of your other `Book`. – Katriel Aug 19 '10 at 11:29
  • @katrielalex ... but in my concrete scenario the initialisation would be performed in the `__init__` method, so that subclasses would inherit the instance variable when they call `super()`. But when I change my application design a bit, I could go with instance variables. – deamon Aug 19 '10 at 13:09

3 Answers3

7

Use a decorator to convert the inherited class to an object at creation time

I believe that the concept of such an object is not a typical way of coding in Python, but if you must then the decorator class_to_object below for immediate initialisation will do the trick. Note that any parameters for object initialisation must be passed through the decorator:

def class_to_object(*args):
    def c2obj(cls):
        return cls(*args)
    return c2obj

using this decorator we get

>>> @class_to_object(42)
... class K(object):
...     def __init__(self, value):
...         self.value = value
... 
>>> K
<__main__.K object at 0x38f510>
>>> K.value
42

The end result is that you have an object K similar to your scala object, and there is no class in the namespace to initialise other objects from.

Note: To be pedantic, the class of the object K can be retrieved as K.__class__ and hence other objects may be initialised if somebody really want to. In Python there is almost always a way around things if you really want.

Community
  • 1
  • 1
Muhammad Alkarouri
  • 23,884
  • 19
  • 66
  • 101
2

Use an abc (Abstract Base Class):

import abc
class Resource( metaclass=abc.ABCMeta ):
    @abc.abstractproperty
    def path( self ):
        ...
        return p

Then anything inheriting from Resource is required to implement path. Notice that path is actually implemented in the ABC; you can access this implementation with super.

Katriel
  • 120,462
  • 19
  • 136
  • 170
  • But what should I do with an ABC? An ABC would lead to the same class as shown above. – deamon Aug 19 '10 at 08:51
  • @deamon: Yes, but it's *abstract*. Subclasses are required to override the abstract properties and methods. – Katriel Aug 19 '10 at 08:55
  • It would be better than a concrete superclass - I agree so far. But the subclass is still a class from which other classes could inherit (the `path`). – deamon Aug 19 '10 at 09:20
  • @deamon: Yes, that is correct. Subclassing `Book` is not an inherently bad idea -- what if you wanted a `PaperbackBook`? There is a very elegant way to do this in Python, which is the Borg pattern: http://code.activestate.com/recipes/66531-singleton-we-dont-need-no-stinkin-singleton-the-bo/. – Katriel Aug 19 '10 at 09:42
  • Interesting [discussion of the borg pattern](http://tarekziade.wordpress.com/2009/01/22/singletons-and-borg-are-unpythonic-well-imvho/). – deamon Aug 19 '10 at 10:19
  • @deamon: yes, although it is also supported by the great Alex Martelli so it must be getting something right! See @aaronasterling's answer for a way to prevent inheritance, if you are *sure* that you want to do that. – Katriel Aug 19 '10 at 10:25
1

If you can instantiate Resource directly you just do that and stick the path and get method on directly.

from types import MethodType

book = Resource()
def get(self):
    return aBook
book.get = MethodType(get, book)
book.path = path

This assumes though that path and get are not used in the __init__ method of Resource and that path is not used by any class methods which it shouldn't be given your concerns.

If your primary concern is making sure that nothing inherits from the Book non-class, then you could just use this metaclass

class Terminal(type):
    classes = []
    def __new__(meta, classname, bases, classdict):
        if [cls for cls in meta.classes if cls in bases]:
            raise TypeError("Can't Touch This")
        cls = super(Terminal, meta).__new__(meta, classname, bases, classdict)
        meta.classes.append(cls)
        return cls

class Book(object):
    __metaclass__ = Terminal

class PaperBackBook(Book):
    pass

You might want to replace the exception thrown with something more appropriate. This would really only make sense if you find yourself instantiating a lot of one offs.

And if that's not good enough for you and you're using CPython, you could always try some of this hackery:

class Resource(object):
    def __init__(self, value, location=1):
        self.value = value
        self.location = location

with Object('book', Resource, 1, location=2):
    path = '/books/{id}'
    def get(self):
        aBook = 'abook' 
        return aBook

print book.path
print book.get()

made possible by my very first context manager.

class Object(object):
    def __init__(self, name, cls, *args, **kwargs):
        self.cls = cls
        self.name = name
        self.args = args
        self.kwargs = kwargs

    def __enter__(self):
        self.f_locals = copy.copy(sys._getframe(1).f_locals)

    def __exit__(self, exc_type, exc_val, exc_tb):
        class cls(self.cls):
            pass
        f_locals = sys._getframe(1).f_locals
        new_items = [item for item in f_locals if item not in self.f_locals]
        for item in new_items:
            setattr(cls, item, f_locals[item])
            del f_locals[item] # Keyser Soze the new names from the enclosing namespace
        obj = cls(*self.args, **self.kwargs)
        f_locals[self.name] = obj # and insert the new object      

Of course I encourage you to use one of my above two solutions or Katrielalex's suggestion of ABC's.

aaronasterling
  • 68,820
  • 20
  • 127
  • 125
  • -1 That won't work, because `get` here is a function instead of a bound method (are they called that in Py3k? something like that...). If you really want to do this sort of thing, you need to instantiate one of the types in `types`. But that doesn't solve the problem anyway, because what is `Resource().path` meant to be? – Katriel Aug 19 '10 at 09:13
  • @katrielalex Right. my original idea works with classes. My edits fix it though. `book.path` will be available as `self.path` for all instance methods. – aaronasterling Aug 19 '10 at 09:27
  • @aaron: now it works, but it's not 'right' -- monkeypatching classes to add crucial state is not exactly good design. Prepare to be assimilated. – Katriel Aug 19 '10 at 09:46
  • also, i'm not sure that it's bad design to monkypatch a `book` psuedo-class if it's _part of the design_ that there is only ever one. It's actually considered to be fairly pythonic. – aaronasterling Aug 19 '10 at 09:55
  • @aaron: heh, cute! It does solve the OP's problem now. I'm not sure I like the idea of preventing somebody subclassing `Book` -- what if you want `PaperbackBook`s? -- but that's more a matter of taste than correctness I think. – Katriel Aug 19 '10 at 10:05
  • @Katrielalex I personally agree. It's not my job to control what people do with my classes, It's my job to not get in their way. It's what the OP wants though. – aaronasterling Aug 19 '10 at 10:11
  • @aaron: true that =). Oh, I think it should be a TypeError, at least based on how `abc` does it. – Katriel Aug 19 '10 at 10:23