乐趣区

关于python:深入理解-python-虚拟机描述器的王炸应用propertystaticmethod-和-classmehtod

深刻了解 python 虚拟机:形容器的王炸利用 -property、staticmethod 和 classmehtod

在本篇文章当中次要给大家介绍形容器在 python 语言当中有哪些利用,次要介绍如何应用 python 语言实现 python 内置的 proterty、staticmethod 和 class method。

property

当你在编写 Python 代码时,你可能会遇到一些须要通过办法来拜访或设置的属性。Python 中的 property 装璜器提供了一种优雅的形式来解决这种状况,容许你将这些办法封装为属性,从而使代码更加简洁和易于浏览。在本文中,我将向你介绍 property 装璜器的工作原理以及如何在你的代码中应用它。

什么是 property?

Python 中的 property 是一种装璜器,它容许你定义一个办法,使其看起来像一个属性。换句话说,property 容许你以属性的形式拜访或设置类的数据成员,而不用间接调用一个办法。

在 Python 中,属性通常是一个对象的数据成员,它们能够通过间接拜访对象来获取或设置。然而,有时候你可能须要在获取或设置属性时执行某些额定的操作,例如进行类型查看、范畴查看或计算属性等。在这种状况下,应用 property 装璜器能够让你以属性的形式拜访或设置这些属性,并在拜访或设置时执行额定的操作。

如何应用 property?

让咱们看一个简略的例子,假如你正在编写一个示意矩形的类,并且你想要在计算矩形的面积时执行一些额定的操作。你能够应用 property 装璜器来实现这个性能,如下所示:

class Rectangle:
    def __init__(self, width, height):
        self._width = width
        self._height = height
    
    @property
    def width(self):
        return self._width
    
    @width.setter
    def width(self, value):
        if value <= 0:
            raise ValueError("Width must be positive")
        self._width = value
    
    @property
    def height(self):
        return self._height
    
    @height.setter
    def height(self, value):
        if value <= 0:
            raise ValueError("Height must be positive")
        self._height = value
    
    @property
    def area(self):
        return self._width * self._height

在这个示例中,咱们应用 property 装璜器定义了三个属性:width、height 和 area。每个属性都有一个 getter 办法和一个 setter 办法,它们别离负责获取和设置属性的值。当你应用类的实例拜访这些属性时,你会发现它们仿佛就像是一个一般的属性,而不是一个办法。

留神,getter 办法没有参数,而 setter 办法承受一个参数。当你通过类的实例拜访属性时,你只须要应用点运算符即可拜访这些属性,就像这样:

rect = Rectangle(10, 20)
print(rect.width)
print(rect.height)
print(rect.area)

输入后果:

10
20
200

你也能够像上面这样设置属性的值:

rect.width = 5
rect.height = 10
print(rect.width)
print(rect.height)
print(rect.area)

输入后果如下所示:

5
10
50

在设置 width 或 height 属性的值时,会执行对应的 setter 办法进行类型检查和范畴查看。如果值不符合要求,将会抛出一个 ValueError 异样。这使得你的代码更加强壮和牢靠。

除了在属性的 getter 和 setter 办法中执行额定的操作外,你还能够应用 property 装璜器计算属性。计算属性是指,当你拜访属性时,它不是从类的实例中获取数据,而是基于类的其余数据成员进行计算。例如,如果你有一个示意温度的类,你能够定义一个计算属性,用于将摄氏度转换为华氏度,如下所示:

class Temperature:
    def __init__(self, celsius):
        self._celsius = celsius
    
    @property
    def celsius(self):
        return self._celsius
    
    @celsius.setter
    def celsius(self, value):
        self._celsius = value
    
    @property
    def fahrenheit(self):
        return (self._celsius * 9/5) + 32

在这个示例中,咱们定义了一个 Temperature 类,它蕴含一个 celsius 属性和一个 fahrenheit 属性。celsius 属性是一个一般的属性,能够间接拜访和设置。而 fahrenheit 属性是一个计算属性,它基于 celsius 属性计算而来。当你拜访 fahrenheit 属性时,它将主动计算出相应的华氏度并返回。你能够会对下面的代码有点纳闷 celsius.setter 是什么,他是那里来的,事实上在它下面的 @property 执行之后 celsius 曾经不再是一个函数了,而是一个 property 的类产生的对象了,因而 celsius.setter 是 property 类中的 setter 属性了,事实上他是一个类的办法了,而装璜器 @celsius.setter 就是将 def celsius(self, value) 这个函数作为参数传递给办法 celsius.setter

咱们介绍了 Python 中的 property 装璜器,它容许你将办法封装为属性,并在拜访或设置属性时执行额定的操作。通过应用 property 装璜器,你能够编写更加简洁、优雅和可读的代码,同时使代码更加强壮和牢靠。

property 的实质

property 是 python 内置的一个类,留神它是类。在后面的内容当中咱们曾经具体探讨过了装璜器的原理,并且从字节码的角度进行了剖析。因而咱们能够很容易了解下面 Temperature 类。咱们能够将装璜器开展:

