红茶的个人站点

  • 首页
  • 专栏
  • 开发工具
  • 其它
  • 隐私政策
Awalon
Talk is cheap,show me the code.
  1. 首页
  2. 编程语言
  3. Python
  4. 正文

Python学习笔记9:类

2021年4月1日 1282点热度 0人点赞 0条评论

因为《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可以使用函数装饰器classmethod和staticmethod实现类方法或静态方法,这会在之后接触到。

魔术方法

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 . "\n");
    }
}
$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的继承和多态这里不做深入讲解,在未来的某篇笔记中再做分析。

本系列文章的代码都存放在Github项目:python-learning-notes。

本作品采用 知识共享署名 4.0 国际许可协议 进行许可
标签: Python 类
最后更新:2021年6月8日

魔芋红茶

加一点PHP,加一点Go,加一点Python......

点赞
< 上一篇
下一篇 >

文章评论

取消回复

*

code

COPYRIGHT © 2021 icexmoon.cn. ALL RIGHTS RESERVED.
本网站由提供CDN加速/云存储服务

Theme Kratos Made By Seaton Jiang

宁ICP备2021001508号

宁公网安备64040202000141号