如何在Python中实现Obj-C类别?
Obj-C(我已经很久没用过了)有一种叫做类别的东西,可以用来扩展类。你只需要声明一个类别,添加一些新方法,然后把它编译进你的程序中,这样这个类的所有实例就都能使用这些新方法了。
Python也有类似的功能,叫做mixin,我也用过,但mixin必须在程序的底部使用:也就是说,类自己得声明它。
想象一下类别的使用场景:假设你有一个很大的类层次结构,用来描述与数据交互的不同方式,声明了多态的方法来获取不同的属性。现在,类别可以帮助使用这些描述类的人,通过实现一个方便的接口,让他们在一个地方访问这些方法。(比如,一个类别方法可以尝试两种不同的方法,然后返回第一个定义的(非空)返回值。)
在Python中有没有办法做到这一点呢?
示例代码
我希望这能让你明白我的意思。关键是,类别就像一个聚合接口,AppObj的使用者可以在他们自己的代码中进行更改。
class AppObj (object):
"""This is the top of a big hierarchy of subclasses that describe different data"""
def get_resource_name(self):
pass
def get_resource_location(self):
pass
# dreaming up class decorator syntax
@category(AppObj)
class AppObjCategory (object):
"""this is a category on AppObj, not a subclass"""
def get_resource(self):
name = self.get_resource_name()
if name:
return library.load_resource_name(name)
else:
return library.load_resource(self.get_resource_location())
3 个回答
2
Python中的setattr
函数让这个事情变得简单。
# categories.py
class category(object):
def __init__(self, mainModule, override = True):
self.mainModule = mainModule
self.override = override
def __call__(self, function):
if self.override or function.__name__ not in dir(self.mainModule):
setattr(self.mainModule, function.__name__, function)
# categories_test.py
import this
from categories import category
@category(this)
def all():
print "all things are this"
this.all()
>>> all things are this
9
为什么不直接动态添加方法呢?
>>> class Foo(object):
>>> pass
>>> def newmethod(instance):
>>> print 'Called:', instance
...
>>> Foo.newmethod = newmethod
>>> f = Foo()
>>> f.newmethod()
Called: <__main__.Foo object at 0xb7c54e0c>
我知道Objective-C,这看起来就像是类别(categories)。唯一的缺点是你不能对内置类型或扩展类型这样做。
4
我想出了一个类装饰器的实现方法。我使用的是Python 2.5,所以我实际上没有用装饰器的语法来测试它(如果能用那样的语法就好了),而且我也不太确定这样做是否真的正确。不过,它的样子是这样的:
pycategories.py
"""
This module implements Obj-C-style categories for classes for Python
Copyright 2009 Ulrik Sverdrup <ulrik.sverdrup@gmail.com>
License: Public domain
"""
def Category(toclass, clobber=False):
"""Return a class decorator that implements the decorated class'
methods as a Category on the class @toclass
if @clobber is not allowed, AttributeError will be raised when
the decorated class already contains the same attribute.
"""
def decorator(cls):
skip = set(("__dict__", "__module__", "__weakref__", "__doc__"))
for attr in cls.__dict__:
if attr in toclass.__dict__:
if attr in skip:
continue
if not clobber:
raise AttributeError("Category cannot override %s" % attr)
setattr(toclass, attr, cls.__dict__[attr])
return cls
return decorator