59

OK, in C# we have something like:

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

So basically, when you have a string you can do:

str = "This is my string to be destroyed";
newstr = str.Destroy()
# instead of 
newstr = Destroy(str)

Now this is cool because in my opinion it's more readable. Does Python have something similar? I mean instead of writing like this:

x = SomeClass()
div = x.getMyDiv()
span = x.FirstChild(x.FirstChild(div)) # so instead of this

I'd like to write:

span = div.FirstChild().FirstChild() # which is more readable to me

Any suggestion?

mrts
  • 16,697
  • 8
  • 89
  • 72
Shaokan
  • 7,438
  • 15
  • 56
  • 80
  • I have to point out that your example is superfluous because strings are immutable, so any instance of `newstr = str.Destroy()` or `newstr = Destroy(str)` could be replaced by `newstr = ""` for exactly the same effect. – murgatroid99 Aug 21 '11 at 15:25
  • 3
    Yeah I know, it was just to keep the question shorter. I did not want to write a whole function just to point out an example :) – Shaokan Aug 21 '11 at 15:29
  • 3
    possible duplicate of [Extension methods in Python](http://stackoverflow.com/questions/514068/extension-methods-in-python) – f.cipriani Mar 16 '14 at 11:35
  • Kotlin also has that and I really miss it. – Luís Soares Nov 24 '21 at 23:49

8 Answers8

42

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.

David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
  • 4
    Just tried this and I get `TypeError: can't set attributes of built-in/extension type 'str'`. – Chris Eberle Aug 21 '11 at 16:34
  • I think that means you can't do what you want with str. – David Heffernan Aug 21 '11 at 16:40
  • Sure you can, just subclass `str` first, and do it on the subclass. – agf Aug 21 '11 at 16:53
  • 4
    @agf Not much use unless you can force all strings to be of this new subclass. – David Heffernan Aug 21 '11 at 16:56
  • 6
    @DavidHeffernan I know it's been two years, but an amazing little library has surfaced called Forbiddenfruit which does let you patch built-in objects (which is normally prevented because the built-ins are implemented in C for speed). http://clarete.li/forbiddenfruit/ I think it deserves some sort of prize for accomplishing the officially impossible and letting python do dates and times like Ruby can, such as 7.weeks.ago. :-) The only hitch is that cpython thinks the . after an int is part of a number, so you'd need to put a space between the . and number, such as 7 .weeks().ago(). – alcalde Dec 12 '13 at 01:29
  • 5
    Even where possible, monkey patching is significantly less powerful than extension methods as you cannot monkey patch entire interfaces (such as IEnumerable in C#, the use of which in extension methods forms the basis of most of LINQ's functionality). – marr75 Nov 10 '15 at 18:49
  • 1
    | Using these techniques can make code more complex to understand and maintain. While I mostly agree, I think this can be said about almost any language feature these days. When it comes to extension methods, if used correctly, it can actually make your code simpler and easier to understand. Almost any modern language feature is a double edged sword. Extension methods aren't any worse or better. My advice is to keep extension methods super short and follow single-responsibility principal. In C#, they should usually be kept package private so they don't pollute the namespace. – NanoTree Jun 01 '23 at 15:34
14

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.

Eerik Sven Puudist
  • 2,098
  • 2
  • 23
  • 42
14

You can do what you have asked like the following:

def extension_method(self):
    #do stuff
class.extension_method = extension_method
murgatroid99
  • 19,007
  • 10
  • 60
  • 95
5

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
mrts
  • 16,697
  • 8
  • 89
  • 72
4

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()
guneysus
  • 6,203
  • 2
  • 45
  • 47
  • 4
    Sole thing that annoys me in the python ecosystem to no end is the naming stuff part... I mean "Forbidden Fruit" ??? I skipped this while googling a while back. How on earth am I supposed to guess what it does? – PaulB Jul 14 '19 at 15:08
  • Due to the precarious nature of this package. A somewhat inaccessible (and downright hostile) name is earned – MHDante Aug 22 '19 at 16:19
-3

I'd like to think that extension methods in C# are pretty much the same as normal method call where you pass the instance then arguments and stuff.

instance.method(*args, **kwargs)
method(instance, *args, **kwargs) # pretty much the same as above, I don't see much benefit of it getting implemented in python.
enaielei
  • 83
  • 5
  • 1
    The difference in readability becomes obvious when you have several calls like this chained. It's also known as a "fluent API" and has become quite popular. – MEMark Oct 27 '21 at 16:16
  • 1
    @MEMark Thanks, the first time I heard of Fluent API actually. I'll take a look into it :) – enaielei Oct 28 '21 at 20:33
-13

C# implemented extension methods because it lacks first class functions, Python has them and it is the preferred method for "wrapping" common functionality across disparate classes in Python.

There are good reasons to believe Python will never have extension methods, simply look at the available built-ins:

len(o) calls o.__len__
iter(o) calls o.__iter__
next(o) calls o.next
format(o, s) calls o.__format__(s)

Basically, Python likes functions.

rgz
  • 362
  • 4
  • 20
  • 10
    `C# implemented extension methods because it lacks first class functions` uh, what? C# definitely has first class functions. – Mike G Oct 13 '16 at 16:19
  • 1
    @mikeTheLiar does it? How can i define a free function in c#? Aren't all Functions in C# Class-Methods, either static or not but there is always a class context. – AF_cpp Aug 11 '17 at 13:38
  • 1
    @mikeTheLiar this seems like a Callable-Interface in C# so it combines different forms of Functions, like Class-Functions and annonymous lambdas (I think they are called Delegates), which meet the contract of the interface. This does not have anything to do with first class functions as they are available in C++ for example. I don't think this is possible in C#: int add(int num1, int num2) only statement in a C#-File... no class around or anything. e.g: len() in Python is not part of a class it is just a simple function, without class-context, and it has no access to private class data – AF_cpp Aug 11 '17 at 14:03
  • 3
    `C# implemented extension methods because it lacks first class functions` This is incorrect. "First class functions" refers to the ability of passing around functions, which C# definitely has. Not only that, but the functional support of C# is outstanding, implementing a lot of the core ideas of functional programming (as in Haskell-like functional, not just Python functions). C# also supports creation of "free functions", using the `Func<>` type as said by @MikeTheLiar above. – gnz Nov 02 '18 at 13:52
-13

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.

Shaokan
  • 7,438
  • 15
  • 56
  • 80
  • 4
    Correct me if I am wrong, but this approach has worse limitation as the monkey patching -- now not only you ruled out the builtin types, but you have to prepare the class in advance to use extension method. Btw. I don't see anywhere `getMYDiv` defined. – greenoldman May 14 '14 at 20:20
  • 3
    I don't know why this is the accepted answer - this is the most fiddly looking of all the solutions and doesn't seem quite right. If you could just change the class like how I'm assuming you're trying to say to do, why not just add the new method there? – Craig Brett Mar 22 '17 at 11:58
  • All this seems to do is badly reinvent `property` (which is not what the question asked for): it makes all attribute lookups on missing (because it uses `__getattr__` instead of `__getattribute__`) attributes (`instance.attribute`) turn into method calls on a new instance (`myClass.attribute(myClass(), instance)`). – Solomon Ucko Feb 17 '19 at 14:16