Python-描述符应用@classmethod、@staticmethod、@property

发布于 2020-05-25  680 次阅读


利用描述符的原理,我们完全可以自定义模拟@classmethod、@staticmethod、@property等属性

模拟@classmethod

class TestClassmethod:
    """
    使用描述符模拟@classmethod
    """

    def __init__(self, func):
        self.func = func

    def __get__(self, instance, owner):
        print('in classmethod __get__')
        # 对原函数进行加工,最后返回该函数
        def newfunc(*args, **kwargs):
            print('函数加工处理后,返回实例的类')
            return self.func(owner, *args, **kwargs)
        return newfunc

class Movie:
    title = '唐人街探案3'

    def movie(cls):
        print('电影名称是:{}'.format(cls.title))
    mymovie = TestClassmethod(movie)
    
    # 完全等价 mymovie = TestClassmethod(movie)
    @TestClassmethod
    def movie1(cls):
        print('电影名称是:{}'.format(cls.title))

print(Movie.mymovie())
print(Movie.movie1())

################################################
in classmethod __get__
函数加工处理后,返回实例的类
电影名称是:唐人街探案3
None
in classmethod __get__
函数加工处理后,返回实例的类
电影名称是:唐人街探案3
None

模拟@staticmethod

staticmethod与classmethod不同的是,classmethod需要传入一个类的引用作为参数,为staticmethod不用

class TestStaticmethod:
    """
    使用描述符模拟@staticmethod
    """

    def __init__(self, func):
        self.func = func

    def __get__(self, instance, owner):
        print('in staticmethod __get__')
        print('owner------------------', owner)
        # 对原函数进行加工,最后返回该函数
        def newfunc(*args, **kwargs):
            print('函数加工处理后,返回实例的类')
            return self.func( *args, **kwargs) # 此处不能加owner参数,静态方法么有自身对象self、也没有自身类cls
        return newfunc

class Movie:
    title = '唐人街探案3'

    def movie(cls):
        print('电影名称是:{}'.format(cls.title))
    mymovie = TestClassmethod(movie)

    @TestClassmethod
    def movie1(cls):
        print('电影名称是:{}'.format(cls.title))

    @TestStaticmethod
    def moive2(): # 此处pycharm会报错但是不会影响执行,静态方法不需要自身对象self、也不需要自身类cls参数
        print('最近没有电影上映------------------------')

print(Movie.moive2())

###################################################################
in staticmethod __get__
owner------------------ <class 'test_classmethod.Movie'>
函数加工处理后,返回实例的类
最近没有电影上映------------------------
None

@Property

Python 内置的 property 函数可以说是最著名的描述符之一,几乎所有讲述描述符的文章都会拿它做例子。

property 是用 C 实现的,不过这里有一份等价的 Python 实现:

class TestProperty:
    def __init__(self, fget=None, fset=None, fdel=None, doc=None):
        self.fget = fget
        self.fset = fset
        self.fdel = fdel
        self.__doc__ = doc

    def __get__(self, obj, objtype=None):
        print('in __get__')
        if obj is None:
            return self
        if self.fget is None:
            raise AttributeError
        return self.fget(obj)

    def __set__(self, obj, value):
        print('in __set__')
        if self.fset is None:
            raise AttributeError
        self.fset(obj, value)

    def __delete__(self, obj):
        print('in __delete__')
        if self.fdel is None:
            raise AttributeError
        self.fdel(obj)

    def getter(self, fget):
        print('in getter')
        return type(self)(fget, self.fset, self.fdel, self.__doc__)

    def setter(self, fset):
        print('in setter')
        return type(self)(self.fget, fset, self.fdel, self.__doc__)

    def deleter(self, fdel):
        print('in deleter')
        return type(self)(self.fget, self.fset, fdel, self.__doc__)


class Student:

    def __init__(self, name, math):
        self.name = name
        self.math = math

    @TestProperty
    def math(self):
        return self._math

    @math.setter
    def math(self, value):
        if 0 <= value <= 100:
            self._math = value
        else:
            raise ValueError('value must be in [0,100]')

s = Student('zhangsan', 100)
print(s.math)

函数 math 作为位置参数被赋给 Property.__init__()fget,得到新的 math 已经不是个函数而是个完整实现了 __get__() 方法的描述器实例了。

@math.setter 的用法略有不同。它实际上是利用上面定义的描述器实例 math 的 setter 方法,重新创建了新的实例。这时变量 math 再次被更新,指向了一个完整实现 __get__()__set__() 方法的新描述器。传入 setter 方法的函数名必须是 math。

与一般的属性访问不同,s.math 访问的已经不是简单的属性,而是相当于 math.__get__(s),可以调用各种复杂方法对属性作检查、包装 。


一名测试工作者,专注接口测试、自动化测试、性能测试、Python技术。