首页 > 解决方案 > Python:将函数作为参数传递以初始化对象的方法。Pythonic 与否?

问题描述

我想知道是否有一种可接受的方式将函数作为参数传递给对象(即在init块中定义该对象的方法)。

更具体地说,如果函数依赖于对象参数,将如何做到这一点。

似乎 pythonic 足以将函数传递给对象,函数就像其他任何东西一样是对象:

def foo(a,b):
    return a*b

class FooBar(object):
    def __init__(self, func):
        self.func = func

foobar = FooBar(foo)
foobar.func(5,6)

# 30

这样就可以了,只要您引入对对象其他属性的依赖,问题就会出现。

def foo1(self, b):
    return self.a*b

class FooBar1(object):
    def __init__(self, func, a):
        self.a=a
        self.func=func

# Now, if you try the following:
foobar1 = FooBar1(foo1,4)
foobar1.func(3)
# You'll get the following error:
# TypeError: foo0() missing 1 required positional argument: 'b'

这可能只是违反了 python 中 OOP 的一些神圣原则,在这种情况下,我只需要做其他事情,但它似乎也可能被证明是有用的。

我有几种可能的方法来解决这个问题,我想知道哪种(如果有的话)被认为是最可以接受的。

解决方案 1

foobar1.func(foobar1,3)

# 12
# seems ugly

解决方案 2

class FooBar2(object):
    def __init__(self, func, a):
        self.a=a
        self.func = lambda x: func(self, x)

# Actually the same as the above but now the dirty inner-workings are hidden away. 
# This would not translate to functions with multiple arguments unless you do some ugly unpacking.
foobar2 = FooBar2(foo1, 7)
foobar2.func(3)

# 21

任何想法,将不胜感激!

标签: pythonoop

解决方案


将函数传递给对象很好。这种设计没有任何问题。

但是,如果您想将该函数转换为绑定方法,则必须小心。如果您执行类似的操作self.func = lambda x: func(self, x),您将创建一个引用循环 -self具有对 的引用self.func,并且存储在其中的 lambdaself.func具有对 的引用self。Python 的垃圾收集器确实会检测到引用循环并最终将其清理干净,但这有时可能需要很长时间。过去我的代码中有引用循环,这些程序经常使用超过 500 MB 的内存,因为 python 不会经常垃圾收集不需要的对象。

正确的解决方案是使用weakref模块创建对 的弱引用self例如这样:

import weakref

class WeakMethod:
    def __init__(self, func, instance):
        self.func = func
        self.instance_ref = weakref.ref(instance)

        self.__wrapped__ = func  # this makes things like `inspect.signature` work

    def __call__(self, *args, **kwargs):
        instance = self.instance_ref()
        return self.func(instance, *args, **kwargs)

    def __repr__(self):
        cls_name = type(self).__name__
        return '{}({!r}, {!r})'.format(cls_name, self.func, self.instance_ref())


class FooBar(object):
    def __init__(self, func, a):
        self.a = a
        self.func = WeakMethod(func, self)

f = FooBar(foo1, 7)
print(f.func(3))  # 21

以下所有解决方案都会创建一个参考循环,因此很糟糕

  • self.func = MethodType(func, self)
  • self.func = func.__get__(self, type(self))
  • self.func = functools.partial(func, self)

推荐阅读