从 JavaScript 角度学 Python(5) - 函式

前言

前一篇聊了关於型别与变数的部分,所以这一篇当然就闪不了要聊聊关於函式,函式对於许多新手来讲是一个很痛的痛点,所以这一篇我也会分享一些什麽时候该用函式的简单概念与如何练习函式。

生活化函式

接着来聊聊函式的部分,虽然这一篇是想要从 JavaScript 角度学 Python,但是我之前写过一篇文章叫做「有点长的浅谈 JavaScript function 函式」,那麽为了避免过度深入探讨 JavaScript 的部份因此如果有兴趣的人可以考虑阅读上述那一篇,而後面就会尽量着重於 Python 的部分哩。

那麽满多人对於函式的概念没有那麽清楚也不太清楚什麽时候要使用函式,所以我这边简单的描述与举例情境函式使用的时机,也就是...

「重复性行为非常高的时候,你就可以包装成函式。」

没错,就是这麽 Easy!这就是函式一开始在学习精髓。

https://ithelp.ithome.com.tw/upload/images/20210905/201194866UjLyklajp.png

好啦,我知道这时候你应该会说什麽叫重复性高的行为?我举例一个简单情境来讲好了。

假设我们一周有五天,而第一天到第五天,绝对会有以下这些相同的生活行为:

  • 起床
  • 刷牙
  • 运动
  • 中午吃饭
  • 午睡
  • 下午运动
  • 晚上吃饭
  • 晚上洗澡
  • 睡觉

那麽如果将上面行为转换成五天的程序码就会类似这样:

(下面先以我们熟悉的 JavaScript 角度撰写一次)

console.log('第一天');
console.log('起床');
console.log('刷牙');
console.log('运动');
console.log('中午吃饭');
console.log('午睡');
console.log('下午运动');
console.log('晚上吃饭');
console.log('晚上洗澡');
console.log('睡觉');
console.log('---------');
console.log('第二天');
console.log('起床');
console.log('刷牙');
console.log('运动');
console.log('中午吃饭');
console.log('午睡');
console.log('下午运动');
console.log('晚上吃饭');
console.log('晚上洗澡');
console.log('睡觉');
console.log('---------');
console.log('第三天');
console.log('起床');
console.log('刷牙');
console.log('运动');
console.log('中午吃饭');
console.log('午睡');
console.log('下午运动');
console.log('晚上吃饭');
console.log('晚上洗澡');
console.log('睡觉');
console.log('---------');
console.log('第四天');
console.log('起床');
console.log('刷牙');
console.log('运动');
console.log('中午吃饭');
console.log('午睡');
console.log('下午运动');
console.log('晚上吃饭');
console.log('晚上洗澡');
console.log('睡觉');
console.log('---------');
console.log('第五天');
console.log('起床');
console.log('刷牙');
console.log('运动');
console.log('中午吃饭');
console.log('午睡');
console.log('下午运动');
console.log('晚上吃饭');
console.log('晚上洗澡');
console.log('睡觉');

你可能会想说:「哈哈,我先打好一次复制贴上五次就好了,我还傻傻打五次?」

这确实也一种解法没错,但是相对程序码会很拢长且不好维护,你自己也可以试着设想看看如果你未来哪一天突然开始更改自己的生活习惯呢?是不是就变成五天都要修改了呢?

因此如果善加利用函式来包装的话,你就可以将重复性的行为减少撰写,而着重於函式呼叫即可:

function fn() {
  console.log('起床');
  console.log('刷牙');
  console.log('运动');
  console.log('中午吃饭');
  console.log('午睡');
  console.log('下午运动');
  console.log('晚上吃饭');
  console.log('晚上洗澡');
  console.log('睡觉');
  console.log('---------');
}
console.log('第一天');
fn();
console.log('第二天');
fn();
console.log('第三天');
fn();
console.log('第四天');
fn()
console.log('第五天');
fn();

上面的范例中你可以发现我只需要透过呼叫 fn() 就可以直接达到相同的行为,除此之外如果我跟你说今天这个重复行为要从 1~100 天呢?甚至是 365 天?那你不就要复制贴上到死掉了?而且善加利用函式将重复性的行为包装起来还有另一个好处,当你要维护修改时,就可以统一全部修改,假设其中某一天我更改了我的生活模式 console.log('午睡'); 变成了 console.log('读书');,那麽你就可以不用取代到死。

