动态添加的方法对子类

时间:2017-04-28 14:57:29

标签: python class inheritance

我遇到了一个动态添加到Python类的方法的问题。考虑以下一组类,其中为任何已定义的<some>_str方法动态添加了方法<some>

class ToStr(object):
    @classmethod
    def add_str_method(cls, name):
        method = getattr(cls, name)
        def str_method(self, *args, **kwargs):
            return str(method(*args, **kwargs))
        setattr(cls, '{0}_str'.format(name), str_method)

class Even(ToStr):
    @classmethod
    def even(cls, values):
        return [value for value in values if value%2 == 0]
Even.add_str_method('even')

class Large(ToStr):
    @classmethod
    def large(cls, values):
        filtered = [value for value in values if value > 5]
        if hasattr(cls, 'even'):
            filtered = cls.even(filtered)
        return filtered
Large.add_str_method('large')

class Special(Even, Large):
    pass

请注意,动态添加的%_str函数是实例方法,而%是类方法。此外,large方法取决于even方法的存在,该方法由hasattr(cls, 'even')确定。

现在我比较每个类中%%_str方法的输出,结果让我感到困惑:

# Even.even
Even().even(values)          [0, 2, 4, 6, 8, 10]
Even().even_str(values)      [0, 2, 4, 6, 8, 10]
# Large.large
Large().large(values)        [6, 7, 8, 9, 10]
Large().large_str(values)    [6, 7, 8, 9, 10]
# Special.even
Special().even(values)       [0, 2, 4, 6, 8, 10]
Special().even_str(values)   [0, 2, 4, 6, 8, 10]
# Special.large
Special().large(values)      [6, 8, 10]
Special().large_str(values)  [6, 7, 8, 9, 10]

Special.large_str()方法不会删除偶数值,但Special类应该从Even类继承它,并通过hasattr检查。

所以我的问题是:在动态添加方法时,hasattr为什么不识别这些方法?

更新:

此效果不依赖于Special类定义中超类的顺序 如果将evenlarge方法定义为实例方法而不是类方法,则不会发生此效果,如下例所示。

class ToStr(object):
    @classmethod
    def add_str_method(cls, name):
        method = getattr(cls, name)
        def str_method(self, *args, **kwargs):
            return str(method(self, *args, **kwargs))
        setattr(cls, '{0}_str'.format(name), str_method)

class Even(ToStr):
    def even(self, values):
        return [value for value in values if value%2 == 0]
Even.add_str_method('even')

class Large(ToStr):
    def large(self, values):
        filtered = [value for value in values if value > 5]
        if hasattr(self, 'even'):
            filtered = self.even(filtered)
        return filtered

Large.add_str_method('large')

1 个答案:

答案 0 :(得分:1)

在这种情况下的问题是可以在类上调用classmethod,所以当你对类getattr执行classmethod时,它将是一个绑定方法(第一个)参数已填写)。这意味着您将添加记住您在getattr中使用的类的方法。因此,拨打Special().large_str(values)的电话会调用str_method,但method内部的Large.large电话只会使用cls=Large拨打Large,但even本身没有classmethods 1}}方法。

另一方面,不是 - getattr将返回self中的自由函数,因此第一个参数将不会被修复,这就是为什么你需要包含method参数的原因str_method - 在您的第二种方法中调用class ToStr(object): @classmethod def add_str_method(cls, name): method = getattr(cls, name) def str_method(self, *args, **kwargs): print(method) # added a print! return str(method(*args, **kwargs)) setattr(cls, '{0}_str'.format(name), str_method) class Even(ToStr): @classmethod def even(cls, values): return [value for value in values if value%2 == 0] Even.add_str_method('even') class Large(ToStr): @classmethod def large(cls, values): print(cls) # added a print! filtered = [value for value in values if value > 5] if hasattr(cls, 'even'): filtered = cls.even(filtered) return filtered Large.add_str_method('large') class Special(Even, Large): pass

>>> Special().large_str(list(range(11)))             
<bound method Large.large of <class '__main__.Large'>>    # bound method
<class '__main__.Large'>                                  # wrong cls

'[6, 7, 8, 9, 10]'                                        # wrong result

说明了这种行为:

<function Large.large at 0x0000024B2FE808C8>       # free function
<__main__.Special object at 0x0000024B2FE7C3C8>    # correct instance

'[6, 8, 10]'                                       # correct result

而正常的方法变体打印:

str_method

您的案例中可能的解决方案/解决方法 将调用class ToStr(object): @classmethod def add_str_method(cls, name): method = getattr(cls, name) def str_method(self, *args, **kwargs): return str(method.__func__(self, *args, **kwargs)) # this line changed setattr(cls, '{0}_str'.format(name), str_method) 中的包装函数(在这种情况下将返回未绑定的方法):

Items