如何在Python中实现Obj-C类别?

9 投票
3 回答
1957 浏览
提问于 2025-04-15 13:44

Obj-C(我已经很久没用过了)有一种叫做类别的东西,可以用来扩展类。你只需要声明一个类别,添加一些新方法,然后把它编译进你的程序中,这样这个类的所有实例就都能使用这些新方法了。

Python也有类似的功能,叫做mixin,我也用过,但mixin必须在程序的底部使用:也就是说,类自己得声明它。

想象一下类别的使用场景:假设你有一个很大的类层次结构,用来描述与数据交互的不同方式,声明了多态的方法来获取不同的属性。现在,类别可以帮助使用这些描述类的人,通过实现一个方便的接口,让他们在一个地方访问这些方法。(比如,一个类别方法可以尝试两种不同的方法,然后返回第一个定义的(非空)返回值。)

在Python中有没有办法做到这一点呢?

示例代码

希望这能让你明白我的意思。关键是,类别就像一个聚合接口,AppObj的使用者可以在他们自己的代码中进行更改。

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())

3 个回答

2

Python中的setattr函数让这个事情变得简单。

# 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
9

为什么不直接动态添加方法呢?

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

我知道Objective-C,这看起来就像是类别(categories)。唯一的缺点是你不能对内置类型或扩展类型这样做。

4

我想出了一个类装饰器的实现方法。我使用的是Python 2.5,所以我实际上没有用装饰器的语法来测试它(如果能用那样的语法就好了),而且我也不太确定这样做是否真的正确。不过,它的样子是这样的:

pycategories.py

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

Copyright 2009 Ulrik Sverdrup <ulrik.sverdrup@gmail.com>
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

撰写回答