当前位置: 首页 > news >正文

8.变量和数据类型

目录

1. Python中的变量:名称和值

1.1 Python vs Java

2. 赋值

3. 数据类型

3.1 type()

3.2 鸭子类型

4. 作用域

4.1 局部作用域

4.2 全局作用域

5. 不可变的真相

6. 赋值传递

7. 集合和引用

7.1 浅拷贝

7.2 深拷贝

8. 隐式类型转换和显示类型转换

9. 关于匈牙利命名法的注意事项


1. Python中的变量:名称和值

很多编程语言都会有一个变量的术语。在Python中则是用name和value来代替variable(变量),

一个name指向一个value或者object。一个value是内存中一个特定数据实例,而name则是这个数据实例的一个别名或者昵称。变量则是指代这两者的组合:一个name指向一个value。

1.1 Python vs Java

我们可以通过Java和Python这两个语言来区分它们的区别,便于我们更容易理解。

java:

int a = 12;我们申请了一个盒子(系统内存),a 就是变量,a代表这个盒子,变量名a其实就是方便我们记忆,可以方便找到盒子。这个盒子只能存放int类型。

a = "this";当我们将字符串放到a的这个盒子时候,是会报错的,int类型的盒子是不可以存放字符串。这就是静态类型语言。

Python:

a = 12 ; 在Pyhton中,是不需要指定a的类型的,Python的变量像是一个贴纸,当a = 12时,a就贴在存放int的盒子上。

a = 'this'; 此时a贴在了str类型的盒子上。所以Python是动态类型语言。

所以我们就比较容易理解 name = value,其中name是没有类型的,但是value是有类型的。

2. 赋值

answer = 42insight = answer

这里insight名称并没有绑定到answer这个名称,而是绑定到answer指向的值。一个名称总是指向一个值。

再来看看下面例子中结果可能超出你的想象:

spam = 123456789maps = spamaggs = 123456789

上面代码,结果可能会有2种情况。

第一种情况如图:Python为eggs创建了一个新值,和spam指向的不是同一个盒子(值相等,但是地址不同)。

第二种情况:都指向同一个盒子,eggs复用了已有的值,即它们身份共享了。

执行赋值操作时,Python会在背后做自己的决定:是创建一个新值,还是绑定到一个已经存在的值。程序员是没有话语权的。

那么我们如何识别身份呢?

我们有几个常见的比较操作: == , is  ,id()

spam == maps ,比较的是名称对应的值是否相等。

is 用来比较名称的身份的。 print(spam is eggs),结果可能是true 可能是否false。取决与Python是否共享了身份。

id(),用来返回传递进来的任何东西的身份,是一个整数类型,可以看作是内存地址(内存地址派生出来的整数)。is 本质上就是用的id()来判断的。

3. 数据类型

在Python中是不需要给变量声明类型的。我们可以把任何类型的值给到某个名称。

名称是有作用域,但是没有类型;值有类型,但是没有作用域。

3.1 type()

用Python内置函数type()函数可以知道一个值的类型。

print(type(answer))

打印 

<class 'int'>

在某些情况下,我们可能需要去判断检查它的数据类型,可以配合 is 或者 isinstance()来使用。

if type(answer) is int:print("What's the question?")if isinstance(answer, int):print("What's the question?")

这种类型检查我们最好使用isinstance()函数,该函数考虑的子类和继承。

3.2 鸭子类型

鸭子类型来自某个俗语:如果它看起来像一直鸭子,走起路、叫起来像鸭子,那么它可能就是一只鸭子。

Python不关心值的数据类型,而关系值的数据类型的功能。如果熟悉面向对象编程,就会了解继承会如何迅速失控,那么鸭子类型的概念可能就是一股清流。如果一个类表现的正如期望那样,那么通常不需要关心它继承自什么。

举例说明

class Duck:def quack(self): print("嘎嘎")class Dog:def quack(self): print("汪汪模拟嘎嘎")def make_noise(animal):animal.quack()      # 只要对象有 quack 就能用make_noise(Duck())  # 嘎嘎
make_noise(Dog())   # 汪汪模拟嘎嘎

打印

嘎嘎
汪汪模拟嘎嘎

“有 quack 就是鸭,管它是不是真的鸭。”

4. 作用域

函数(包括lambda)和推导式都定义了自己的作用域,也是Python仅有的定义了作用域的结构

,其中定义的所有名称在作用域外都会被自动删除。严格来讲,模块和类没有自己的作用域,它们只有自己的命名空间。

4.1 局部作用域

