Python支持类似字面量对象的东西吗?

11 投票
3 回答
2866 浏览
提问于 2025-04-16 03:00

在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中该怎么做呢?

3 个回答

1

如果你可以直接创建一个 Resource 对象,那就直接这么做,把路径和 get 方法直接加上就行了。

from types import MethodType

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

不过,这里假设 pathget 这两个东西在 Resource__init__ 方法里没有用到,而且路径也不应该被任何类的方法使用,这样才符合你的担忧。

如果你最关心的是确保没有东西会从 Book 这个非类继承,那你可以直接使用这个元类。

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 的建议。

2

使用一个abc(抽象基类):

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

这样,任何从Resource继承的东西都必须实现path。注意,path实际上是在这个抽象基类中实现的;你可以通过super来访问这个实现。

7

使用装饰器在创建时将继承的类转换为对象

我觉得这种对象的概念在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.__class__ 来获取对象K的类,因此如果有人真的想的话,还是可以初始化其他对象。在Python中,如果你真的想,总是能找到解决办法。

撰写回答