python扩展方法

Sha*_*kan 23 python extension-methods

好的,在c#中我们有类似的东西:

public static string Destroy(this string s) { 
    return "";
}
Run Code Online (Sandbox Code Playgroud)

所以基本上,当你有一个字符串,你可以做:

str = "This is my string to be destroyed";
newstr = str.Destroy()
# instead of 
newstr = Destroy(str)
Run Code Online (Sandbox Code Playgroud)

现在这很酷,因为在我看来它更具可读性.python有类似的东西吗?我的意思是代替写这样的:

x = SomeClass()
div = x.getMyDiv()
span = x.FirstChild(x.FirstChild(div)) # so instead of this
Run Code Online (Sandbox Code Playgroud)

有什么建议吗?

Dav*_*nan 19

您可以直接修改类,有时也称为猴子修补.

def MyMethod(self):
      return self + self

MyClass.MyMethod = MyMethod
del(MyMethod)#clean up namespace
Run Code Online (Sandbox Code Playgroud)

我不是100%确定你可以在像str这样的特殊类上执行此操作,但它对于用户定义的类很好.

更新

你在评论中证实我怀疑这对于像str这样的内置是不可能的.在这种情况下,我认为这类的C#扩展方法没有类似的东西.

最后,在C#和Python中,这些方法的便利性带来了相关的风险.使用这些技术可以使代码更难以理解和维护.

  • @agf除非你可以强制所有字符串都是这个新的子类,否则没什么用处. (4认同)
  • @DavidHeffernan我知道它已经有两年了,但是一个名为Forbiddenfruit的令人惊奇的小型库已经出现,它可以让你修补内置对象(这通常会被阻止,因为内置插件是用C实现的速度).http://clarete.li/forbiddenfruit/我认为它应该得到一些奖励来完成官方不可能的让python像Ruby那样做日期和时间,比如7.weeks.ago.:-)唯一的障碍是cpython认为.int之后是一个数字的一​​部分,所以你需要在之间放一个空格.和数字,例如7 .weeks().前(). (3认同)
  • 即使在可能的情况下,猴子修补也明显不如扩展方法强大,因为您无法修补整个接口(例如 C# 中的 IEnumerable,在扩展方法中使用它构成了大多数 LINQ 功能的基础)。 (3认同)
  • 刚试过这个,我得到了`TypeError:无法设置内置/扩展类型'str'的属性. (2认同)

mur*_*d99 10

您可以执行您所要求的操作,如下所示:

def extension_method(self):
    #do stuff
class.extension_method = extension_method
Run Code Online (Sandbox Code Playgroud)


Eer*_*ist 6

我会在这里使用适配器模式。所以,假设我们有一个Person类,并且在一个特定的地方我们想添加一些与健康相关的方法。

from dataclasses import dataclass


@dataclass
class Person:
    name: str
    height: float  # in meters
    mass: float  # in kg


class PersonMedicalAdapter:
    person: Person

    def __init__(self, person: Person):
        self.person = person

    def __getattr__(self, item):
        return getattr(self.person, item)

    def get_body_mass_index(self) -> float:
        return self.person.mass / self.person.height ** 2


if __name__ == '__main__':
    person = Person('John', height=1.7, mass=76)
    person_adapter = PersonMedicalAdapter(person)

    print(person_adapter.name)  # Call to Person object field
    print(person_adapter.get_body_mass_index())  # Call to wrapper object method
Run Code Online (Sandbox Code Playgroud)

我认为它是一个易于阅读但灵活的 Pythonic 解决方案。


mrt*_*rts 5

您可以使用以下上下文管理器很好地实现此目的,该上下文管理器将方法添加到上下文块内的类或对象中,然后将其删除:

class extension_method:

    def __init__(self, obj, method):
        method_name = method.__name__
        setattr(obj, method_name, method)
        self.obj = obj
        self.method_name = method_name

    def __enter__(self):
        return self.obj

    def __exit__(self, type, value, traceback):
        # remove this if you want to keep the extension method after context exit
        delattr(self.obj, self.method_name)

Run Code Online (Sandbox Code Playgroud)

用法如下:

class C:
    pass

def get_class_name(self):
    return self.__class__.__name__

with extension_method(C, get_class_name):
    assert hasattr(C, 'get_class_name') # the method is added to C
    c = C()
    print(c.get_class_name()) # prints 'C'

assert not hasattr(C, 'get_class_name') # the method is gone from C
Run Code Online (Sandbox Code Playgroud)

  • 这对于像 str 这样的内置函数不起作用,这是原始问题的一部分。 (3认同)

Sha*_*kan -14

一周后,我得到了一个最接近我所寻求的解决方案。该解决方案包括使用getattr__getattr__。这是一个例子,供有兴趣的人参考。

class myClass:

    def __init__(self): pass

    def __getattr__(self, attr): 
        try:
            methodToCall = getattr(myClass, attr)
            return methodToCall(myClass(), self)
        except:
            pass

    def firstChild(self, node):
        # bla bla bla
    def lastChild(self, node):
        # bla bla bla 

x = myClass()
div = x.getMYDiv()
y = div.firstChild.lastChild 
Run Code Online (Sandbox Code Playgroud)

我还没有测试这个例子,我只是为了给可能感兴趣的人提供一个想法。希望有帮助。

  • 如果我错了,请纠正我,但这种方法作为猴子修补有更严重的限制——现在不仅排除了内置类型,而且还必须提前准备类才能使用扩展方法。顺便提一句。我没有看到任何地方定义了“getMYDiv”。 (4认同)
  • 我不知道为什么这是公认的答案 - 这是所有解决方案中看起来最繁琐的,而且看起来不太正确。如果您可以像我假设的那样更改类,为什么不在那里添加新方法呢? (3认同)