def spam():message = "Spam"word = "spam"for _ in range(100):separator = ", "message = separator + wordmessage += separatormessage += "spam!"return message# print(message)  # NameError: name 'message' is not definedoutput = spam()
print(output)

4.2 全局作用域

名词定义在模块内部,可以认为这个名称拥有全局作用域。我们需要谨慎使用全局作用域,因为定义太多会让整个代码难以调试和维护。

定义的一个current_score 全局变量,同时在函数中score()又定义了一个current_score局部名称,导致全局名称被覆盖。如果我们想看到全局名称,可以声明一个global关键字。

high_score = 10def score():global high_scorenew_score = 465             # SCORING LOGIC HEREif new_score > high_score:print("New high score")high_score = new_scorescore()
print(high_score)  # prints 465

如果想在局部作用域中绑定一个全局变量名称时,必须先使用global关键字。如果只是访问一个全局名称指向的当前值,可以不需要global关键字。但是我们得养成这个习惯。

current_score = 0def score():print(current_score) #打印0score()
print(current_score) #打印0

如下图所示,没有使用global关键字,函数内部current_score就是一个局部变量。函数执行完,局部名称都会被销毁删除。只有全局名称current_score不会变,仍然绑定到0这个值。所以结果打印的是0而非465。

current_score = 0def score():new_score = 465current_score = new_scorescore()
print(current_score) #0

5. 不可变的真相

Python中的值分为不可变值和可变值,区别在于能否原地修改,这意味着它们可以在内存中能够改变。

不可变值:整数 int,浮点数float,字符串str,元组tuple。

尝试修改一个不可变值,会得到一个完全不同的值。

可变值是可以原地修改的,比如列表

temps = [87, 76, 79]
highs = temps
print(temps is highs)  # prints True
temps += [81]
print(temps is highs)  # prints True
print(highs)           # prints [87, 76, 79, 81]
print(temps) 

6. 赋值传递

经常有人问,在Python中使用的是值传递还是引用传递。

答案是两者都不是。准确说是赋值传递。值通过赋值被绑定到参数上的。

记住是赋值而不是复制。所以函数内外实质是共享了一个对象。当对象值是个不可变值,函数内形参值变化不会影响到函数外值。但是如果是可变值,函数内的任何修改都会会影响任何绑定这个值的名称。

7. 集合和引用

所有的集合(包括列表)都是用了一个巧妙的技巧,集合中的元素是引用。就像是名称被绑定到值一样,集合中的元素也是以相同的方式被绑定到值。这种绑定称为引用。

scores_team_1 = [100, 95, 120]
scores_team_2 = [45, 30, 10]
scores_team_3 = [200, 35, 190]scores = (scores_team_1, scores_team_2, scores_team_3)scores_team_1[0] = 300
print(scores[0])  # prints [300, 95, 120]scores[0][0] = 400
print(scores[0])  # prints [400, 95, 120]

如上代码所示,元组本身是不能直接修改的,但是元组中的元素是可以修改的,相当于可以间接修改它们的值。

scores_team_1[0] = 300 ,修改时这个变化会出现在元素的第一个元素中,因为这个元素只是一个可变值的别名。

scores_team_1 对应值修改,也会反映到不可变元素scores。

上面的例子问题可能很容易发现,但是如果代码分散到多个文件,问题就会变得棘手。在一个模块中修改一个名称的值,可能会导致另外一个完全不同的模块中的集合项被修改。

7.1 浅拷贝

有很多方法可以帮你将一个名称绑定到一个可变值的副本,而不将其绑定到原始值。其中最明确方法就是使用copy()函数。它属于浅拷贝。

import copya = [1,2,3]
b = copy.copy(a)
b[0] = 3
print(a)
print(b)

打印结果:

[1, 2, 3]
[3, 2, 3]

对于不可变值的列表,浅拷贝没有问题,但是可变值又包含可变值时,对这些值的更改可能以奇怪方式出现。

a = [1,2,[1,2]]
b = copy.copy(a)
b[2][0] = 3
print(a)
print(b)

打印如下,b的修改,会影响到a。对于可变值的拷贝,相当于仅拷贝了一个引用,仍然共享了同一个地址。

[1, 2, [3, 2]]
[1, 2, [3, 2]]

copy()发生的事情如下图

7.2 深拷贝

为了解决上面的问题,可以使用深拷贝来确保对象内部任何可变值也被复制。这样b对象的拷贝将创造a值的副本,以及a所引用的任何可变值的副本。

deepCopy()发生的事情如下图

