Python extension methods

后端 未结 7 1942
星月不相逢
星月不相逢 2021-02-02 06:39

OK, in C# we have something like:

public static string Destroy(this string s) { 
    return \"\";
}

So basically, when you have a string you ca

相关标签:
7条回答
  • 2021-02-02 07:19

    I would use the Adapter pattern here. So, let's say we have a Person class and in one specific place we would like to add some health-related methods.

    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
    

    I consider it to be an easy-to-read, yet flexible and pythonic solution.

    0 讨论(0)
  • 2021-02-02 07:22

    You can do what you have asked like the following:

    def extension_method(self):
        #do stuff
    class.extension_method = extension_method
    
    0 讨论(0)
  • 2021-02-02 07:26

    You can achieve this nicely with the following context manager that adds the method to the class or object inside the context block and removes it afterwards:

    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)
    
    

    Usage is as follows:

    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
    
    0 讨论(0)
  • 2021-02-02 07:27

    You can change the built-in classes by monkey-patching with the help of forbidden fruit

    But installing forbidden fruit requires a C compiler and unrestricted environment so it probably will not work or needs hard effort to run on Google App Engine, Heroku, etc.

    I changed the behaviour of unicode class in Python 2.7 for a Turkish i,I uppercase/lowercase problem by this library.

    # -*- coding: utf8 -*-
    # Redesigned by @guneysus
    
    import __builtin__
    from forbiddenfruit import curse
    
    lcase_table = tuple(u'abcçdefgğhıijklmnoöprsştuüvyz')
    ucase_table = tuple(u'ABCÇDEFGĞHIİJKLMNOÖPRSŞTUÜVYZ')
    
    
    def upper(data):
        data = data.replace('i',u'İ')
        data = data.replace(u'ı',u'I')
        result = ''
        for char in data:
            try:
                char_index = lcase_table.index(char)
                ucase_char = ucase_table[char_index]
            except:
                ucase_char = char
            result += ucase_char
        return result
    
    curse(__builtin__.unicode, 'upper', upper)
    class unicode_tr(unicode):
        """For Backward compatibility"""
        def __init__(self, arg):
            super(unicode_tr, self).__init__(*args, **kwargs)
    
    if __name__ == '__main__':
        print u'istanbul'.upper()
    
    0 讨论(0)
  • 2021-02-02 07:29

    After a week, I have a solution that is closest to what I was seeking for. The solution consists of using getattr and __getattr__. Here is an example for those who are interested.

    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 
    

    I haven't test this example, I just gave it to give an idea for who might be interested. Hope that helps.

    0 讨论(0)
  • 2021-02-02 07:31

    You can just modify the class directly, sometimes known as monkey patching.

    def MyMethod(self):
          return self + self
    
    MyClass.MyMethod = MyMethod
    del(MyMethod)#clean up namespace
    

    I'm not 100% sure you can do this on a special class like str, but it's fine for your user-defined classes.

    Update

    You confirm in a comment my suspicion that this is not possible for a builtin like str. In which case I believe there is no analogue to C# extension methods for such classes.

    Finally, the convenience of these methods, in both C# and Python, comes with an associated risk. Using these techniques can make code more complex to understand and maintain.

    0 讨论(0)
提交回复
热议问题