Python 数据分析基础入门:《Excel Python:飞速搞定数据分析与处理》学习笔记系列(附录 C 高级 Python 概念)

Excel Python:飞速搞定数据分析与处理

附录 C 高级 Python 概念

本附录会更细致地研究以下 3 个主题:类和对象、带时区的 datetime 对象,以及可变与不可变对象。这些主题相互独立,可以以任意顺序阅读。

C.1 类和对象

本节我们会编写自己的类,以便更好地理解类和对象之间的关系。类定义了一类新的对象:一个类就像是你用来烤蛋糕(比如巧克力蛋糕或者起司蛋糕)的模具用模具 (类)制作蛋糕(对象)的过程就叫作实例化 。这就是为什么对象也被称为类实例(class instance)。无论是巧克力蛋糕还是起司蛋糕,它们都是一类(type)蛋糕:类(class)可 以让你定义新的数据类型,这些类型将数据(属性)和函数(方法)放在了一起,因而可以帮助你架构和组织代码。现在回到第 3 章中赛车游戏的例子,定义我们自己的类:

复制代码
In [1]: class Car: 
            def __init__(self, color, speed=0): 
                self.color = color 
                self.speed = speed 
                
            def accelerate(self, mph): 
                self.speed += mph

这是一个简单的汽车类,其中包含了两个方法 。方法是在类定义中定义的函数 ,该类有一个叫作 accelerate 的普通方法。该方法会更改类实例的数据(speed)。它还有一个叫作__init__的特殊方法,方法名首尾有两个下划线。当对象被实例化之后,Python 会调用这个方法为对象附加一些初始数据。每个方法的第一个参数表示的是类实例,依照惯例会被命名为 self。在看到如何使用 Car 类之后,你就会明白其中的道理。我们首先来实例化两辆汽车。这个过程和调用函数是一样的:在类名后面加上圆括号以调用类,同时提供__init__方法的参数。你不需要为 self 提供实参,因为 Python 会负责这项工作。在本例中,self 分别是 ZX 和 YAMAHA:

复制代码
In [2]: # 来实例化两个汽车对象 
        ZX = Car("red") 
        YAMAHA = Car(color="blue")

当你调用一个类时,实际上调用的是__init__函数,这就是为什么所有对于函数参数有效的东西也可以应用到这里:对于 ZX,我们以位置参数形式提供实参,而对于 YAMAHA,我们使用的是关键字参数形式。从 Car 类实例化两个机车对象之后,来看一下它们的属性并调用其方法。我们会看到,在加速 ZX 之后,ZX 的速度会发生变化,YAMAHA 则保持不变, 原因是两个对象是相互独立的:

复制代码
In [3]: # 在默认情况下会打印出对象的内存位置 
        ZX 
Out[3]: <__main__.Car at 0x196e7d90550> 
In [4]: # 通过属性可以访问对象的数据 
        ZX.color 
Out[4]: 'red' 
In [5]: ZX.speed 
Out[5]: 0  
In [6]: # 在 ZX 上调用 accelerate 方法 
        ZX.accelerate(20) 
In [7]: # ZX 的 speed 属性发生了改变 
        ZX.speed 
Out[7]: 20  
In [8]: # YAMAHA 的 speed 属性保持不变 
        YAMAHA.speed 
Out[8]: 0

Python 也允许直接修改属性而无须使用方法:

复制代码
In [9]: ZX.color = "green" 
In [10]: ZX.color 
Out[10]: 'green' 
In [11]: YAMAHA.color  # 不变 
Out[11]: 'blue'

总结一下类定义了对象的属性和方法。类将函数("方法")和数据("属性")组合到一起,从而使你可以方便地利用点语法访问:myobject.attribute 或 myobject.method()。

C.2 使用带时区的 datetime 对象

在第 3 章简单介绍过不带时区的 datetime 对象。如果需要考虑时区,那么你通常都会在 UTC 时区下进行工作,只有在显示时间时才将其转换为当地时区。在使用 Excel 和 Python 时,你可能想要将 Excel 产生的不带时区的时间戳转换为带时区的 datetime 对象。对于 Python 中的时区支持,你可以使用 dateutil 包,虽然它不是标准库的一部分,但是已经在 Anaconda 中预装。下面的示例展示了在处理 datetime 和时区时的一些常见操作。

