1.局部作用域修改全局变量
## 如何用一个变量来记录一个函数调用的次数
count = 0
def hello(name):
print('hello', name)
count += 1 # 等同于 count = count + 1 # 报错,如何让此语句能改变全局变量而不是创建局部变量
会报错的没有赋予count初值
hello('小张')
hello('小李')
print('您共调用hello函数', count, '次') # 2 次
nonlocal
-
作用
告诉python 的解释执行器, global 语句声明的一个或多个变量, 这些变量是全局变量
-
语法
-
# 如何用一个变量来记录一个函数调用的次数 count = 0 def hello(name): global count # 声明 global 是全局变量 print('hello', name) count += 1 # 等同于 count = count + 1 hello('小张') hello('小李') hello('小魏') print('您共调用hello函数', count, '次') # 3 次
-
global 说明
-
全局变量如果要在函数内部被赋值,则必须经过全局声明 global
-
默认全局变量在函数内部可以使用,但只能取值,不能赋值
-
不能先声明为局部变量,再用 global 声明为全局变量,此做法不符合语法规则
-
-
函数的形参已经是局部变量,不能用 global 声明为全局变量
-
a = 100 b = 200 def fa(a): b = 20 # SyntaxError: name 'b' is assigned to before global declaration global b b = 222
1.1 局部作用域修改外部变量
前提:必须是嵌套函数
说明:
局部作用域中若要修改外部函数嵌套作用域中的变量需要使用:nonlocal 语句
-
格式:nonlocal 变量名
-
作用:将局部作用域中变量声明为外部函数嵌套作用域中的变量。
-
def func01(): a = 10 def func02(): # 内部函数,可以访问外部嵌套变量 # print(a) # 内部函数,如果修改外部嵌套变量,需要使用nonlocal语句声明 nonlocal a a = 20 func02() print(a) func01()
创建一个计数器函数
-
def count(): count = 0 def count2(): nonlocal count count += 1 return count return count2 count = count() print(count()) print(count())
简单累加器:定义一个函数,每次调用它时都会累加传入的数值,并返回累加的总和。
-
total = 0 def add(x): global total total = total + x return total print(add(5)) print(add(10))
2. 函数的内存分配
1、将函数的代码存储到代码区,函数体中的代码不执行。
2、调用函数时,在内存中开辟空间(栈帧),存储函数内部定义的变量。
3、函数调用后,栈帧立即被释放。
-
-
不可变类型的数据传参时,函数内部不会改变原数据的值。
-
可变类型的数据传参时,函数内部可以改变原数据。
内存分配:
1.初始化变量:
a=10:首先在堆中创建变量10,并将引用地址赋值给变量a
b = [100]:在堆中创建数组对象,将引用地址赋值给b
func(a, b):函数调用,创建一个栈帧,其中包含局部变量、参数、返回地址等信息,并将栈帧压入调用栈。调用栈是一个后进先出(LIFO)的数据结构,用于管理程序中的函数调用关系。参数a和b引用初始化的变量a和b的地址。
func(a, b)内部:a、b是函数func的局部变量,存在于栈帧中,a=20会在堆中新创建一个对象20,并将引用地址重新赋给a;b[0] = 20则将20的引用地址赋值给数组b中0下标对应的元素,但b的引用地址没变。当函数调用结束后,调用栈将栈帧从栈顶弹出,局部变量a、b也会被销毁。
print(a):变量a的值仍然是10,函数内部的操作不会影响它,是不可变对象。
print(b):变量b的引用地址仍然是数组的地址,但是数组中的元素被改变了,所以b通过引用地址打印数组时数组的内容发生了改变,是可变对象。
3. 函数自调用(递归)
3.1 递归的定义
在定义一个过程或函数时出现调用本过程或本函数的成分,称之为递归。
若调用自身,称之为直接递归。
若过程或函数A调用过程或函数B,而B又调用A,称之为间接递归。
在算法设计中,任何间接递归算法都可以转换为直接递归算法来实现,所以主要讨论直接递归。
-
递归算法通常通常把一个大的复杂问题层层转化为一个或多个与原问题相似的规模较小的问题来求解。
-
递归策略只需少量的代码就可以描述出解题过程所需要的多次重复计算,大大减少了算法的代码量。
解决递归问题满足的三个条件:
-
需要解决的问题可以转化为一个或多个子问题来求解,而这些子问题的求解方法与原问题完全相同,只是在数量规模上不同。
-
递归调用的次数必须是有限的。
-
必须有结束递归的条件来终止递归。
3.2 递归的使用
找出函数的等价关系式,我们要不断缩小参数的范围,缩小之后,我们可以通过一些辅助的变量或者操作,使原函数的结果不变。
比如,f(n) 这个范围比较大,我们可以让 f(n) = n * f(n-1)。这样,范围就由 n 变成了 n-1 了,范围变小了,并且为了原函数f(n) 不变,我们需要让 f(n-1) 乘以 n。
说白了,就是要找到原函数的一个等价关系式,f(n) 的等价关系式为 n * f(n-1),即
f(n) = n * f(n-1)。
举个例子,还是从阶乘来出发
假设我们用递归来算阶乘 f(n)
def fun(n):
if n == 1:
return 1
return n * fun(n - 1)
print(fun(2))
print(fun(3))
2
6
说明:
-
递归一定要控制递归的层数,当符合某一条件时要终止递归调用
-
几乎所有的递归都能用while循环来代替
递归的实现方法:
-
先假设此函数已经实现
递归优缺点:
-
优点:
-
递归可以把问题简单化,让思路更为清晰,代码更简洁
-
-
缺点:
-
递归因系统环境影响大,当递归深度太大时,可能会得到不可预知的结果
-
-
练习
-
斐波那契数列(Fibonacci sequence)是一种经典的数列,它的特点是从第 3 项开始,每一项都等于前两项之和。前两项通常定义为:
- F(0) = 0
- F(1) = 1从 F(2) 开始:
- F(2) = F(1) + F(0) = 1 + 0 = 1
- F(3) = F(2) + F(1) = 1 + 1 = 2使用递归的思想实现斐波那契数列
-
def fun(n): if n == 0: return 0 elif n == 1: return 1 else: return fun(n-1) + fun(n-2) print(fun(0)) print(fun(1)) print(fun(2)) print(fun(3)) 0 1 1 2
Python类和对象
Python从设计之初就已经是一门面向对象的语言,正因为如此,在Python中创建一个类和对象是很容易的。
如果你以前没有接触过面向对象的编程语言,那你可能需要先了解一些面向对象语言的一些基本特征,在头脑里头形成一个基本的面向对象的概念,这样有助于你更容易的学习Python的面向对象编程。
接下来我们先来简单的了解下面向对象的一些基本特征
-
1. 面向对象编程 Object-Oriented Programming
-
什么是对象
-
对象是指现实中的物体或实体
-
-
什么是面向对象
-
把一切看成对象(实例),用各种对象之间的关系来描述事务。
-
-
对象都有什么特征
-
对象有很多属性(名词)
-
姓名, 年龄, 性别,
-
-
对象有很多行为(动作,动词)
-
学习,吃饭,睡觉,踢球, 工作
-
-
-
什么是类:
-
拥有相同属性和行为的对象分为一组,即为一个类
-
类是用来描述对象的工具,用类可以创建此类的对象(实例)
-
-
面向对象 示意
车(类)
\-------> BMW X5(京B.00000) 实例(对象)
/-------> 小京巴(户籍号:000001)
狗(类)
\-------> 导盲犬(户籍号:000002)
/-------> 100 (对象)
int(类)
\-------> 200 (对象)
类和对象的关系:
类是对象的模板或蓝图,定义了对象的属性和行为。
对象是类的实例,是根据类创建的具体实体。
同一个类可以创建多个对象,每个对象都有自己的状态。例如,一个Dog类可以创建多个 Dog 对象,每个对象有不同的 name 和 age。
2. 类和对象的基础语法
2.1 类的定义
类是创建对象的 ”模板”。
-
数据成员:表明事物的特征。 相当于变量
-
方法成员:表明事物的功能。 相当于函数
-
通过
class
关键字定义类。 -
类的创建语句语法:
class 类名 (继承列表):
实例属性(类内的变量) 定义
实例方法(类内的函数method) 定义
类变量(class variable) 定义
类方法(@classmethod) 定义
静态方法(@staticmethod) 定义
2.2 实例化对象(构造函数)
(1) 构造函数调用表达式
对象 = 类名([参数])
(2) 说明
-- 对象存储的是实例化后的地址。
-- 类名后面的参数按照构造方法的形参传递
-
对象是类的实例,具有类定义的属性和方法。
-
通过调用类的构造函数来创建对象。
-
每个对象都有自己的状态,但共享相同的方法定义。
类的构造函数是通过 __init__
方法定义的。__init__
方法是一个特殊的方法,用于在创建类的实例时进行初始化操作。它接收 self
作为第一个参数,表示类的实例。
如果你不显式定义 __init__
方法,Python 会自动提供一个默认的无参构造函数。
示例代码:
class Person:
# #__init__:类的构造函数
# #在__inin__函数之前还有__new__,用来创建对象实例的,创建实例后,给到__init__,
# # 在__init__用self表示实例
# #实例创建后不知道自己有哪些属性,在__init__方法中给实例什么属性,那么该实例就有哪些属性
def __init__(self, name, age):
self.name = name
self.age = age
#定义类的行为(函数)
def speak(self):
print(f'my name is {self.name}, my age is {self.age}')
#调用类,产生实例
person = Person('John', 25)
person.speak()
p1 = Person('lisa', 12)
p1.speak()
my name is John, my age is 25
my name is lisa, my age is 12
2.3 self
大家学Python面向对象的时候,总会遇到一个让人难以理解的存在:self
这个self到底是谁啊,为什么每个类实例方法都有一个参数self,它到底有什么作用呢?
「先下结论:类实例化后,self即代表着实例(对象)本身」
想要理解self有个最简单的方法,就是你把self当做「实例(对象)的身份证」。
类比人类,人类就是一个Python类,每个个体的人代表着实例(对象),而每个人的身份证代表的Python中self,每个人可以凭借身份证去上大学、坐高铁、住酒店...(方法),而Python中的实例(对象)也可以凭着self去调用类的方法。
3. 属性和方法
类的属性(也称为实例变量)可以在类定义时不显式定义,而是在实例化对象后动态添加。Python 是一种动态类型语言,允许在运行时动态地添加、修改和删除属性。
动态添加属性
可以在实例化对象后,通过直接赋值的方式为对象添加属性。这些属性不需要在类定义时预先声明。
3.1 实例属性
-
每个实例可以有自己的变量,称为实例变量(也叫属性)
-
属性的使用语法
实例.属性名
-
属性的赋值规则
-
首次为属性赋值则创建此属性.
-
再次为属性赋值则改变属性的绑定关系.
-
-
作用
-
记录每个对象自身的数据
-
属性使用示例:
# file : attribute.py
class Dog:
def eat(self, food):
print(self.color, '的', self.kinds, '正在吃', food)
pass
# 创建一个实例:
dog1 = Dog()
dog1.kinds = "京巴" # 添加属性
dog1.color = "白色"
dog1.color = "黄色" # 改变属性的绑定关系
print(dog1.color, '的', dog1.kinds)
dog2 = Dog()
dog2.kinds = "藏獒"
dog2.color = "棕色"
print(dog2.color, '的', dog2.kinds)
-
实例方法和实例属性(实例变量)结合在一起用:
class Dog:
def eat(self, food):
print(self.color, '的',
self.kinds, '正在吃', food)
# 创建第一个对象
dog1 = Dog()
dog1.kinds = '京巴' # 添加属性kinds
dog1.color = '白色' # 添加属性color
# print(dog1.color, '的', dog1.kinds) # 访问属性
dog1.eat("骨头")
dog2 = Dog()
dog2.kinds = '牧羊犬'
dog2.color = '灰色'
# print(dog2.color, '的', dog2.kinds) # 访问属性
dog2.eat('包子')
动态属性可以在运行时根据需要动态地添加属性,不需要在类定义时预先声明。对于一些简单的场景,可以减少类定义的复杂性。
但是动态添加属性可能会降低代码的可读性,因为属性的定义和使用分散在不同的地方。也可能会导致类型检查的困难,因为属性在运行时才被添加,静态类型检查工具可能无法识别这些属性。
在大多数情况下,建议在类定义时显式定义属性,并在构造函数中进行初始化。这样可以提高代码的可读性和可维护性。
3.2 实例方法
-
实例方法的作用
-
用于描述一个对象的行为,让此类型的全部对象都拥有相同的行为
-
实例方法说明
-
实例方法的实质是函数,是定义在类内的函数
-
实例方法至少有一个形参,第一个形参绑定调用这个方法的实例,一般命名为"self"
-
实例方法名是类属性
-
class Dog: def __init__(self, name,food,play,sleep): self.name = name self.food = food self.play = play self.sleep = sleep def speak(self): print(f'{self.name} eat {self.food},sleep{self.sleep}hours and like play{self.play}') dog1 =Dog('piter','buff','feipan','3') dog1.speak() piter eat buff,sleep3hours and like playfeipan
3.3 初始化方法
-
初始化方法的作用:
对新创建的对象添加属性
-
-
初始化方法的说明:
-
初始化方法名必须为
__init__
不可改变 -
初始化方法会在构造函数创建实例后自动调用,且将实例自身通过第一个参数self传入
__init__
方法 -
构造函数的实参将通过
__init__
方法的参数列表传入到__init__
方法中 -
初始化方法内如果需要return语句返回,则必须返回None
-
-
3.4 类属性
-
类属性是类的属性,此属性属于类,不属于此类的实例
-
作用:
-
通常用来存储该类创建的对象的共有属性
-
-
类属性说明
-
类属性,可以通过该类直接访问
-
类属性,可以通过类的实例直接访问
-
class Human: total_count = 0 # 创建类属性 self.name = name def __init__(self, name): self.name = name print(Human.total_count) h1 = Human("小张") print(h1.total_count) 0 0
类属性:在类定义的顶部定义,通常在
__init__
方法之外。实例属性:在
__init__
方法中定义,使用self
关键字。 -
3.5 类方法
-
类方法是用于描述类的行为的方法,类方法属于类,不属于该类创建的对象
-
说明
-
类方法需要使用@classmethod装饰器定义
-
类方法至少有一个形参,第一个形参用于绑定类,约定写为'cls'
-
类和该类的实例都可以调用类方法
-
类方法不能访问此类创建的对象的实例属性
-
-
-
class A: v = 0 @classmethod def set_v(cls, value): # def fun01(self) cls.v = value @classmethod def get_v(cls): return cls.v print(A.get_v()) A.set_v(100) print(A.get_v()) a = A() print(a.get_v())
cls
在Python中,
cls
是一个约定俗成的名称,用于表示类本身。在类方法(使用@classmethod
装饰的方法)中,cls
作为第一个参数传递给方法。这使得类方法可以访问和修改类属性以及调用其他类方法,而不需要引用具体的实例。cls
的作用 -
访问类属性:类方法可以通过
cls
访问和修改类属性。 -
调用类方法:类方法可以通过
cls
调用其他类方法。 -
创建类实例:类方法可以使用
cls
来创建类的实例
-
3.6 静态方法 @staticmethod
-
静态方法是定义在类的内部函数,此函数的作用域是类的内部
-
说明
-
静态方法需要使用@staticmethod装饰器定义
-
静态方法与普通函数定义相同,不需要传入self实例参数和cls类参数
-
静态方法只能凭借该类或类创建的实例调用
-
静态方法可以访问类属性但是不能访问实例属性
-
-
静态方法示例
-
class A: class_attr = 42 # 类属性 def __init__(self, value): self.instance_attr = value # 实例属性 @staticmethod def myadd(a, b): # 只能访问传递的参数,不能访问实例属性 return a + b # 创建类实例 a = A(10) # 调用静态方法 print(A.myadd(100, 200)) # 输出: 300 print(a.myadd(300, 400)) # 输出: 700 # 尝试在静态方法内访问类属性或实例属性(会导致错误) class B: class_attr = 42 def __init__(self, value): self.instance_attr = value
3.7 魔术方法
Python中的魔术方法(Magic Methods)是一种特殊的方法,它们以双下划线开头和结尾,例如
__init__
,__str__
,__add__
等。这些方法允许您自定义类的行为,以便与内置Python功能(如+运算符、迭代、字符串表示等)交互。以下是一些常用的Python魔术方法:
-
__init__(self, ...)
: 初始化对象,通常用于设置对象的属性。 -
__str__(self)
: 定义对象的字符串表示形式,可通过str(object)
或print(object)
调用。例如,您可以返回一个字符串,描述对象的属性。 -
__repr__(self)
: 定义对象的“官方”字符串表示形式,通常用于调试。可通过repr(object)
调用。 -
__len__(self)
: 定义对象的长度,可通过len(object)
调用。通常在自定义容器类中使用。 -
__getitem__(self, key)
: 定义对象的索引操作,使对象可被像列表或字典一样索引。例如,object[key]
。 -
__setitem__(self, key, value)
: 定义对象的赋值操作,使对象可像列表或字典一样赋值。例如,object[key] = value
。 -
__delitem__(self, key)
: 定义对象的删除操作,使对象可像列表或字典一样删除元素。例如,del object[key]
。 -
__iter__(self)
: 定义迭代器,使对象可迭代,可用于for
循环。 -
__next__(self)
: 定义迭代器的下一个元素,通常与__iter__
一起使用。 -
__add__(self, other)
: 定义对象相加的行为,使对象可以使用+
运算符相加。例如,object1 + object2
。 -
__sub__(self, other)
: 定义对象相减的行为,使对象可以使用-
运算符相减。 -
__eq__(self, other)
: 定义对象相等性的行为,使对象可以使用==
运算符比较。 -
__lt__(self, other)
: 定义对象小于其他对象的行为,使对象可以使用<
运算符比较。 -
__gt__(self, other)
: 定义对象大于其他对象的行为,使对象可以使用>
运算符比较。 -
__call__(self, other)
是一个特殊的方法(也称为“魔法方法”),它允许一个对象像函数一样被调用。 -
1.`__init__(self, ...)`: 初始化对象 ```python class MyClass: def __init__(self, value): self.value = value obj = MyClass(42) ``` 2.`__str__(self)`: 字符串表示形式 class MyClass: def __init__(self, value): self.value = value def __str__(self): return f"MyClass instance with value: {self.value}" obj = MyClass(42) print(obj) # 输出:MyClass instance with value: 42