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中该怎么做呢?
3 个回答
如果你可以直接创建一个 Resource
对象,那就直接这么做,把路径和 get
方法直接加上就行了。
from types import MethodType
book = Resource()
def get(self):
return aBook
book.get = MethodType(get, book)
book.path = path
不过,这里假设 path
和 get
这两个东西在 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 的建议。
使用一个abc
(抽象基类):
import abc
class Resource( metaclass=abc.ABCMeta ):
@abc.abstractproperty
def path( self ):
...
return p
这样,任何从Resource
继承的东西都必须实现path
。注意,path
实际上是在这个抽象基类中实现的;你可以通过super
来访问这个实现。
使用装饰器在创建时将继承的类转换为对象
我觉得这种对象的概念在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中,如果你真的想,总是能找到解决办法。