记一些 Python 的学习笔记,关于作用域、函数以及类:
作用域
- 函数查找变量:当前函数局部变量 —— 包含函数的局部变量 —— 全局变量 —— 内置名字表
所有函数中的变量赋值都是将值存储在当前函数局部变量内,全局变量不能在函数中直接赋值(除非用 global 语句命名),但是可以引用。
global
global
表示该变量为全局作用域
num = 1
def fun1():
global num # 需要使用 global 关键字声明
print(num)
num = 123
fun1()
print(num)
以上输出结果:
1
123
nonlocal
nonlocal
表示该变量为外层作用域
def outer():
num = 10
def inner():
nonlocal num # nonlocal关键字声明
num = 100
print(num)
inner()
print(num)
outer()
以上输出结果:
100
100
函数
#定义
def 函数名(参数) :
'''描述'''
缩进的逻辑语句
- 返回:不指定
return
的返回值会默认返回None
。
参数
- 位置参数
# 定义
def add(x, y):
'''一个加法函数'''
return x + y
# 调用
add(1, 2) # 1对应x, 2对应y
- 默认参数
# 必选参数 在 默认参数 前
def add(x, y=3):
'''一个加法函数'''
return x + y
# 调用
add(1, 2) # 1对应x, 2对应y
add(1) # 1对应x,y默认为3
定义默认参数要牢记一点:默认参数必须指向不变对象!因为默认值只被赋值一次。
- 可变参数
传入的参数个数是可变的。
# 在函数内部,参数 args 接收到的是一个 tuple
def add(*args):
sum = 0
for n in args:
sum = sum + n
return sum
# 调用
add(1, 2, 3) # 参数 args: (1, 2, 3)
# 已有一个序列
nums = [1, 2, 3]
add(*nums) # *nums 表示把 nums 这个 list 的所有元素作为可变参数传进去
- 关键字参数
关键字参数允许你传入0个或任意个含参数名的参数,这些关键字参数在函数内部自动组装为一个dict。
def person(**kw):
print(kw)
# 调用
person(name='Ben', age=10) # 输出 {'name': 'Ben', 'age': 10}
person() # 输出 {}
# 已有字典
Ben = {'name': 'Ben', 'age': 10}
person(**Ben)
'''
**Ben表示把Ben这个dict的所有key-value用关键字参数传入到函数的**kw参数kw将获得一个dict
注意kw获得的dict是Ben的一份拷贝,对kw的改动不会影响到函数外的Ben
'''
- 命名关键字参数
要限制关键字参数的名字,就可以用命名关键字参数
# 和关键字参数**kw不同,命名关键字参数需要一个特殊分隔符*,*后面的参数被视为命名关键字参数。
def person(name, age, *, city, job):
print(name, age, city, job)
# 调用
person('Jack', 24, city='Beijing', job='Engineer')
# 如果函数定义中已经有了一个可变参数,后面跟着的命名关键字参数就不再需要一个特殊分隔符*了
def person(name, age, *args, city, job):
print(name, age, args, city, job)
# 调用
person('Jack', 24, city='Beijing', job='Engineer')
命名关键字参数必须传入参数名,否则报错。
参数定义的顺序必须是:必选位置参数、默认参数、可变参数、命名关键字参数、关键字参数。
递归
使用递归函数需要注意防止栈溢出。在计算机中,函数调用是通过栈(stack)这种数据结构实现的,每当进入一个函数调用,栈就会加一层栈帧,每当函数返回,栈就会减一层栈帧。由于栈的大小不是无限的,所以,递归调用的次数过多,会导致栈溢出。
解决递归调用栈溢出的方法是通过尾递归优化。尾递归是指,在函数返回的时候,调用自身本身,并且,return语句不能包含表达式。这样,编译器或者解释器就可以把尾递归做优化,使递归本身无论调用多少次,都只占用一个栈帧,不会出现栈溢出的情况。
def fact(n):
return fact_iter(n, 1)
def fact_iter(num, product):
if num == 1:
return product
return fact_iter(num - 1, num * product)
匿名函数
- 主体是一个表达式,而不是一个代码块。仅仅能在lambda表达式中封装有限的逻辑进去。
lambda
函数拥有自己的命名空间,且不能访问自己参数列表之外或全局命名空间里的参数。
lambda [arg1 [,arg2,.....argn]]:expression
类
- 获取类型
type(123)
# <class 'int'>
type(abs)
# <class 'builtin_function_or_method'>
- 判断函数
import types
def fn():
pass
type(fn) == types.FunctionType # True
判断 class 类型
isinstance()
判断的是一个实例对象是否是该类型,或者位于该类型的父继承链上。isinstance(对象, 类型)
还可以判断一个变量是否是某些类型中的一种,比如下面的代码就可以判断是否是
list
或者tuple
:isinstance([1, 2, 3], (list, tuple))
issubclass()
判断的是一个类是否某类型的子类型。issubclass(子类, 父类)
封装
class Student(object):
a = 123 # 类属性
def __init__(self, name, score): # 定义实例属性
self.name = name
self.__score = score # 内部属性 外部不能访问
def print_score(self):
print('%s: %s' % (self.name, self.__score))
继承
如下,obj
是父类,Student
会继承 obj
的所有方法和属性。
class Student(obj):
pass
但在 Student
中定义的和父类重名方法或重名属性,会优先用 Student
的中的方法和属性。
- 多继承
class DerivedClassName(Base1, Base2, Base3):
pass
从优先级左到右继承,比如某方法或属性,当前类中找不到,去 Base1
找,也没有就去 Base2
找,以此类推。
多态
如下,A
为 B
的父类。
class A:
def do(self):
pass
class B(A):
pass
a = A()
b = B()
def Do(obj):
obj.do()
# Do 函数接受一个对象,执行对象的do方法。
# 只要是A类或者继承自A类,都可以作为参数obj