【问题标题】:How to add function with a name supplied as string to a class in python?如何将名称作为字符串提供的函数添加到python中的类?
【发布时间】:2019-10-18 14:03:11
【问题描述】:

我有几个类,它们的方法都遵循相同的结构。为了这个例子,让我们说它是

class Foo:
    def say_hello(self,a,b,c,d) : print("hello" + a + b + c + d)
    def say_moo(self,a,b,c,d) : print("moo" + a + b + c + d)
    def say_foo(self,a,b,c,d) : print("foo" + a + b + c + d)
    # more of the same pattern...

class Bar:
    def say_hello(self,a,b,c,d) : print("hello" + a + b + c + d)
    def say_bar(self,a,b,c,d) : print("bar" + a + b + c + d)
    def say_boo(self,a,b,c,d) : print("boo" + a + b + c + d)

#more classes of the same pattern...

拥有这些方法是必需的,因此不幸的是,简单地使用def say_x(self,x,a,b,c,d) : print(x + a + b + c + d) 并不是解决方案。

我想避免重复自己,并且正在寻找一种方法来写一些类似的东西

class Foo :
   def SOME_MAGIC("hello")
   def SOME_MAGIC("moo")
   def SOME_MAGIC("foo")

这将导致相当于上述Foo。搜索主题时,我总是使用元类找到一些东西,但它们总是带有注释:“如果你想知道是否需要它们,你就不需要。”。因此,显然我不需要它们。我需要什么?

【问题讨论】:

  • 您可以在这里利用继承。使类 FooBar 从另一个单独的类继承 SOME_MAGIC,例如 BarStool
  • @HampusLarsson 大多数方法在FooBar(以及其他)中都不同,只有少数例外。我知道我可以将say_hello 移动到基类,但这就是我所知道的如何在这里使用继承

标签: python python-2.7 class


【解决方案1】:

或者,您可以使用functools.partial

from functools import partial
class Foo:
    def __init__(self):     
        cmds = ('hello', 'moo', 'boo')  # only define your strings once
        for c in cmds:
            setattr(self, f'say_{c}', partial(self.say, c))

    def say(self, x, a, b, c, d):
        print(x + a + b + c + d)

测试:

>>> f = Foo()
>>> f.say_hello(*'abcd')
helloabcd
>>> f.say_boo(*'1234')
boo1234

好处是您只需定义一次字符串。缺点是可读性不太明显。

如果say() 在类之间是通用的,您甚至可以这样做:

from functools import partialmethod

def add_say(cls, *cmds:str):
    def say(self, x, a, b, c, d):
        print(x, a, b, c, d)
        # ... do something with self if you need ...

    for cmd in cmds:
        setattr(cls, f'say_{cmd}', partialmethod(say, cmd))

class Foo:
    def __init__(self):
        add_say(Foo, 'hello', 'moo', 'boo')

class Bar:
    def __init__(self):
        add_say(Bar, 'hello', 'bar', 'baz')

测试:

>>> f = Foo()
>>> f.say_boo(*'abcd')
boo a b c d
>>> b = Bar()
>>> b.say_baz(*'dude')
baz d u d e

【讨论】:

  • OP 标记了他们的问题 python2.7,我认为格式字符串或类型注释不兼容。 每次创建实例时,任一解决方案的执行都会创建和绑定新的部分函数。在第一个解决方案中,这些方法根本不存在于类中。在我看来,这两种解决方案都很糟糕。
  • 使用后一种解决方案,在创建实例之前,该方法不会存在于类字典中。这可能会导致意想不到的问题:Foo.say_hello( Foo(), *range(4) ) 的无异常执行将取决于它之前是否创建了 Foo 的实例。
  • 很公平,我没有抓住 python2.7 标记。但是,我认为这里最明显和最清晰的解决方案就是写出所有 say_whatever 方法,因为它在整个代码库中更加清晰、可读和可管理。 IMO 要求强制使用不同的 say_whatever 方法来实现同一件事,这是一个应该重新审视的设计决策,但这显然不在 OP 的控制范围内。
【解决方案2】:

您正在寻找functools.partialmethod

class Foo:
    def say(self, first, a, b, c, d) : 
        print(first, a, b, c, d)

    say_hello = functools.partialmethod(say, 'hello')

【讨论】:

  • 我希望我只能写一次hello,但这已经足够接近了
【解决方案3】:
from __future__ import print_function

class SayMetaClass( type ):
    def __new__( mcls, name, bases, dict_ ):
        say_template = lambda value: lambda self, a, b, c, d: print( value + a + b + c + d )

        if "SAY_WHAT" in dict_:
            for value in dict_[ "SAY_WHAT" ]:
                method_name = "say_{value}".format( value=value )
                if method_name in dict_:
                    raise ValueError( "{method_name} is already defined in the class!".format( method_name=method_name ) )
                dict_[ method_name ] = say_template( value )

            del dict_[ "SAY_WHAT" ]

        return super( SayMetaClass, mcls ).__new__( mcls, name, bases, dict_ )

class Foo( object ):
    __metaclass__ = SayMetaClass

    SAY_WHAT = [ "hello", "moo", "foo" ]

class Bar( object ):
    __metaclass__ = SayMetaClass

    SAY_WHAT = [ "hello", "bar", "boo" ]

class Baz( Foo, Bar ):
    pass

args = "1234"

f = Foo()
f.say_hello( *args )
f.say_moo( *args )
f.say_foo( *args )

b = Baz()
b.say_hello( *args )
b.say_moo( *args )
b.say_bar( *args )

代码需要适应 Python 3 兼容性。

我故意避免使用 functools 助手。我会不鼓励上面的代码,因为它使堆栈跟踪更加冗长,破坏了静态分析工具,使动态自省更加困难,并且通常比动态方法更混乱。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2018-12-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-12-23
    • 2021-07-04
    • 2015-01-25
    • 1970-01-01
    相关资源
    最近更新 更多