在超类构造函数中为子类添加方法
我想要在Python的子类中自动添加一些方法(更具体来说,是方法别名)。如果子类定义了一个叫做'get'的方法,我想在这个子类的字典里添加一个别名'GET'。
为了避免重复,我想在基类中定义这个修改的过程。但是如果我在基类的__init__
方法中检查,就找不到这个方法,因为它是在子类中定义的。下面的代码会让这个问题更清楚:
class Base:
def __init__(self):
if hasattr(self, "get"):
setattr(self, "GET", self.get)
class Sub(Base):
def get():
pass
print(dir(Sub))
输出:
['__doc__', '__init__', '__module__', 'get']
它还应该包含'GET'
。
有没有办法在基类中做到这一点呢?
3 个回答
在你的子类中创建一个派生构造函数,用来设置这个属性。
这是因为类 Sub 还没有被实例化,你需要在它的实例中这样做:
>>> s=Sub()
>>> dir(s)
['GET', '__doc__', '__init__', '__module__', 'get']
>>>
你班级的 __init__
方法会把一个 绑定方法 作为属性添加到你班级的 实例 中。这和把属性添加到类本身并不完全一样。通常,方法是通过把函数存储在类里作为属性来工作的,然后当这些函数作为属性被从类中取出时,会创建 方法对象。如果是从类中取出,就会创建未绑定的方法(这些方法只知道它们属于哪个类);如果是从实例中取出,就会创建绑定的方法(这些方法知道它们属于哪个实例)。
那这和你做的有什么不同呢?其实,你是把 GET
这个 实例 属性赋值给一个 特定 的实例,而不是类。这个绑定方法就成了实例数据的一部分:
>>> s.__dict__
{'GET': <bound method Sub.get of <__main__.Sub object at 0xb70896cc>>}
注意这个方法是以 GET
为键存在的,但在 get
下却没有。GET
是一个实例属性,而 get
不是。这在很多方面是有细微差别的:这个方法并不存在于类对象中,所以你不能用 Sub.GET(instance)
来调用 Sub
的 GET
方法,尽管你可以用 Sub.get(instance)
。其次,如果你有一个 Sub
的子类,它定义了自己的 GET
方法 但没有定义自己的 get
方法,那么这个实例属性会 隐藏子类的 GET
方法,而显示基类的绑定 get
方法。第三,这会在绑定方法和实例之间创建一个循环引用:绑定方法有对实例的引用,而实例现在也存储了对绑定方法的引用。通常,绑定方法不会存储在实例上,部分原因就是为了避免这种情况。循环引用通常不是大问题,因为现在我们有循环垃圾回收模块(gc
)来处理它们,但它并不总能清理引用循环(例如,当你的类还有一个 __del__
方法时)。最后,存储绑定方法对象通常会使你的实例无法序列化:大多数序列化工具(比如 pickle
)无法处理绑定方法。
你可能对这些问题不在乎,但如果你在乎,有一种更好的方法来实现你想做的事情:元类。你可以在创建类的时候,把普通函数赋值给类属性,而不是在创建实例时把绑定方法赋值给实例属性:
class MethodAliasingType(type):
def __init__(self, name, bases, attrs):
# attrs is the dict of attributes that was used to create the
# class 'self', modifying it has no effect on the class.
# So use setattr() to set the attribute.
for k, v in attrs.iteritems():
if not hasattr(self, k.upper()):
setattr(self, k.upper(), v)
super(MethodAliasingType, self).__init__(name, bases, attrs)
class Base(object):
__metaclass__ = MethodAliasingType
class Sub(Base):
def get(self):
pass
现在,Sub.get
和 Sub.GET
真的是别名,在子类中重写一个而不重写另一个的效果也如你所愿。
>>> Sub.get
<unbound method Sub.get>
>>> Sub.GET
<unbound method Sub.get>
>>> Sub().get
<bound method Sub.get of <__main__.Sub object at 0xb708978c>>
>>> Sub().GET
<bound method Sub.get of <__main__.Sub object at 0xb7089a6c>>
>>> Sub().__dict__
{}
(当然,如果你不想让重写一个而不重写另一个有效,你可以在你的元类中直接把这当作错误处理。)你也可以使用类装饰器(在 Python 2.6 及以后版本)来实现和元类相同的效果,但这意味着每个 Base
的子类都需要使用类装饰器——类装饰器是不会被继承的。