复制代码
In [12]: import datetime as dt 
         from dateutil import tz 
In [13]: # 不带时区的 datetime 对象 
         timestamp = dt.datetime(2020, 1, 31, 14, 30) 
         timestamp.isoformat() 
Out[13]: '2020-01-31T14:30:00' 
In [14]: # 带时区的 datetime 对象 
         timestamp_eastern = dt.datetime(2020, 1, 31, 14, 30, 
                                         tzinfo=tz.gettz("US/Eastern")) 
         # 以isoformat格式打印可以更清楚地看出和UTC的差距 
         timestamp_eastern.isoformat() 
Out[14]: '2020-01-31T14:30:00-05:00' 
In [15]: # 为不带时区的datetime对象赋予时区 
         timestamp_eastern = timestamp.replace(tzinfo=tz.gettz("US/Eastern")) 
         timestamp_eastern.isoformat() 
Out[15]: '2020-01-31T14:30:00-05:00' 
In [16]: # 转换时区 
         # 由于 UTC 时区很常用,因此可以简写为 tz.UTC 
         timestamp_utc = timestamp_eastern.astimezone(tz.UTC) 
         timestamp_utc.isoformat() 
Out[16]: '2020-01-31T19:30:00+00:00' 
In [17]: # 带时区转换为不带时区 
         timestamp_eastern.replace(tzinfo=None) 
Out[17]: datetime.datetime(2020, 1, 31, 14, 30) 
In [18]: # 不带时区的当前时间 
         dt.datetime.now() 
Out[18]: datetime.datetime(2021, 1, 3, 11, 18, 37, 172170) 
In [19]: # UTC时区中的当前时间 
         dt.datetime.now(tz.UTC) 
Out[19]: datetime.datetime(2021, 1, 3, 10, 18, 37, 176299, tzinfo=tzutc())

提示:Python 3.9 中的时区处理

Python 3.9 通过 timezone 模块为标准库添加了对时区的完全支持。可以用它来代替 dateutil 的 tz.gettz 调用。

复制代码
from zoneinfo import ZoneInfo 
timestamp_eastern = dt.datetime(2020, 1, 31, 14, 30, 
                                tzinfo=ZoneInfo("US/Eastern"))

C.3 可变和不可变的 Python 对象

在 Python 中,可以修改其值的对象称为可变的(mutable),而不能修改的就称为不可变的 (immutable)。表 C-1 展示了各个数据类型属于哪一类。

了解两者之间的差别是很重要的,因为可变对象可能和你在其他语言(包括 VBA)中习以为常的东西有不一样的行为。来看看下面这段 VBA 代码:

复制代码
Dim a As Variant, b As Variant  
a = Array(1, 2, 3) 
b = a 
a(1) = 22  
Debug.Print a(0) & ", " & a(1) & ", " & a(2) 
Debug.Print b(0) & ", " & b(1) & ", " & b(2)

打印的结果如下:

复制代码
1, 22, 3  
1, 2, 3

现在在 Python 中用列表完成同样的操作:

复制代码
In [20]: a = [1, 2, 3]  
         b= a  
         a[1] = 22 
         print(a) 
         print(b) 
[1, 22, 3] 
[1, 22, 3]

在 Python 中,变量是你 "赋予" 对象的名称。b = a 将两个名称赋予了同一个对象,即 list[1, 2, 3]。因此所有指向该对象的变量都会体现出列表的变化。不过这只对可变对象有用:如果你将列表替换成不可变对象,比如元组,那么修改 a 并不会影响 b。如果想让可变对象 b 不受 a 的改变的影响,则必须显式地复制列表:

复制代码
In [21]: a = [1, 2, 3] 
         b = a.copy() 
In [22]: a 
Out[22]: [1, 2, 3] 
In [23]: b 
Out[23]: [1, 2, 3] 
In [24]: a[1] = 22  # 修改a...... 
In [25]: a 
Out[25]: [1, 22, 3] 
In [26]: b  # ......不影响b 
Out[26]: [1, 2, 3]

列表的 copy 方法创建的是一份浅复制 (shallow copy):你确实会得到一份列表的副本, 但是如果列表中包含可变 元素,那么这些元素仍然是共享的。如果你想递归复制所有的元素,则需要利用标准库中的 copy 模块来进行深复制(deep copy):

