• Python学习笔记9:类


    Python学习笔记9:类

    因为《Head Frist Python》一书的内容设置,所以我这个系列笔记也在这时候才介绍Python中的类。

    本文内容和示例都基于笔者之前对Java和PHP运用的理解综合而成,和《Head First Python》一书关系不大,对原书内容感兴趣的强烈建议购买一本。

    基本概念

    在Python中使用类很简单,这里举一个最简单的例子:

    class Test():
        def __init__(self, a: int = 0):
            self.a = a
    
        def print(self) -> None:
            print(self.a)
    
    
    test = Test()
    test2 = Test(2)
    test.print()
    test2.print()
    

    输出

    0
    2

    与其它编程语言相比,Python的类定义有以下几个特点:

    1. 类内部的所有方法声明中第一个参数必须为self

      事实上命名也可以不是self,但self是Python约定的命名,最好不要使用其它。

    2. 魔术方法__init__是类的初始化方法,相当于构造函数。

    3. 对象的属性使用self.a的方式在初始化方法中声明并初始化。

    现在我们进一步讨论Python为何会有这些特点。

    在Python的类定义中,self的作用和C++中的对象指针或者Java中的对象引用很相似,但其额外承担了初始化对象属性的作用。

    至于为何Python需要在所有类的内部方法参数中加入self,我们可以用下面的方式验证:

    class Test():
        def __init__(self, a: int = 0):
            self.a = a
    
        def print(self) -> None:
            print(self.a)
    
    
    test = Test()
    test2 = Test(2)
    Test.print(test)
    Test.print(test2)
    

    输出

    0
    2

    我们仅仅修改了最后两行代码,使用类名调用print并传入相应的对象,输出结果与上面的相同。

    可以看到Python使用了类似类静态方法的方式来实现对象方法,而这种实现方式的前提是静态方法必须要接收一个对象引用,而这个对象引用就是方法定义参数self,所以从这个角度上说,self是必不可缺的。

    当然这种方式给编码会带来一些小困惑,虽然也能很快适应,但依然对Python为何这样设计很不解,毕竟这样做有连个缺陷:

    1. 类方法定义必须加入self参数,否则会报错,很多Python新手应该都遇到过。
    2. 无法实现类的静态方法。

    虽然这两个缺陷也不是不能克服,毕竟类静态方法更多的是在优化程序性能上的作用,强行使用对象方法也不是不行。

    • 此外类的方法也支持默认参数等,这些都是函数已有的特性,这里不再一一赘述。
    • 复习可以阅读Python学习笔记4:函数

    魔术方法

    Python的类都继承自object,而object本身定义了很多特殊方法:

    print(dir(object))
    

    输出:

    ['__class__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__']
    

    这些双下划线左右包裹方式命名的函数被称作魔术方法,通常都有特殊的用途,比如:

    • __dir__:使用内部函数dir()时候调用,收集对象的属性和方法等信息。
    • __eq__:使用==逻辑运算符时候调用。
    • __str__:使用str()时候调用。
    • __repr__:使用print()时候调用。

    通过重写这些魔术方法,我们可以改变类对象的一些行为,比如:

    class Test():
        def __init__(self, a: int = 0):
            self.a = a
    
        def print(self) -> None:
            print(self.a)
    
        def __repr__(self) -> str:
            return "this is a Test object,a:"+str(self.a)
    
    
    test = Test()
    test2 = Test(2)
    print(test)
    print(test2)
    

    输出

    this is a Test object,a:0
    this is a Test object,a:2

    如果你学过C++的话,肯定会觉得熟悉,因为这就是某种意义上的运算符重载。

    封装

    我们都知道面向对象(OOP)是一个宏大的概念,主要包括封装、继承和多态。这部分内容不仅难学,而且还需要在项目中长期打磨才能领略其中的真谛。

    这里之所以会在简单介绍完Python类的基本使用后介绍一点OOP概念,是因为Python的封装与其它流行语言有很大差别。

    我们先写一个PHP的常用类结构,再写一个类似的Python类来对比说明。

    <?php
    class Calculator
    {
        private $a = 0;
        private $b = 0;
    
        /**
         * 构造函数
         * @param int $a
         * @param int $b
         */
        public function __construct($a, $b)
        {
            $this->a = $a;
            $this->b = $b;
        }
    
        /**
         * 求和
         * @return int
         */
        public function add()
        {
            $this->beforeRun(__FUNCTION__);
            return $this->a + $this->b;
        }
    
        /**
         * 执行数学计算前输出说明
         * @param string $operateName
         * @return void
         */
        private function beforeRun($operateName)
        {
            print("calculator will operate " . $operateName . "
    ");
        }
    }
    $calculator = new Calculator(1, 2);
    print($calculator->add());
    

    对应的python代码我们可以这样写:

    class Calculator():
        def __init__(self, a, b):
            self.a = a
            self.b = b
    
        def add(self):
            self.beforeRun("add")
            return self.a+self.b
    
        def beforeRun(self, operateName):
            print("calculator will operate ", operateName)
    
    
    cal = Calculator(1, 2)
    result = cal.add()
    print(result)
    

    貌似没有任何问题,但是对OOP封装有所了解的就知道,两者的封装不同。

    我们知道,在创建类的时候,对于类内部的属性和方法,都应该遵循最小访问权限这一封装规则。

    所在在这个例子中,PHP中的属性均为private,方法beforeRun也是private,对于类外部是不可见的,这样做是对的,毕竟在这个例子中Calculator仅仅对外提供一个功能,即add()调用。

    而反观Python的代码,并不能通过访问修饰符private/protected/public来进行访问限定,这样的结果就是类的所有属性和方法对外部都是可见的,可以任意访问和修改。这会对OOP设计造成巨大破坏。

    可能你会觉得这一个例子也没啥影响,但对于有大型应用团队开发经验的人都知道,一旦你开放某个本应该是私有的方法为公有,那你就不能怪某一天翻代码发现别人调用了这个方法,而导致某些很严重的系统问题,而且还要花很大力气去重构解决。

    那Python能不能在没有访问限定符的情况下解决这个问题?答案是有的:

    class Calculator():
        def __init__(self, a, b):
            self.__a = a
            self.__b = b
    
        def add(self):
            self.__beforeRun("add")
            return self.__a+self.__b
    
        def __beforeRun(self, operateName):
            print("calculator will operate ", operateName)
    
    
    cal = Calculator(1, 2)
    result = cal.add()
    print(result)
    # print(cal.__a)
    # cal.__beforeRun("see")
    

    如上边的例子中显示的那样,我们可以用双下划线来标记private类型的属性和方法,而且在事实上的确也不能通过cal.__a的方式调用,起到了封装的作用。

    但需要说明的是,这种方式仅仅是看起来起到了封装的作用,事实上Python中对象的属性和方法是不存在访问限制的,你可以通过一些其它方式访问:

    print(cal._Calculator__a)
    

    就像上面这样,你可以通过特殊途径来访问到看似访问受限的对象属性,所以Python的这种私有声明也被叫做伪私有。

    但是这依然给我们提供了一种实现OOP封装的途径,我们要尽量杜绝上面那样通过“歪门邪道”来进行不正常的对象属性、方法访问,那样会破坏OOP的封装原则,一旦我们有类似的需要,第一时间应该去重构类设计。

    顺带一提,双下划线类似于private,而单下划线类似于protected,不过对于OOP的继承和多态这里不做深入讲解,在未来的某篇笔记中再做分析。

    本篇文章首发自魔芋红茶的博客https://www.cnblogs.com/Moon-Face/ 请尊重其他人的劳动成功,转载请注明。
  • 相关阅读:
    点击文本变成输入框
    html代码片段
    node 开启Gzip压缩
    npm 安装与卸载
    console.dir()-----js中console.log()和console.dir()的区别
    javaScript学习笔记之-------this
    javaScript学习笔记之-------闭包
    从零搭建vue项目---VUE从无到有
    require.js扫盲版
    cross-env 解决跨平台设置的NODE_ENV的问题
  • 原文地址:https://www.cnblogs.com/Moon-Face/p/14549816.html
Copyright © 2020-2023  润新知