学习内容:
1.1 变量作用域
接着25_3_17 python进阶学习.
1.1.1 嵌套函数
嵌套函数是指在一个函数内部定义的另一个函数,它可以像普通函数一样使用,但通常是作为外部函数逻辑的一部分,内部函数可以将一些复杂的逻辑隐藏在外部函数内部,只暴露必要的接口,隐藏实现细节,具有更强的局部化和封装性.
嵌套函数的外部函数是可以调用内部函数的,并且函数也可以看作是定义在作用域中的数据,在执行函数的时候,要遵循:优先在自己的作用域中寻找,没有则返回到上一层的作用域寻找.
示例1:定义两个全局函数 func,execute,其中 execute 调用 func .
def func():
print('func')
def execute():
print('execute')
func()
execute()
示例2:定义两个 func 函数,一个在 execute 外部,一个在 execute 内部 .
def func():
print('func')
def execute():
print('execute')
def func():
print('local func')
func()
execute()
示例3:当内部函数执行时需要变量,如果在局部作用域中找不到,则到上层作用域中找.
def execute():
name = 'zhangsan'
def inner():
print(name)
inner()
execute() # zhangsan
示例4:当同一作用域里面出现了两个同名函数时,后面的会将前面的同名函数覆盖.
def func():
print('func')
def execute():
print('execute')
def func():
print('local func')
func()
def func():
print('new func')
execute()
func() # execute local func new func
示例5:嵌套函数可以访问外部函数的变量和参数,但外部函数不能直接访问内部函数的变量.
def outer():
x = 10 # 外部函数的变量
def inner():
y = 20 # 内部函数的变量
print(f"x: {x}, y: {y}") # 内部函数可以访问外部函数的变量
inner()
print(y) # 错误:外部函数不能访问内部函数的变量
outer()
示例6:函数的返回值可以是一个函数.
def execute():
name = 'zhangsan'
def inner():
print(name)
return inner
inner = execute()
print(type(inner))
inner() # <class 'function'> zhangsan
1.1.2 局部作用域修改全局变量
使用 global 语句声明一个或多个变量,并告诉python解释执行器这些变量是全局变量.
语法:global 变量名1, 变量名2, ....
示例:
# 如何用一个变量来记录一个函数调用的次数
count = 0
def hello(name):
global count # 声明 global 是全局变量
print('hello', name)
count += 1 # 等同于 count = count + 1
hello('小张')
hello('小李')
hello('小魏')
print('您共调用hello函数', count, '次') # 3 次
注:1. 全局变量如果要在函数内部被赋值,则必须经过全局声明 global;
2. 默认全局变量在函数内部可以使用,但只能取值,不能赋值;
3. 不能先声明为局部变量,再用 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.3 局部作用域修改外部变量
局部作用域修改外部变量的前提是必须是嵌套函数,而且局部作用域中若要修改外部函数嵌套作用域中的变量需要使用:nonlocal 语句,其作用是将局部作用域中变量声明为外部函数嵌套作用域中的变量.
语法:nonlocal 变量名
示例:
"""
外部嵌套作用域
"""
def func01():
a = 10
def func02():
# 内部函数,可以访问外部嵌套变量
# print(a)
# 内部函数,如果修改外部嵌套变量,需要使用nonlocal语句声明
nonlocal a
a = 20
func02()
print(a)
func01()
1.2 函数的自调用(递归)
1.2.1 递归的定义
在定义一个过程或函数时出现调用本过程或本函数的成分,称之为递归,若调用自身,称之为直接递归,若过程或函数A调用过程或函数B,而 B 又调用 A,称之为间接递归.
在算法设计中,任何间接递归算法都可以转换为直接递归算法来实现,所以主要讨论直接递归,递归算法通常通常把一个大的复杂问题层层转化为一个或多个与原问题相似的规模较小的问题来求解,递归策略只需少量的代码就可以描述出解题过程所需要的多次重复计算,大大减少了算法的代码量.
解决递归问题满足的三个条件:
1. 需要解决的问题可以转化为一个或多个子问题来求解,而这些子问题的求解方法与原问题完全相同,只是在数量规模上不同.
2. 递归调用的次数必须是有限的.
3. 必须有结束递归的条件来终止递归.
1.2.2 递归的使用
递归函数示例:计算阶乘 factorial()
迭代实现:
def factorial(n):
result = 1
i = 1
while i <= n:
result *= i
i+=1
return result
print("factorial: ", factorial(5))
递归实现示例:
# file: factorial.py
def factorial(n):
if n == 1:
return 1
s = n * factorial(n - 1)
return s
print("factorial: ", factorial(5))
注:递归一定要控制递归的层数,当符合某一条件时要终止递归调用,并且几乎所有的递归都能用 while 循环来代替.
递归优缺点:优点: 递归可以把问题简单化,让思路更为清晰,代码更简洁;
缺点: 递归因系统环境影响大,当递归深度太大时,可能会得到不可预知的结果.
1.3 Python类和对象
python从设计之初就已经是一门面向对象的语言,正因为如此,在python中创建一个类和对象是很容易的,如果你以前没有接触过面向对象的编程语言,那你可能需要先了解一些面向对象语言的一些基本特征,在头脑里头形成一个基本的面向对象的概念,这样有助于你更容易的学习python的面向对象编程.
1.3.1 类和对象的基础语法
1)类的定义
类是创建对象的 "模板"
- 数据成员:表明事物的特征(相当于变量).
- 方法成员:表明事物的功能(相当于函数).
- 通过' class '关键字定义类.
- 类的创建语句语法:
class 类名 (继承列表):
实例属性(类内的变量) 定义
实例方法(类内的函数method) 定义
类变量(class variable) 定义
类方法(@classmethod) 定义
静态方法(@staticmethod) 定义
示例:
class Dog: # 定义一个Dog类
pass
class Person:
# 构造方法,用于初始化对象的实例属性
def __init__(self, name, age):
self.name = name
self.age = age
def introduce(self):
print(f"My name is {self.name} and I am {self.age} years old.")
注:在类的创建时类名必须为标识符(与变量的命名相同,建议首字母大写),类名实质上就是变量,它绑定一个类.
2)实例化对象(构造函数)
语法:对象 = 类名([参数])
注:1. 对象存储的是实例化后的地址;
2. 类名后面的参数按照构造方法的形参传递
- 对象是类的实例,具有类定义的属性和方法.
- 通过调用类的构造函数来创建对象.
- 每个对象都有自己的状态,但共享相同的方法定义.
3. 类的构造函数是通过 '__init__' 方法定义的,'__init__' 方法是一个特殊的方法,用于在创建类的实例时进行初始化操作。它接收 `self` 作为第一个参数,表示类的实例,如果你不显式定义 '__init__' 方法,python 会自动提供一个默认的无参构造函数.
示例:
class Dog:
pass
# 创建第一个实例:
dog1 = Dog()
print(id(dog1)) # 打印这个对象的ID
# 创建第二个实例对象
dog2 = Dog() # dog2 绑定一个Dog类型的对象
print(id(dog2))
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
def introduce(self):
print(f"My name is {self.name} and I am {self.age} years old.")
person1 = Person("Alice", 25)
person2 = Person("Bob", 30)
实例说明:实例有自己的作用域和名字空间,可以为该实例添加实例变量(也叫属性);实例可以调用类方法和实例方法;实例可以访问类变量和实例变量.
3)self 的使用
' self '是类方法的第一个参数,用于引用对象本身,并且 ' self ' 不是python关键字,但是约定俗成的命名,可以使用其他名称代替,但通常不建议.
示例:
class Students:
# 构造方法
def __init__(self,name):
self.name = name
# 实例方法
def study(self,examination_results):
self.examination_results = examination_results
print("同学{}的考试分数是{}".format(self.name,self.examination_results))
print("该实例对象的地址是{}".format(self))
# 先来个实例student_a
studend_a = Students('studend_a')
print(studend_a.name)
# 再来个实例student_b
studend_b = Students('studend_b')
print(studend_b.name)
1.3.2 属性和方法
类的属性(也称为实例变量)可以在类定义时不显式定义,而是在实例化对象后动态添加。python是一种动态类型语言,允许在运行时动态地添加、修改和删除属性.
动态添加属性:可以在实例化对象后,通过直接赋值的方式为对象添加属性。这些属性不需要在类定义时预先声明.
1)实例属性
每个实例可以有自己的变量,称为实例变量(也叫属性).
语法:实例.属性名
属性的赋值规则:首次为属性赋值则创建此属性,再次为属性赋值则改变属性的绑定关系,其作用为记录每个对象自身的数据.
属性使用示例:
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('包子')
注:动态属性可以在运行时根据需要动态地添加属性,不需要在类定义时预先声明,对于一些简单的场景,可以减少类定义的复杂性,但是动态添加属性可能会降低代码的可读性,因为属性的定义和使用分散在不同的地方,也可能会导致类型检查的困难,因为属性在运行时才被添加,静态类型检查工具可能无法识别这些属性,在大多数情况下,建议在类定义时显式定义属性,并在构造函数中进行初始化,这样可以提高代码的可读性和可维护性.
2)实例方法
实例方法的作用:用于描述一个对象的行为,让此类型的全部对象都拥有相同的行为,实例方法的实质是函数,是定义在类内的函数,实例方法至少有一个形参,第一个形参绑定调用这个方法的实例,一般命名为" self ",实例方法名是类属性.
实例方法的调用语法:实例.实例方法名(调用传参)
# 或
类名.实例方法名(实例, 调用传参)
示例:
class Dog:
"""这是一个种小动物的定义
这种动物是狗(犬)类,用于创建各种各样的小狗
"""
def eat(self, food):
'''此方法用来描述小狗吃东西的行为'''
print("小狗正在吃", food)
def sleep(self, hour):
print("小狗睡了", hour, "小时!")
def play(self, obj):
print("小狗正在玩", obj)
dog1 = Dog()
dog1.eat("骨头")
dog1.sleep(1)
dog1.play('球')
dog2 = Dog()
dog2.eat("窝头")
dog2.sleep(2)
dog2.play('飞盘')
3)初始化方法
初始化方法的作用是对新创建的对象添加属性.
初始化方法的语法格式:
class 类名(继承列表):
def __init__(self[, 形参列表]):
语句块
# [] 代表其中的内容可省略
初始化方法的说明:
1. 初始化方法名必须为'__init__ '不可改变.
2. 初始化方法会在构造函数创建实例后自动调用,且将实例自身通过第一个参数 self 传入'__init__ '方法.
3. 构造函数的实参将通过'__init__ '方法的参数列表传入到'__init__ '方法中.
4. 初始化方法内如果需要 return 语句返回,则必须返回 None .
示例:
class Car:
def __init__(self, c, b, m):
self.color = c # 颜色
self.brand = b # 品牌
self.model = m # 型号
def run(self, speed):
print(self.color, "的", self.brand, self.model, "正在以", speed, "公里/小时的速度行驶")
def change_color(self, c):
self.color = c
a4 = Car("红色", "奥迪", "A4")
a4.run(199)
a4.change_color("黑色")
a4.run(230)
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)
注:类属性:在类定义的顶部定义,通常在'__init__ '方法之外.
实例属性:在'__init__ '方法中定义,使用 self 关键字.
5)类方法
类方法是用于描述类的行为的方法,类方法属于类,不属于该类创建的对象,类方法需要使用 @classmethod 装饰器定义,类方法至少有一个形参,第一个形参用于绑定类,约定写为'cls',类和该类的实例都可以调用类方法,类方法不能访问此类创建的对象的实例属性 .
示例1:
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())
示例2:
class MyClass:
class_attr = 0 # 类属性
def __init__(self, value):
self.instance_attr = value # 实例属性
@classmethod
def modify_class_attr(cls, new_value):
cls.class_attr = new_value
print(f"类属性已修改为: {cls.class_attr}")
@classmethod
def try_modify_instance_attr(cls):
try:
cls.instance_attr = 10 # 尝试修改实例属性(会失败)
except AttributeError as e:
print(f"错误: {e}")
def show_attrs(self):
print(f"实例属性: {self.instance_attr}")
print(f"类属性: {self.class_attr}")
# 创建类的实例
obj = MyClass(5)
# 调用类方法修改类属性
MyClass.modify_class_attr(20) # 输出: 类属性已修改为: 20
obj.show_attrs() # 实例属性: 5 类属性: 20
# 调用类方法尝试修改实例属性
MyClass.try_modify_instance_attr() type object 'MyClass' has no attribute 'instance_attr'
# 尝试调用类方法修改实例属性
obj.try_modify_instance_attr() # type object 'MyClass' has no attribute 'instance_attr'
cls:在Python中,' cls '是一个约定俗成的名称,用于表示类本身,在类方法(使用 ' @classmethod '装饰的方法)中,' cls '作为第一个参数传递给方法,这使得类方法可以访问和修改类属性以及调用其他类方法,而不需要引用具体的实例.
cls 的作用:1. 访问类属性:类方法可以通过' cls '访问和修改类属性.
2. 调用类方法:类方法可以通过' cls '调用其他类方法.
3. 创建类实例:类方法可以使用' cls '来创建类的实例.
示例:
class MyClass:
class_attr = 0 # 类属性
def __init__(self, value):
self.instance_attr = value # 实例属性
@classmethod
def modify_class_attr(cls, new_value):
cls.class_attr = new_value
print(f"类属性已修改为: {cls.class_attr}")
@classmethod
def show_class_attr(cls):
print(f"类属性当前值: {cls.class_attr}")
@classmethod
def create_instance(cls, value):
# 使用 cls 创建类实例,会调用__init__方法
return cls(value)
# 调用类方法修改类属性
MyClass.modify_class_attr(20) # 输出: 类属性已修改为: 20
# 调用类方法显示类属性
MyClass.show_class_attr() # 输出: 类属性当前值: 20
# 使用类方法创建类的实例
new_instance = MyClass.create_instance(10)
print(f"新实例的实例属性: {new_instance.instance_attr}") # 输出: 新实例的实例属性: 10
print(f"新实例的类属性: {new_instance.class_attr}") # 输出: 新实例的类属性: 20
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
@staticmethod
def myadd(a, b):
# return a + b + B.class_attr
# 以下访问会导致错误
# return a + b + self.instance_attr
return a + b
# 创建类实例
b = B(10)
# 调用静态方法
print(B.myadd(100, 200)) # 输出: 300
print(b.myadd(300, 400)) # 输出: 700
7)魔术方法
python中的魔术方法(Magic Methods)是一种特殊的方法,它们以双下划线开头和结尾,例如'__init__','__str__','__add__'等,这些方法允许您自定义类的行为,以便与内置python功能(如:+运算符、迭代、字符串表示等)交互.
以下是一些常用的python魔术方法:
序号 | 方法 | 描述 |
1 | __init__(self, ...) | 初始化对象,通常用于设置对象的属性 |
2 | __str__(self) | 定义对象的字符串表示形式,可通过`str(object)`或`print(object)`调用。例如,您可以返回一个字符串,描述对象的属性 |
3 | __repr__(self) | 定义对象的“官方”字符串表示形式,通常用于调试。可通过' repr(object) '调用 |
4 | __len__(self) | 定义对象的长度,可通过`len(object)`调用。通常在自定义容器类中使用 |
5 | __getitem__(self, key) | 定义对象的索引操作,使对象可被像列表或字典一样索引。例如,object[key] |
6 | __setitem__(self, key, value) | 定义对象的索引操作,使对象可被像列表或字典一样索引。例如:' object[key] ' |
7 | __delitem__(self, key) | 定义对象的删除操作,使对象可像列表或字典一样删除元素。例如:' del object[key] ' |
8 | __iter__(self) | 定义迭代器,使对象可迭代,可用于 for 循环 |
9 | __next__(self) | 定义迭代器的下一个元素,通常与' __iter__' 一起使用 |
10 | __add__(self, other) | 定义对象相加的行为,使对象可以使用 '+' 运算符相加。例如:object1 + object2 |
11 | __sub__(self, other) | 定义对象相减的行为,使对象可以使用 '-' 运算符相减 |
12 | __eq__(self, other) | 定义对象相等性的行为,使对象可以使用 '==' 运算符比较 |
13 | __lt__(self, other) | 定义对象小于其他对象的行为,使对象可以使用 ' < ' 运算符比较 |
14 | __gt__(self, other) | 定义对象大于其他对象的行为,使对象可以使用 ' > ' 运算符比较 |
15 | __call__(self, other) | 是一个特殊的方法(也称为“魔法方法”),它允许一个对象像函数一样被调用 |
部分示例:
class MyClass:
def __init__(self, value):
self.value = value
obj = MyClass(42)
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
class MyClass:
def __init__(self, value):
self.value = value
def __repr__(self):
return f"MyClass({self.value})"
#def __repr__(self):
#return f"Person(name={self.name}, age={self.age})"
obj = MyClass(42)
print(obj) # 输出:MyClass(42)
class MyList:
def __init__(self, items):
self.items = items
def __len__(self):
return len(self.items)
my_list = MyList([1, 2, 3, 4])
print(len(my_list)) # 输出:4