Python - 让类装饰器作用于派生类

4 投票
3 回答
722 浏览
提问于 2025-04-16 19:46

在我们用Django开发的应用中,有时候需要自动给用户分配一些模型的权限,这些模型有所有者(字段的名字没有固定,可以是“user”、“owner”、“coach”等等,也可能有多个字段)。我的解决办法是创建一个装饰器,把这些字段名放在模型定义之前,像这样(示例中不使用Django特定的代码):

@auto_assign_perms('owner', 'user')
class Test(Base):
    pass

假设Base是一个抽象类,继承自Django的Model类,我在里面添加了在对象保存后分配权限的功能。现在我只是打印出分配给这个类的用户列表。下面是装饰器和Base类的代码:

class auto_assign_perms(object):
    def __init__(self, *users):
        self.users = users

    def __call__(self, cls):
        cls.owners.update(self.users)
        return cls


class Base(object):
    owners = set()

    def save(self, *args, **kwargs):
        for owner in self.owners:
            print owner,
        print

我的模型可能看起来是这样的:

@auto_assign_perms('owner', 'user')
class Test(Base):
    pass

@auto_assign_perms('coach')
class Test2(Base):
    pass

问题是这两个子类都包含了所有三个字段('owner', 'user', 'coach'),虽然在Base.save()方法中用print self.__class__.__name__可以正确显示“Test”或“Test2”。我尝试在Base类中添加类方法get_owners(),然后遍历它的结果,但没有帮助。我该怎么解决这个问题?也许我应该使用 metaclasses(我还不太明白这个)?谢谢!

3 个回答

0

叫我多疑也没关系,但我觉得这个解决方案更优雅,因为我认为你根本不需要把“拥有者”设为一个类的变量:

def auto_assign_perms(*users):

    def class_wrapper(cls):
        class ClassWrapper(cls):
            def __init__(self, owners=users):
                super(cls, self).__init__(owners=owners)

        ClassWrapper.__name__ = cls.__name__
        ClassWrapper.__module__ = cls.__module__

        return ClassWrapper

    return class_wrapper


class Base(object):
    def __init__(self, owners=None):
        if owners is None:
            owners = set()
        self.owners = owners

    def save(self, *args, **kwargs):
        for owner in self.owners:
            print owner,
        print


@auto_assign_perms('owner', 'user')
class Test1(Base):
    pass


@auto_assign_perms('coach')
class Test2(Base):
    pass


class Test3(Base):
    pass


t = Test1(); t.save() # owner user
t = Test2(); t.save() # coach
t = Test3(); t.save() # 
1

你把 owners 当作 Base 类的一个类变量使用,所以每次你修改 owners 的时候,所有继承这个类的子类都会看到这个变化。

要解决这个问题,你应该把 owners 变量定义为子类的类变量:

class Base(object):

    def save(self, *args, **kwargs):
        for owner in self.owners:
            print owner,
        print

@auto_assign_perms('owner', 'user')
class Test(Base):
     owners = set()

@auto_assign_perms('coach')
class Test2(Base):
     owners = set()
4

你需要设置拥有者的列表,而不是更新它:

class auto_assign_perms(object):
    def __init__(self, *users):
        self.users = users

    def __call__(self, cls):
        cls.owners = set(self.users) # <- here
        return cls

#some tests
@auto_assign_perms('owner', 'user')
class Test(Base):
    pass

@auto_assign_perms('coach')
class Test2(Base):
    pass


t = Test()
t.save()
t = Test2()
t.save()

>>> 
owner user
coach

撰写回答