https://ithelp.ithome.com.tw/upload/images/20210905/20119486NmUavtks39.png

当然上面这一段程序码我们也可以加上回圈的方式连 第一天第二天 等等都给包装起来,这样子做的话你想要跑几天都没有问题:

function fn(day) {
  console.log('第'+ day +'天');
  console.log('起床');
  console.log('刷牙');
  console.log('运动');
  console.log('中午吃饭');
  console.log('午睡');
  console.log('下午运动');
  console.log('晚上吃饭');
  console.log('晚上洗澡');
  console.log('睡觉');
  console.log('---------');
}

var day = 5;

for(var i = 0; i < day; i+=1) {
  fn(i + 1);
}

上面这些程序码的结果我就不呈现了,如果对於函式的使用时机还是没有那麽清楚的话,你也可以试着将自己生活上的重复性行为列出来,然後再试着转换成程序码,试着思考一下怎麽包装成函式。

Python 的 def 定义

那麽让我们回到 Python 的部分,在前面我们知道 JavaScript 宣告一个函式是使用 function 这个关键字,而 Python 呢?Python 则是使用 def,而 def 中文是「定义」某个东西的意思,所以让我们直接将上方的 JavaScript 函式改写成 Python 的试试看:

def fn(day):
  print('第'+ day +'天')
  print('起床')
  print('刷牙')
  print('运动')
  print('中午吃饭')
  print('午睡')
  print('下午运动')
  print('晚上吃饭')
  print('晚上洗澡')
  print('睡觉')
  print('---------')

fn(1)

在宣告 def 的时候,请务必注意 def 名称的後方要记得补上一个「:」号,否则是会出现错误的,最主要原因是因为 Python 的花括号 {} 只会在字典的时候用到(後面章节会在说明什麽是字典,这边先知道这是什麽就好)。

另外上面这一段程序码基本上是会出现错误的,而错误讯息主要是 TypeError: can only concatenate str (not "int") to str,这是什麽意思呢?简单来讲就是我们即将做字串组合的那一段程序码所传入的型别是错误的 (print('第'+ day +'天')),此时此刻的你应该会是你表情包的第一个表情:

https://ithelp.ithome.com.tw/upload/images/20210905/20119486DJ6JjordBj.png

毕竟我们在写 JavaScript 字串组合的时候,通常都是这样子组合 console.log('第'+ day +'天'); 字串的(当然还有样板字面值啦)。

但是在 Python 上这样做是会发生型别错误的,它这一段错误讯息的意思简单来讲就是整数型别 (int) 无法与字串型别 (string) 相加,所以我们可以知道一件事情 JavaScript 在做字串组合的时候是会帮我们隐含转型的:

var a = 1;
console.log('第'+ a +'天');

意想不到吧?我也想不到,我写一篇范例的时候才知道的。

因此在 Python 中会出现 TypeError: can only concatenate str (not "int") to str 的错误原因最主要是因为我们尝试将字串型别 (string) 与整数型别 (int) 相加 (print('第'+ day +'天')) 所导致,正常来讲字串只能跟字串相加,因此这一段如果要解决的话,你就必须先做型别转换:

def fn(day):
  print('第'+ str(day) +'天')
  print('起床')
  print('刷牙')
  print('运动')
  print('中午吃饭')
  print('午睡')
  print('下午运动')
  print('晚上吃饭')
  print('晚上洗澡')
  print('睡觉')
  print('---------')

fn(1)

至於回圈该怎麽写,这边先不介绍,我们保留到下一次再介绍(否则这一篇会太长)。

除此之外这边要注意 Python 的型别转换都是使用全小写,底下也列给各位看一下:

  • str()
  • int()
  • bool()
  • float()

与 JavaScript 型别转换首字大写是不同的:

  • String()
  • Number()
  • Boolean()

Python 与 JavaScript 的函式差异

接下来聊聊 Python 的函式与 JavaScript 函式有哪些差异之处,JavaScript 的函式有几个常见的特性:

  • 可以使用变数储存函式 (匿名表达式)
  • 可以在函式陈述式建立之前呼叫 (Hoisting 与 具名函式陈述式)
  • IIFE (立即执行函式)