8. 隐式类型转换和显示类型转换

变量名称没有类型,所以Python没有典型的类型转换需求。

Python会自动完成转换,例如将整数和浮点数相加。这种称为隐式类型转换。

print(42.5)   # coerces to a string
x = 5 + 1.5   # coerces to a float (6.5)
y = 5 + True  # coerces to an int (6)...and is also considered a bad idea

有些时候,可能需要通过代码进行显示类型转换,即用一个值来创建另一种类型的值。

在Python中,每种类型都是一个类的实例。所以想要创建的类型的类只需要一个类初始化器。

比如将包含数字的字符串转换为数值类型。

life_universe_everything = "42"answer = float(life_universe_everything)print(type(answer))
print(answer)

float类有一个该类有一个接受字符串作为参数的初始化器__init__()。

每个类都定义了自己的初始化器,对于float()而言,如果传递的字符串不能被解释为浮点数,会引发ValueError。

9. 关于匈牙利命名法的注意事项

我们可能习惯了使用java这样的静态类型语言,习惯了使用数据类型。所以当我们学习Python这样动态语言时,可能想着使用某种方式记住这个名称所绑定的值的类型。但是建议不要这样做!只有学会充分利用动态类型、弱绑定、鸭子类型,就可以在Python中如鱼得水。

def calculate_age(intBirthYear, intCurrentYear):intAge = intCurrentYear - intBirthYearreturn intAgedef calculate_third_age_year(intCurrentAge, intCurrentYear):floatThirdAge = intCurrentAge / 3floatCurrentYear = float(intCurrentYear)floatThirdAgeYear = floatCurrentYear - floatThirdAgeintThirdAgeYear = int(floatThirdAgeYear)return intThirdAgeYearstrBirthYear = "1985"    # get from user, assume data validation
intBirthYear = int(strBirthYear)strCurrentYear = "2010"  # get from system
intCurrentYear = int(strCurrentYear)intCurrentAge = calculate_age(intBirthYear, intCurrentYear)
intThirdAgeYear = calculate_third_age_year(intCurrentAge, intCurrentYear)
print(intThirdAgeYear)

上面代码很难阅读。如果能充分利用Python语言动态类型系统(且抑制将中间步骤存储在变量中的冲动),代码会变得更加紧凑。

def calculate_age(birth_year, current_year):return (current_year - birth_year)def calculate_third_age_year(current_age, current_year):return int(current_year - (current_age / 3))birth_year = "1985"    # get from user, assume data validation
birth_year = int(birth_year)current_year = "2010"  # get from system
current_year = int(current_year)current_age = calculate_age(birth_year, current_year)
third_age_year = calculate_third_age_year(current_age, current_year)
print(third_age_year)

http://www.dtcms.com/a/356065.html

相关文章:

  • 浏览器访问 ASP.NET Core wwwroot 目录下静态资源的底层实现
  • 多线程 线程池 并发
  • 机器视觉学习-day08-图像缩放
  • MBA/EMBA毕业论文写作总结
  • 第20章|轻松实现远程控制
  • NumPy 2.x 完全指南【三十二】通用函数(ufunc)之数学运算函数
  • 面试tips--JVM(1)--对象分配内存的方式TLAB
  • CTFshow系列——命令执行web61-68
  • C++之多态篇
  • 君正T31学习(四)- MT7682+VLC出图
  • 【python】python进阶——as关键字
  • 程序代码篇---类
  • SpringCloud Alibaba Nacos 注册中心/配置中心
  • SpringBoot 配置文件在运维开发中的应用
  • 基于springboot的商业店铺租赁系统
  • 在 Vue 前端(Vue2/Vue3 通用)载入 JSON 格式的动图
  • 校园文化活动管理系统设计与实现(代码+数据库+LW)
  • web前端知识——第一阶段
  • 【buildroot】【1. Buildroot版本与Linux内核调试对应关系】
  • 基于SpringBoot的旅游景点推荐系统【2026最新】
  • 域名所有权变更,需要重新备案吗
  • Day16_【机器学习分类】
  • 软磁材料与硬磁材料
  • MTK Linux DRM分析(十九)- KMS drm_framebuffer.c
  • LeetCode 141.环形链表
  • 软考中级【网络工程师】第6版教材 第4章 无线通信网 (上)
  • 8.28 JS移动端事件
  • HTTP 范围请求:为什么你的下载可以“断点续传”?
  • 现在购买PCIe 5.0 SSD是否是最好的时机?
  • 嵌入式学习笔记--LINUX系统编程阶段--DAY02系统编程