Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to do Obj-C Categories in Python?

Tags:

python

Obj-C (which I have not used for a long time) has something called categories to extend classes. Declaring a category with new methods and compiling it into your program, all instances of the class suddenly have the new methods.

Python has mixin possibilities, which I use, but mixins must be used from the bottom of the program: the class has to declare it itself.

Foreseen category use-case: Say you have a big class hierarchy that describe different ways of interacting with data, declaring polymorphic ways to get at different attributes. Now a category can help the consumer of these describing classes by implementing a convenient interface to access these methods in one place. (A category method could for example, try two different methods and return the first defined (non-None) return value.)

Any way to do this in Python?

Illustrative code

I hope this clarifies what I mean. The point is that the Category is like an aggregate interface, that the consumer of AppObj can change in its code.

class AppObj (object):
  """This is the top of a big hierarchy of subclasses that describe different data"""
  def get_resource_name(self):
    pass
  def get_resource_location(self):
    pass

# dreaming up class decorator syntax
@category(AppObj)
class AppObjCategory (object):
  """this is a category on AppObj, not a subclass"""
  def get_resource(self):
    name = self.get_resource_name()
    if name:
      return library.load_resource_name(name)
    else:
      return library.load_resource(self.get_resource_location())
like image 952
u0b34a0f6ae Avatar asked Aug 20 '09 11:08

u0b34a0f6ae


3 Answers

Python's setattr function makes this easy.

# categories.py

class category(object):
    def __init__(self, mainModule, override = True):
        self.mainModule = mainModule
        self.override = override

    def __call__(self, function):
        if self.override or function.__name__ not in dir(self.mainModule):
            setattr(self.mainModule, function.__name__, function)

 

# categories_test.py

import this
from categories import category

@category(this)
def all():
    print "all things are this"

this.all()
>>> all things are this
like image 158
Patrick Perini Avatar answered Oct 16 '22 16:10

Patrick Perini


Why not just add methods dynamically ?

>>> class Foo(object):
>>>     pass
>>> def newmethod(instance):
>>>     print 'Called:', instance
...
>>> Foo.newmethod = newmethod
>>> f = Foo()
>>> f.newmethod()
Called: <__main__.Foo object at 0xb7c54e0c>

I know Objective-C and this looks just like categories. The only drawback is that you can't do that to built-in or extension types.

like image 33
fraca7 Avatar answered Oct 16 '22 15:10

fraca7


I came up with this implementation of a class decorator. I'm using python2.5 so I haven't actually tested it with decorator syntax (which would be nice), and I'm not sure what it does is really correct. But it looks like this:

pycategories.py

"""
This module implements Obj-C-style categories for classes for Python

Copyright 2009 Ulrik Sverdrup <[email protected]>
License: Public domain
"""

def Category(toclass, clobber=False):
    """Return a class decorator that implements the decorated class'
    methods as a Category on the class @toclass

    if @clobber is not allowed, AttributeError will be raised when
    the decorated class already contains the same attribute.
    """
    def decorator(cls):
        skip = set(("__dict__", "__module__", "__weakref__", "__doc__"))
        for attr in cls.__dict__:
            if attr in toclass.__dict__:
                if attr in skip:
                    continue
                if not clobber:
                    raise AttributeError("Category cannot override %s" % attr)
            setattr(toclass, attr, cls.__dict__[attr])
        return cls
    return decorator
like image 20
u0b34a0f6ae Avatar answered Oct 16 '22 15:10

u0b34a0f6ae