• Home
  • About
    • Xrlin photo

      Xrlin

      A blog for sharing my thoughts and knowledge

    • Learn More
    • Github
  • Posts
    • All Posts
    • All Tags
  • Projects

python——对class中属性的操作进行限制

03 Jun 2016

Reading time ~1 minute

python不想java等语言一样,对类型有强制要求,这是因为会自动处理变量的类型,所以在python中可以给一个原本为int类型的变量赋予一个字符串类型的值(其实此时实际上是新建了一个字符串的变量),但有时候我们想明确指定变量的类型,只允许给变量赋予特定类型的值,这就需要我们在给变量赋值值进行类型检查,常见的方法是使用@property装饰器或者是重写__get__、 __set__等方法。

使用@property装饰器

#:demo1
class Person(object):
    def __init__(self, name):
        self._name = name
    
    @property   # 使用@property装饰器设定一个为name属性名,通过obj.name访问将返回 self._name
    def name(self):   
        return self._name
    
    @name.setter    # setter方法,给name赋值时调用,接受一个参数,通过此方法限定name只能是字符串类型
    def name(self, name):
        if isinstance(name, str):
            self._name = name
        else:
            raise TypeError('name must be string')
    
    @name.deleter  # deleter方法,使用del 关键字删除属性时调用
    def name(self):
        raise AttributeError('name cannot be deleted')

p = Person('小明')
print(p.name)  #  输出’小明‘
p.name = 'xrlin'  # 更改name属性为’xrlin'
print(p.name)
p.name = 2 # 抛出TypeError异常
del p.name # 抛出AttributeError异常

重写__get__、__set__方法

python中使用setattr、getattr时分别会调用对应对象的__get__、set__方法,当访问对象或者给对象赋值时也会调用对应对象的__get、set__方法,因此可以通过重写class的__get、__set__方法对对象的访问等操作进行自定义。

#:demo2
class Name(object):
    def __init__(self, name='', expected_type=str):  # 默认name的属性为str类型
        self.name = name
        self.expected_type = expected_type
        
    def __set__(self, instance, value):   
        if not isinstance(value, self.expected_type):  
            raise TypeError('{} must be a {}'.format(self.name, self.expected_type))  # 当类型不符合时抛出异常
        self.name = value    
        
    def __get__(self, instance, cls):
        if not instance:
            return self
        return self.name
        
class Human(object):
    name  = Name()  
    def __init__(self, name):
        self.name = name

h = Human('xrlin')
print(h.name)  # 输出xrlin
h.name = 3   # 抛出TypeError


# 还可以将自定义一个强制类型检查的装饰器,方便重复使用
#:demo3
class Typed(object):
    def __init__(self, name, expected_type):
        self.name = name
        self.expected_type = expected_type
        
    def __set__(self, instance, value):  
        if not isinstance(value, self.expected_type):
            raise TypeError('{} must be a {}'.format(self.name, self.expected_type))
        # setattr(instance, self.name, value)    # 不能使用setattr方法,该方法就是调用__set__方法,会导致循环递归调用
        instance.__dict__[self.name] = value   # instance即被装饰的class的实例对象
        
    def __get__(self, instance, cls):  
        if not instance:     # 不是通过实例对象而是通过class直接调用时instance为None
            return self
        return instance.__dict__[self.name]
        
def forcedtyped(**kwargs):
    """
    :@kwargs dict, 包含接受的要进行类型检查的属性名和类型
    """
    def decorate(cls):
        for name, expected_type in kwargs.items():
            setattr(cls, name, Typed(name, expected_type))  # 设置类属性
        return cls
    return decorate
    
@forcedtyped(name=str)  # 设定name属性为str类型
class Test(object):
    def __init__(self, name):
        self.name = name
        
test = Test('xr')
print(test.name) # 输出 xr
test.name = 2333  # 抛出TypeError异常

使用@property装饰器可以比较方便地自定义属性,能够满足一般需求,如果在多个class中都需要进行不同的属性类型检查,可以像demo3一样创建一个装饰器,方便进行代码重用。



Python Like Tweet +1