class Temperature:
    def __init__(self, celsius):
        self._celsius = celsius

    def celsius1(self):
        return self._celsius

    celsius = property(celsius1)

    def celsius2(self, value):
        self._celsius = value

    celsius = celsius.setter(celsius2)

    def fahrenheit(self):
        return (self._celsius * 9 / 5) + 32

    fahrenheit = property(fahrenheit)


if __name__ == '__main__':
    t = Temperature(10)
    print(t.celsius)
    t.celsius = 100
    print(t.celsius)
    print(t.fahrenheit)

下面的程序输入后果如下所示:

10
100
212.0

能够看到下面的程序正确的输入了后果,合乎咱们对与 property 的了解和应用。从下面的剖析咱们能够看到 property 实质就是一个 python 的类,因而我能够齐全本人实现一个和内置的 property 类雷同性能的类。

在 python 语言层面实现 property 机制

具体的实现代码如下所示:

class Property:
    "Emulate PyProperty_Type() in Objects/descrobject.c"

    def __init__(self, fget=None, fset=None, fdel=None, doc=None):
        self.fget = fget
        self.fset = fset
        self.fdel = fdel
        if doc is None and fget is not None:
            doc = fget.__doc__
        self.__doc__ = doc
        self._name = ''

    def __set_name__(self, owner, name):
        self._name = name

    def __get__(self, obj, objtype=None):
        if obj is None:
            return self
        if self.fget is None:
            raise AttributeError(f"property'{self._name}'has no getter")
        return self.fget(obj)

    def __set__(self, obj, value):
        if self.fset is None:
            raise AttributeError(f"property'{self._name}'has no setter")
        self.fset(obj, value)

    def __delete__(self, obj):
        if self.fdel is None:
            raise AttributeError(f"property'{self._name}'has no deleter")
        self.fdel(obj)

    def getter(self, fget):
        prop = type(self)(fget, self.fset, self.fdel, self.__doc__)
        prop._name = self._name
        return prop

    def setter(self, fset):
        prop = type(self)(self.fget, fset, self.fdel, self.__doc__)
        prop._name = self._name
        return prop

    def deleter(self, fdel):
        prop = type(self)(self.fget, self.fset, fdel, self.__doc__)
        prop._name = self._name
        return prop

当初对下面咱们本人实现的类对象进行应用测试:

class Rectangle:
    def __init__(self, width, height):
        self._width = width
        self._height = height

    @Property
    def width(self):
        return self._width

    @width.setter
    def width(self, value):
        if value <= 0:
            raise ValueError("Width must be positive")
        self._width = value

    @Property
    def height(self):
        return self._height

    @height.setter
    def height(self, value):
        if value <= 0:
            raise ValueError("Height must be positive")
        self._height = value

    @Property
    def area(self):
        return self._width * self._height


if __name__ == '__main__':
    rect = Rectangle(10, 20)
    print(rect.width)
    print(rect.height)
    print(rect.area)

    rect.width = 5
    rect.height = 10
    print(rect.width)
    print(rect.height)
    print(rect.area)

下面的程序输入后果如下所示:

10
20
200
5
10
50

能够看到正确的输入了后果。

当初咱们来好好剖析一下咱们在下面应用到的本人实现的 Property 类是如何被调用的,在后面的内容当中咱们曾经探讨过了,只有类属性才可能是形容器,咱们在应用 @Property 的时候是获取到对应的函数,更精确的说是取得对象的 get 函数,而后应用 @Property 的类当中的原来的函数就变成了 Property 对象了,前面就能够应用对象的 setter 办法了。

而后在应用 rect.width 或者 rect.height 办法的时候就活触发形容器的机制,rect 对象就会被传入到形容器的 __get__ 办法,而后在这个办法当中将传入的对象再传给之前失去的 fget 函数,就完满的实现了咱们想要的成果。

classmethod 和 staticmethod

在 Python 中,staticmethod 和 classmethod 是两个罕用的装璜器,它们别离用于定义静态方法和类办法。

staticmethod

staticmethod 是一个装璜器,它能够将一个函数定义为静态方法。静态方法与类实例无关,能够在不创立类实例的状况下间接调用,但它们依然能够通过类名拜访。

上面是一个简略的示例:

class MyClass:
    @staticmethod
    def my_static_method(x, y):
        return x + y

print(MyClass.my_static_method(1, 2))

在这个示例中,咱们定义了一个 MyClass 类,并应用 @staticmethod 装璜器将 my_static_method 办法定义为静态方法。而后咱们能够通过 MyClass.my_static_method(1, 2) 间接调用该办法,而不须要创立 MyClass 的实例。须要留神的是,静态方法没有对类或实例进行任何批改,因而它们通常用于一些独立的、无状态的函数,或者在类中定义的一些帮忙函数。

那么 staticmethod 是如何在语法层面实现的呢?这又离不开形容器了,在下面的代码当中咱们应用 staticmethod 装璜函数 my_static_method 而后在类 MyClass 当中会有一个类 staticmethod 的对象,且名字为 my_static_method。咱们须要留神到的是下面的过程用一行代码示意为 my_static_method = staticmethod(my_static_method),传入的 my_static_method 就是 my_static_method 函数,那么这就很简略了,当应用 my_static_method 的属性时候,咱们能够在形容器的函数 __get__ 当中间接返回传入的函数即可。