下面我先举例一些常见的 JavaScript 函式陈述式、函式表达式与 IIFE 的撰写方式:

// 第一种
fn1();
function fn1() {
  console.log('Hello Ray');
}

// 第二种
const fn2 = function() {
  console.log('Hello Ray');
}
fn2();

// 第三种
(function() {
  console.log('Hello Ray');
})();

首先先讲讲第一种函式宣告的方式也就是在函式建立之前呼叫函式,基本上 JavaScript 是允许你在函式建立之前呼叫函式(通常不建议这样做),那...

Python 呢?Python 一样也可以这麽做吗?答案是不行的:

fn1()

def fn1():
  print('Hello Ray')

上面这一段 Python 程序码是会出现 NameError: name 'fn1' is not defined 的错误讯息,因此在一次验证 JavaScript 的提升 (Hoisting) 美好(误)

接下来要直接跳到第二种匿名表达式与 IIFE 的写法,但在 Python 会比较特别,所以让我们直接跳到下一个章节。

lambda

接下来是关於直接将函式储存到变数内,这边在 Python 中如果要做到这个行为就必须使用一个运算子叫做 lambda,而 lambda 就是所谓的匿名函式,它的特性顾名思义也就是:

  • 不用宣告名称
  • 但只能有一行程序码,若换行则会出错

所以 lambda 该怎麽用呢?让我们直接来看看范例程序码:

myName = lambda name: print('Hello ' + name)
myName('Ray') # Hello Ray

是不是觉得短的不像话?喜好简洁的你感受到快乐了呢?

https://ithelp.ithome.com.tw/upload/images/20210905/20119486ejI8FdYNnK.png

所以这边先聊简单聊一下 Lambda 函式,由於 Lambda 函式只能撰写一行(意指你换行就会喷错),所以基本上还是会有一些小限制,可是却非常精简且强大,而 Lambda 包含了三个重点部分:

  • Lambda - 关键字
  • 参数列表 - parameter
  • 表达式 - expression
lambda parameter1, parameter2,...: expression

(如果你对於表达式与陈述式不熟悉的话,可以详见 此篇 文章。)

假设你硬是把 lambda 改成多行的话,是会出现错误的:

# SyntaxError: invalid syntax
myName = lambda name:
  print('Hello ' + name)

那麽 JavaScript 中的 IIFE 呢?第三个 IIFE (立即执行函式)也会与 Lambda 有关系,废话就不多说了,直接来看范例程序码:

(lambda name: print('Hello ' + name))('Ray') # Hello Ray

看到现在是不是有一种与 JavaScript 的 IIFE 似曾相见的感觉呢?

https://ithelp.ithome.com.tw/upload/images/20210905/20119486wJgVBrbYOY.png

最後想提醒一下,虽然 lambda 很强大很好用,但是却也要小心使用,因为它非常简洁的关系且强制只能写一行,那麽在程序码可读性上可能会不好阅读唷。

参考文献

作者的话

因为买了一大罐花雕酒,所以这阵子一直在疯狂做花雕醉鸡(鸡肉我是买好市多的清鸡肉),尝试了普通版本的花雕醉鸡,第一次试做整个大失败,有点太咸花雕酒加太多。

关於兔兔们

兔法无边


<<:  为什麽也需要有 CLASSPATH 呢?

>>:  DAY1 筑个前端毛胚屋

14. Log X Notification x Slack

好想被推播啊 身为一个负责的工程师,当系统有错误的时候,总是想收到即时推播讯息该怎麽做? 上一篇有提...

[SQL]ISNULL()函式对於资料型态的隐性规则

ISNULL()函式对於资料型态的隐性规则 情境 想在Sql server要用 特定学生姓名 去查阅...

【Day 06 】- Module 隐藏大法,不可能再被发现了吧 / _ \(基於 VAD 断链的隐藏方法)

Agenda 资安宣言 测试环境与工具 学习目标 技术原理与程序码 References 下期预告 ...

DAY 26 『 AVPlayerViewController - 播放影片 』

今天要分享的是,如何用 AVPlayerViewController 显示影片 成品: {%yout...

Day 10 - SELECT INTO !

今天来认识一下SELECT INTO吧!SELECT INTO用来从某资料表查询所得之资料集结果新增...