Поддерживает ли Python что-то вроде буквальных объектов?

В Scala я мог бы определить абстрактный класс и реализовать его с помощью объекта:

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

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

Мой конкретный пример в Python:

class Book(Resource):

    path = "/book/{id}"

    def get(request):
        return aBook

Наследование здесь не имеет смысла, поскольку ни один из двух классов не может иметь одинаковый path. И нужен только один экземпляр, так что класс не действует как объект для объектов. Другими словами: здесь нет класса для Resource (Book в моем примере), но базовый класс необходим для обеспечения общей функциональности.

Я бы хотел:

object Book(Resource):

    path = "/book/{id}"

    def get(request):
        return aBook

Каким будет способ Python 3?

Ответ 1

Используйте декоратор для преобразования унаследованного класса в объект во время создания

Я считаю, что концепция такого объекта не является типичным способом кодирования в Python, но если вы должны тогда сделать декоратор class_to_object ниже для немедленной инициализации, это сделает трюк. Обратите внимание, что любые параметры инициализации объекта должны быть переданы через декоратор:

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

используя этот декоратор, получим

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

Конечным результатом является то, что у вас есть объект K, похожий на ваш объект scala, и в пространстве имен нет класса для инициализации других объектов.

Примечание. Чтобы быть педантичным, класс объекта K можно получить как K.__class__, и, следовательно, другие объекты могут быть инициализированы, если кто-то действительно захочет. В Python почти всегда есть путь, если вы действительно хотите.

Ответ 2

Используйте abc (абстрактный базовый класс):

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

Тогда для реализации path требуется любое наследование от Resource. Обратите внимание, что path фактически реализовано в ABC; вы можете получить доступ к этой реализации с помощью super.

Ответ 3

Если вы можете создать экземпляр Resource напрямую, вы просто сделаете это и примените метод path и get непосредственно.

from types import MethodType

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

Это предполагает, что path и get не используются в методе __init__ Resource, и этот путь не используется никакими методами класса, в которых ему не должны быть заданы ваши проблемы.

Если ваша главная задача - убедиться, что ничего не наследует от Book non-class, тогда вы можете просто использовать этот метакласс

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

Возможно, вы захотите заменить исключение, добавленное чем-то более подходящим. Это действительно имело бы смысл, если бы вы обнаружили, что вы создаете множество отступов.

И если это недостаточно для вас, и вы используете CPython, вы всегда можете попробовать некоторые из этих хакеров:

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()

сделал возможным мой самый первый менеджер контекста.

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      

Конечно, я рекомендую вам использовать одно из моих двух вышеупомянутых решений или предложение Katrielalex для ABC.