天天看點

Python描述器實作類型檢查

(一)

使用描述器對指派過程做類型檢查

下述代碼的簡要說明:
  • a = A(1,’yhy’) 執行個體化A類的時候,self.x通路的x是類變量TypeCheck(‘a’,int),首先會初始化TypeCheck類,由于是self.x = x指派會調用set方法,在set方法裡面,instance.dict[self.srcType] = value, 就已經将指派完成了。
  • a.x 取值的時候,需要self.x,同樣是調用self.TypeCheck,也會初始化,初始化的時候,在init方法裡面會拿到self.srcType和self.dstType的值,拿到了self.srcType和self.dstType值在get方法裡面,才會知道instance__dict__[key]的key是什麼,依據對應的key值取出對于的value,最後傳回。是以最後拿到的是instance__dict__[key]的值
class TypeCheck:
    def __init__(self, srcType, dstType):
        self.srcType = srcType
        self.dstType = dstType
    # instance == a , cls == A
    def __get__(self, instance, cls):
        if instance is None:
            return self
        return instance.__dict__[self.srcType]

    def __set__(self, instance, value):
        if isinstance(value,self.dstType):
            instance.__dict__[self.srcType] = value
        else:
            raise TypeError('{} should be {}'.format(value,self.dstType))

class A:

    # 這個a,b 作為字典的key隻要不一樣就行
    x = TypeCheck('a',int)
    y = TypeCheck('b',str)

    def __init__(self, x, y):
        self.x = x
        self.y = y

a = A(,'yhy')
print(a.x,a.y)
           
(二)

在裝飾器中調用描述器做類型檢查

下述代碼的簡要說明:
  • 通過setattr魔術方法,對Person類進行了修改,這裡的name作為類屬性,name = TypeCheck(name, required_type),這樣就将Person類進行了改造,使得Person類有了兩個類變量,一個是name = TypeCheck(‘name’, required_type), 另一個是age = TypeCheck(‘age’, required_type)
  • 是以,Person(‘yhy’,18) 初始化的時候,self.name 中的name不是執行個體變量而是類變量,會調用描述器TypeCheck
  • 指派的時候,就會調用set方法,取值的時候會調用get方法
# Python write by yhy
from functools import wraps

class TypeCheck:
    def __init__(self, srcType, dstType):
        self.srcType = srcType
        self.dstType = dstType
    # instance == a , cls == A
    def __get__(self, instance, cls):
        if instance is None:
            return self
        return instance.__dict__[self.srcType]

    def __set__(self, instance, value):
        if isinstance(value,self.dstType):
            instance.__dict__[self.srcType] = value
        else:
            raise TypeError('{} should be {}'.format(value,self.dstType))

# 函數裝飾器裝飾的是一個Person類
# 描述器描述類與描述函數是不一樣的

def typeassert(**kwargs):

    def dec(cls):
        for name, required_type in kwargs.items():


            setattr(cls, name, TypeCheck(name, required_type))
        return cls
    return dec

@typeassert(name=str, age=int)
class Person:
    def __init__(self, name: str, age: int):
        self.name = name
        self.age = age

p = Person('yhy',)
print(p.name)
           

裝飾器裝飾的Person類變為:

# 裝飾器修改後的Person類
class Person:
    # 裝飾器使得Person類多了兩個類變量name和age
    name = TypeCheck('name',str) 
    age = TypeCheck('age',int)
    def __init__(self, name: str, age: int):
        self.name = name
        self.age = age
           
(三)

導入inspect庫,通過signature函數分析裝飾器中類的參數,實作對裝飾器中類的裝飾

簡要介紹inspect庫的signature函數:
# signature庫可以對擷取類初始化的參數的變量名和變量類型
from inspect import signature

class A:
    def __init__(self, x: int, y: str):
        pass

print(signature(A).parameters)
print(signature(A).parameters.items())
for key, value in signature(A).parameters.items():
    print(key)
    # 取出類型
    print(value.annotation)


# print(key) 和 print(value.annotation) 的輸出結果為
x                   # 這是參數變量名
<class 'int'>       # 這是變量名對應的類型
y
<class 'str'>
           
通過上述的inspect庫的signature函數的簡要介紹,那麼就可以通過signature函數擷取變量名,進而實作無需給裝飾器顯示的傳遞參數,可以直接分析需要裝飾的類,擷取類初始化需要傳遞的參數名和參數的類型,在通過參數名和參數的類型裝飾需要裝飾的類
from inspect import signature

class TypeCheck:
    def __init__(self, srcType, dstType):
        self.srcType = srcType
        self.dstType = dstType
    # instance == a , cls == A
    def __get__(self, instance, cls):
        if instance is None:
            return self
        return instance.__dict__[self.srcType]

    def __set__(self, instance, value):
        if isinstance(value,self.dstType):
            instance.__dict__[self.srcType] = value
        else:
            raise TypeError('{} should be {}'.format(value,self.dstType))

def typeassert(cls):
    #def dec(cls):
        sig = signature(cls).parameters.items()
        for key, value in sig:
            setattr(cls, key, TypeCheck(key,value.annotation))
        return cls
    #return dec

@typeassert
class Person:
    def __init__(self, name: str, age: int):
        self.name = name
        self.age = age

p = Person('yhy',)

print(p.name)
print(p.age)