views:

192

answers:

3

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?

+1  A: 

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.

katrielalex
But what should I do with an ABC? An ABC would lead to the same class as shown above.
deamon
@deamon: Yes, but it's *abstract*. Subclasses are required to override the abstract properties and methods.
katrielalex
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
@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/.
katrielalex
Interesting [discussion of the borg pattern](http://tarekziade.wordpress.com/2009/01/22/singletons-and-borg-are-unpythonic-well-imvho/).
deamon
@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.
katrielalex
+1  A: 

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
-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?
katrielalex
@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
@aaron: now it works, but it's not 'right' -- monkeypatching classes to add crucial state is not exactly good design. Prepare to be assimilated.
katrielalex
@katrielalex See my new idea ;) It's much more correct.
aaronasterling
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
@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.
katrielalex
@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
@aaron: true that =). Oh, I think it should be a TypeError, at least based on how `abc` does it.
katrielalex
+4  A: 

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.

Muhammad Alkarouri