38

How can I extend a builtin class in python? I would like to add a method to the str class.
I've done some searching but all I'm finding is older posts, I'm hoping someone knows of something newer.

dreftymac
  • 31,404
  • 26
  • 119
  • 182
UnkwnTech
  • 88,102
  • 65
  • 184
  • 229

4 Answers4

41

Just subclass the type

>>> class X(str):
...     def my_method(self):
...         return int(self)
...
>>> s = X("Hi Mom")
>>> s.lower()
'hi mom'
>>> s.my_method()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 3, in my_method
ValueError: invalid literal for int() with base 10: 'Hi Mom'

>>> z = X("271828")
>>> z.lower()
'271828'
>>> z.my_method()
271828
ruohola
  • 21,987
  • 6
  • 62
  • 97
S.Lott
  • 384,516
  • 81
  • 508
  • 779
  • 6
    I was hoping that I wouldn't have to subclass but after some more research I have come to the conclusion that it is not possible any other way, so I'll accept this answer. – UnkwnTech Dec 09 '08 at 12:20
  • 4
    @Unkwntech: Can't imagine a better way than subclassing. Seriously. Whatever you had in mind (monkeypatching?) never seems to work out as well as a clear, precise, obvious subclass. – S.Lott Dec 09 '08 at 12:24
  • 11
    @S.Lott - Ruby's open classes and C#'s extension methods come to mind. – orip Dec 09 '08 at 12:40
  • 3
    @orip: Aware of these. Find monkeypatching in all forms to be a management nightmare -- they effectively prevent reuse by injecting features in obscure ways. – S.Lott Dec 09 '08 at 12:45
  • In this sample type(z.lower()) is str an not X. How to keep it an X ? – dagfr Jul 24 '20 at 22:54
  • 1
    @dagfr by overriding the .lower() method in the X class and returning a new X object – GalacticRaph Jan 14 '21 at 21:38
18

One way could be to use the "class reopening" concept (natively existing in Ruby) that can be implemented in Python using a class decorator. An exemple is given in this page: http://www.ianbicking.org/blog/2007/08/opening-python-classes.html

I quote:

I think with class decorators you could do this:

@extend(SomeClassThatAlreadyExists)
class SomeClassThatAlreadyExists:
    def some_method(self, blahblahblah):
        stuff

Implemented like this:

def extend(class_to_extend):
    def decorator(extending_class):
        class_to_extend.__dict__.update(extending_class.__dict__)
        return class_to_extend
    return decorator
lll
  • 189
  • 1
  • 4
  • 2
    Python 3.9, `__dict__` is no longer a `dict` but a `mappingproxy`, you have to use @MVP's solution instead. – caram Jan 21 '21 at 07:37
7

Assuming that you can not change builtin classes. To simulate a "class reopening" like Ruby in Python3 where __dict__ is an mappingproxy object and not dict object :

def open(cls):
  def update(extension):
    for k,v in extension.__dict__.items():
      if k != '__dict__':
        setattr(cls,k,v)
    return cls
  return update


class A(object):
  def hello(self):
    print('Hello!')

A().hello()   #=> Hello!

#reopen class A
@open(A)
class A(object):
  def hello(self):
    print('New hello!')
  def bye(self):
    print('Bye bye')


A().hello()   #=> New hello!
A().bye()     #=> Bye bye

In Python2 I could also write a decorator function 'open' as well:

def open(cls):
  def update(extension):
    namespace = dict(cls.__dict__)
    namespace.update(dict(extension.__dict__))
    return type(cls.__name__,cls.__bases__,namespace)
  return update
MVP
  • 1,061
  • 10
  • 8
  • At last I can do this like in Ruby. Works like a charm. I prefer the second definition. – caram Jan 21 '21 at 07:38
3

I know this question is quite old now, but just in case someone comes here later, there's a library that allows actually extending built-ins types.

Available for Python 3.7 - 3.11.

ExType

Disclaimer: I'm one of the authors