咱们本人实现的 StaticMethod 如下所示:

class StaticMethod:
    "Emulate PyStaticMethod_Type() in Objects/funcobject.c"

    def __init__(self, f):
        self.f = f
        f = functools.update_wrapper(self, f)

    def __get__(self, obj, objtype=None):
        return self.f

    def __call__(self, *args, **kwds):
        return self.f(*args, **kwds)

咱们应用下面本人实现的类:

class MyClass(object):

    @StaticMethod
    def demo():
        return "demo"


if __name__ == '__main__':
    a = MyClass()
    print(a.demo())

下面的程序会输入字符串 "demo"

classmethod

classmethod 是另一个装璜器,它能够将一个函数定义为类办法。类办法与静态方法相似,但它们接管的第一个参数是类对象而不是实例对象。类办法通常用于实现与类无关的操作,如工厂办法或构造函数。

上面是一个应用 classmethod 的示例:

class MyClass:
    num_instances = 0
    
    def __init__(self):
        MyClass.num_instances += 1
    
    @classmethod
    def get_num_instances(cls):
        return cls.num_instances

obj1 = MyClass()
obj2 = MyClass()
print(MyClass.get_num_instances())

在这个示例中,咱们定义了一个 MyClass 类,它蕴含一个类变量 num_instances 和一个构造函数。而后,咱们应用 @classmethod 装璜器将 get_num_instances 办法定义为类办法,并将 cls 参数用于拜访类变量 num_instances。

在创立 MyClass 的两个实例后,咱们调用 MyClass.get_num_instances() 来获取以后创立的实例数。因为咱们应用了类办法,所以能够间接通过类名调用该办法。

须要留神的是,类办法能够在类和实例之间共享,因为它们都能够拜访类变量。另外,类办法能够被子类继承和重写,因为它们接管的第一个参数是类对象,而不是固定的类名。

在大节中,咱们介绍了 Python 中的两种罕用装璜器,即 staticmethod 和 classmethod。staticmethod 用于定义与类实例无关的静态方法,而 classmethod 用于定义与类相干的操作,如工厂办法或构造函数。两种装璜器都能够通过类名进行拜访,但 classmethod 还能够被子类继承和重写,因为它们接管的第一个参数是类对象。

须要留神的是,staticmethod 和 classmethod 都能够被类或实例调用,但它们不同的是,classmethod 的第一个参数是类对象,而 staticmethod 没有这样的参数。因而,classmethod 能够拜访类变量,而 staticmethod 不能拜访类变量。

上面是一个更具体的比拟:

class MyClass:
    class_var = 'class_var'

    @staticmethod
    def static_method():
        print('This is a static method')
        
    @classmethod
    def class_method(cls):
        print('This is a class method')
        print(f'The class variable is: {cls.class_var}')

obj = MyClass()

# 静态方法能够被类或实例调用
MyClass.static_method()
obj.static_method()

# 类办法能够被类或实例调用,并且能够拜访类变量
MyClass.class_method()
obj.class_method()

在这个示例中,咱们定义了一个 MyClass 类,并别离定义了静态方法和类办法。在调用静态方法时,咱们能够应用类名或实例名进行调用,因为静态方法与类或实例无关。而在调用类办法时,咱们必须应用类名或实例名进行调用,并且类办法能够拜访类变量。总的来说,staticmethod 和 classmethod 是 Python 中两个十分有用的装璜器,它们能够帮忙咱们更好地组织和治理代码。须要依据理论状况来抉择应用哪种装璜器,以便实现最佳的代码设计和可维护性。

同样的情理咱们能够实现本人的 ClassMethod

class ClassMethod:
    "Emulate PyClassMethod_Type() in Objects/funcobject.c"

    def __init__(self, f):
        self.f = f
        functools.update_wrapper(self, f)

    def __get__(self, obj, cls=None):
        if cls is None:
            cls = type(obj)
        return MethodType(self.f, cls)

咱们对下面的代码进行测试:

class Myclass:

    @ClassMethod
    def demo(cls):
        return "demo"


if __name__ == '__main__':
    a = Myclass()
    print(a.demo())

下面的代码能够正确的输入字符串 "demo"

总结

在本篇文章当中次要给大家介绍了形容器的三个利用,认真介绍了这三个类的应用办法,并且具体介绍了如何应用 python 实现同样的成果,这对于咱们深刻了解 python 面向对象编程十分有帮忙,咱们能够了解很多黑科技的内容,对于整个类的语法有更加深刻的了解。


本篇文章是深刻了解 python 虚拟机系列文章之一,文章地址:https://github.com/Chang-LeHung/dive-into-cpython

更多精彩内容合集可拜访我的项目:https://github.com/Chang-LeHung/CSCore

关注公众号:一无是处的钻研僧,理解更多计算机(Java、Python、计算机系统根底、算法与数据结构)常识。

退出移动版