复制代码
In [27]: import copy 
         b = copy.deepcopy(a)

C.3.1 以可变对象为参数调用函数

如果你是从 VBA 转到 Python 的,那么可能已经习惯于将函数参数标记为按引用传递 (ByRef)或按值传递(ByVal):当你将一个变量作为参数传递给函数时,函数要么拥有改变这个变量的能力(ByRef),要么就是在处理值的副本(ByVal),因此原变量不会发生变化。VBA 中默认按引用传递参数(ByRef)。考虑如下 VBA 函数:

复制代码
Function increment(ByRef x As Integer) As Integer  
    x = x+ 1  
    increment = x 
End Function

然后像下面这样调用这个函数:

复制代码
Sub call_increment() 
    Dim a As Integer 
    a = 1    
    Debug.Print increment(a)  
    Debug.Print a  
End Sub

上述代码会打印如下内容:

复制代码
2
2

然而,如果你将 increment 函数中的 ByRef 改成 ByVal,则会打印出如下内容:

复制代码
2
1

在 Python 中又是怎样呢?当你把变量四处传递时,实际上传递 的是指向对象的名称 。也就是说,具体行为取决于对象是可变的还是不可变的。先使用一个不可变对象来进行测试:

复制代码
In [28]: def increment(x):  
             x = x+ 1  
             return x  
In [29]: a = 1 
         print(increment(a)) 
         print(a)  
2  
1

然后使用可变对象重复上面的例子:

复制代码
In [28]: def increment1(x):  
             x[0] = x[0] + 1  
             return x  
In [29]: a = [1] 
         print(increment1(a)) 
         print(a)  
[2]  
[2]

如果对象是可变的,而你想要原对象保持不变,那么就需要传递对象的副本

复制代码
In [32]: a = [1] 
         print(increment1(a.copy())) 
         print(a)  
[2] 
[1]

还有一种情况值得注意,那就是定义函数时默认参数中对可变对象的使用。下面来看看为什么值得注意。

C.3.2 使用可变对象作为默认参数的函数

在编写函数时,一般来说不应该使用可变对象作为默认参数 。这是因为默认参数的值是函数定义的一部分,它只会被求值一次,而不会在每次调用函数时求值。因此,使用可变对象作为默认参数会导致出人意料的行为:

复制代码
In [33]: # 不要这么做: 
         def add_one(x=[]):  		# 这里的 x=[] 是赋值给了一个列表,而列表是可变量。
             x.append(1) 
             return x 
In [34]: add_one() 
Out[34]: [1] 
In [35]: add_one() 
Out[35]: [1, 1]

如果你想将空列表作为默认参数,则应该像下面这样做。

复制代码
In [36]: def add_one1(x=None): 
             if x is None: 
                 x = [] 
             x.append(1) 
             return x 
In [37]: add_one1() 
Out[37]: [1] 
In [38]: add_one1() 
Out[38]: [1]
相关推荐
隔壁大炮1 小时前
MNE-Python 第2天学习笔记:Montage与通道信息管理
python·eeg·mne·脑电数据处理
涛声依旧-底层原理研究所1 小时前
防止Agent胡来五大安全防线
人工智能·python
RSTJ_16251 小时前
PYTHON+AI LLM DAY FIFITY-THREE
开发语言·人工智能·python
晚烛1 小时前
CANN 模型蒸馏实战:大模型知识迁移到小模型
python·线性代数·矩阵
俊哥工具1 小时前
解决网速卡顿、断网、网络报错,万能网络修复工具教程
网络·python·django·计算机外设·智能路由器·pygame
WL_Aurora1 小时前
Python爬虫实战(九):百度百聘招聘数据采集
爬虫·python·百度
lili00121 小时前
Gemini 3.5发布后的AI格局:谷歌重新定义行业标准
java·人工智能·python·ai编程
JunLa1 小时前
Java语法糖
java·python·哈希算法
财经资讯数据_灵砚智能1 小时前
基于全球经济类多源新闻的NLP情感分析与数据可视化(夜间-次晨)2026年5月21日
大数据·人工智能·python·信息可视化·自然语言处理