Поддерживает ли 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.