Python - 让类装饰器作用于派生类
在我们用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