零基础为了学人工智能,正在艰苦的学习
这里把,函数以及类相关的知识做一个笔记,放在这里。
期待与大家交流~
变量作用域
-
Python 中,程序的变量并不是在哪个位置都可以访问的,访问权限决定于这个变量是在哪里赋值的。
-
定义在函数内部的变量拥有局部作用域,该变量称为局部变量。
-
定义在函数外部的变量拥有全局作用域,该变量称为全局变量。
-
局部变量只能在其被声明的函数内部访问,而全局变量可以在整个程序范围内访问。
-
当内部作用域想修改外部作用域的变量时,就要用到
global
和nonlocal
关键字了。def discounts(price, rate):
final_price = price * rate
return final_priceold_price = float(input('请输入原价:')) # 98
rate = float(input('请输入折扣率:')) # 0.9
new_price = discounts(old_price, rate)
print('打折后价格是:%.2f' % new_price) # 88.20
闭包机制
-
闭包是函数式编程的一个重要的语法结构,是一种特殊的内嵌函数。
-
如果在一个内部函数里对外层非全局作用域的变量进行引用,那么内部函数就被认为是闭包。
-
通过闭包可以访问外层非全局作用域的变量,这个作用域称为 闭包作用域。
def funX(x):
def funY(y):
return x * yreturn funY
i = funX(8)
print(type(i)) # <class 'function'>
print(i(5)) # 40
上面的代码看到,funx(x)返回了一个函数funy,一般而言,函数调用完毕,x变量的内存应该释放,但是因为这里涉及闭包机制。这里的x其实转为,堆存放。所以x=8,这里是被存储的。具体解释下方,有进一步说明。
def make_counter(init):
counter = [init]
def inc(): counter[0] += 1
def dec(): counter[0] -= 1
def get(): return counter[0]
def reset(): counter[0] = init
return inc, dec, get, reset
inc, dec, get, reset = make_counter(0)
inc()
inc()
inc()
print(get()) # 3
dec()
print(get()) # 2
reset()
print(get()) # 0
如何理解闭包机制。从计算机的原理做一个讲解。(From ChatGPT)
内存管理
在计算机中,内存管理主要分为栈(stack)和堆(heap)。
-
栈内存:
- 栈内存用于存储局部变量和函数调用信息。每次函数调用时,函数的局部变量和一些函数调用信息都会被压入栈中;当函数返回时,这些信息会被弹出栈。
- 栈是自动管理的,随着函数调用和返回自动调整。
-
堆内存:
- 堆内存用于动态分配内存,如对象和闭包中的变量。堆内存中的数据由垃圾收集器管理,当不再需要时会被回收。
- 堆内存允许在函数返回之后依然保留数据,这对闭包特别重要。
闭包实现
-
创建闭包:
- 当外部函数
make_counter
被调用时,参数init
被存储在栈中。 - 在
make_counter
函数内,定义了几个内部函数(inc
、dec
、get
和reset
),并且它们都引用了counter
列表。
- 当外部函数
-
移动到堆内存:
- 当
make_counter
返回时,内部函数引用的counter
列表会被移动到堆内存中。这是因为即使make_counter
函数返回了,内部函数依然需要访问和修改counter
。 - 堆内存中的数据不会因为外部函数返回而被清除,只有当没有引用指向这些数据时,垃圾收集器才会回收它们。
- 当
-
函数引用和调用:
- 返回的内部函数
inc
、dec
、get
和reset
都保留了对堆内存中counter
列表的引用。 - 当这些函数被调用时,它们通过闭包机制访问并修改
counter
列表中的值。
- 返回的内部函数
总结
闭包机制是因为,本来应该释放内存空间的数值继续被存放。后续的函数调用直接使用了原来函数的赋值。
Lambda 表达式
Python 使用 lambda
关键词来创建匿名函数,而非def
关键词,它没有函数名,其语法结构如下:
lambda argument_list: expression
lambda
- 定义匿名函数的关键词。argument_list
- 函数参数,它们可以是位置参数、默认参数、关键字参数,和正规函数里的参数类型一样。:
- 冒号,在函数参数和表达式中间要加个冒号。expression
- 只是一个表达式,输入函数参数,输出一些值。
注意:
-
expression
中没有 return 语句,因为 lambda 不需要它来返回,表达式本身结果就是返回值。 -
匿名函数拥有自己的命名空间,且不能访问自己参数列表之外或全局命名空间里的参数。
lbd_sqr = lambda x: x ** 2
print(lbd_sqr)<function <lambda> at 0x000000BABB6AC1E0>
y = [lbd_sqr(x) for x in range(10)]
print(y)[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
sumary = lambda arg1, arg2: arg1 + arg2
print(sumary(10, 20)) # 30func = lambda *args: sum(args)
print(func(1, 2, 3, 4, 5)) # 15
匿名函数的应用
-
filter(function, iterable)
过滤序列,过滤掉不符合条件的元素,返回一个迭代器对象,如果要转换为列表,可以使用list()
来转换。odd = lambda x: x % 2 == 1
templist = filter(odd, [1, 2, 3, 4, 5, 6, 7, 8, 9])
print(list(templist)) # [1, 3, 5, 7, 9] -
map(function, *iterables)
根据提供的函数对指定序列做映射。odd = lambda x: x % 2 == 1
templist = filter(odd, [1, 2, 3, 4, 5, 6, 7, 8, 9])
print(list(templist)) # [1, 3, 5, 7, 9] -
map(function, *iterables)
根据提供的函数对指定序列做映射。
类与对象 对象 = 属性+方法
-
继承:子类自动共享父类之间数据和方法的机制
class MyList(list):
passlst = MyList([1, 5, 2, 7, 8])
lst.append(9)
lst.sort()
print(lst)[1, 2, 5, 7, 8, 9]
-
多态:不同对象对同一方法响应不同的行动
self 是什么?
Python 的 self
相当于 C++ 的 this
指针。
类的方法与普通的函数只有一个特别的区别 ------ 它们必须有一个额外的第一个参数名称(对应于该实例,即该对象本身),按照惯例它的名称是 self
。在调用方法时,我们无需明确提供与参数 self
相对应的参数。
Python 的魔法方法
据说,Python 的对象天生拥有一些神奇的方法,它们是面向对象的 Python 的一切...
它们是可以给你的类增加魔力的特殊方法...
如果你的对象实现了这些方法中的某一个,那么这个方法就会在特殊的情况下被 Python 所调用,而这一切都是自动发生的...
类有一个名为__init__(self[, param1, param2...])
的魔法方法,该方法在类实例化时会自动调用。
class Ball:
def __init__(self, name):
self.name = name
def kick(self):
print("我叫%s,该死的,谁踢我..." % self.name)
a = Ball("球A")
b = Ball("球B")
c = Ball("球C")
a.kick()
# 我叫球A,该死的,谁踢我...
b.kick()
# 我叫球B,该死的,谁踢我...
__new__方法
__new__(cls[, ...])
在一个对象实例化的时候所调用的第一个方法,在调用__init__
初始化前,先调用__new__
。-
__new__
至少要有一个参数cls
,代表要实例化的类,此参数在实例化时由 Python 解释器自动提供,后面的参数直接传递给__init__
。 -
__new__
对当前类进行了实例化,并将实例返回,传给__init__
的self
。但是,执行了__new__
,并不一定会进入__init__
,只有__new__
返回了,当前类cls
的实例,当前类的__init__
才会进入。class A(object):
def init(self, value):
print("into A init")
self.value = valuedef __new__(cls, *args, **kwargs): print("into A __new__") print(cls) return object.__new__(cls)
class B(A):
def init(self, value):
print("into B init")
self.value = valuedef __new__(cls, *args, **kwargs): print("into B __new__") print(cls) return super().__new__(cls, *args, **kwargs)
b = B(10)
结果:
into B new
<class 'main.B'>
into A new
<class 'main.B'>
into B init
class A(object):
def init(self, value):
print("into A init")
self.value = valuedef __new__(cls, *args, **kwargs): print("into A __new__") print(cls) return object.__new__(cls)
class B(A):
def init(self, value):
print("into B init")
self.value = valuedef __new__(cls, *args, **kwargs): print("into B __new__") print(cls) return super().__new__(A, *args, **kwargs) # 改动了cls变为A
b = B(10)
结果:
into B new
<class 'main.B'>
into A new
<class 'main.A'>
-
利用__new__
实现单例模式。
class Earth:
pass
a = Earth()
print(id(a)) # 260728291456
b = Earth()
print(id(b)) # 260728291624
class Earth:
__instance = None # 定义一个类属性做判断
def __new__(cls):
if cls.__instance is None:
cls.__instance = object.__new__(cls)
return cls.__instance
else:
return cls.__instance
a = Earth()
print(id(a)) # 512320401648
b = Earth()
print(id(b)) # 512320401648
-
super().__new__(cls)
调用:super()
返回基类对象,这个对象可以访问父类的方法。__new__(cls)
调用基类的__new__
方法。对于大多数类来说,这意味着调用的是object.__new__
方法,因为object
是所有类的最终基类。cls
作为参数传递,告诉__new__
要创建的是哪个类的实例。
-
创建新实例:
super().__new__(cls)
实际上创建了一个新的cls
类型的实例,并返回该实例。这个实例还没有被初始化(即__init__
还没有被调用)。
-
赋值给类变量
cls._instance
:cls._instance
是一个类变量,用于保存这个新创建的实例。- 这个赋值操作确保了类的
_instance
属性持有这个单例实例,以便以后调用Singleton()
时,可以返回同一个实例。
详细解释
-
第一次调用
Singleton()
:cls._instance
是None
,所以执行super().__new__(cls)
创建一个新的Singleton
实例,并将其赋值给cls._instance
。- 返回这个新创建的实例。
-
后续调用
Singleton()
:cls._instance
已经不是None
,直接返回cls._instance
,不再创建新实例。
通过这种方式,cls._instance = super().__new__(cls)
确保了 Singleton
类只有一个实例。这在需要确保类的唯一实例存在的情况下非常有用,例如实现配置管理器、数据库连接池等场景。
公有变量和函数
公有变量和函数是可以在类的外部访问和使用的。它们没有任何特殊的前缀。默认情况下,Python 中的所有变量和函数都是公有的。
其实就是你平常见得很多的函数,都是公有的。
class MyClass:
def __init__(self):
self.public_var = 10
def public_method(self):
print("This is a public method")
# 创建类的实例
obj = MyClass()
# 访问公有变量
print(obj.public_var) # 输出: 10
# 调用公有方法
obj.public_method() # 输出: This is a public method
私有变量和函数
私有变量和函数则是在类的外部不可访问和修改的。在 Python 中,通过在变量名或函数名前加上两个下划线 __
来定义私有变量和函数。
前面加上两个__,这个函数的调用方法就变化了,一般而言你是无法调用的。
class MyClass:
def __init__(self):
self.__private_var = 20
def __private_method(self):
print("This is a private method")
def access_private(self):
print(self.__private_var)
self.__private_method()
# 创建类的实例
obj = MyClass()
# 尝试直接访问私有变量(会导致错误)
# print(obj.__private_var) # AttributeError
# 尝试直接调用私有方法(会导致错误)
# obj.__private_method() # AttributeError
# 通过公有方法间接访问私有变量和方法
obj.access_private() # 输出: 20 \n This is a private method
但是你真的无法改动了嘛,不是,你还是可以改变的。
Python 并没有真正的私有成员。所谓的私有变量和函数实际上是通过名称改写(name mangling)来实现的。
Python 在类定义中,如果成员名称以双下划线开头,Python 解释器会将其改写为 _类名__变量名
的形式,以避免与其他类的名称冲突。
例如,以上例子中的 __private_var
和 __private_method
实际上被改写为 _MyClass__private_var
和 _MyClass__private_method
。因此,它们仍然可以通过这种方式访问,但这不是推荐的做法。
注意事项:区别总结
-
私有方法
__test
:- 使用名称改写(name mangling)机制实现私有化。
- 只能在类内部访问,不能从类外部直接访问。
- 名称改写后实际名称为
_ClassName__test
。
-
特殊方法
__test__
:- 通常是 Python 的魔术方法(magic method),具有特定的用途。
- 可以从类外部直接访问。
- 不会被名称改写机制处理。
继承
【例子】如果子类中定义与父类同名的方法或属性,则会自动覆盖父类对应的方法或属性。
import random
class Fish:
def __init__(self):
self.x = random.randint(0, 10)
self.y = random.randint(0, 10)
def move(self):
self.x -= 1
print("我的位置", self.x, self.y)
class GoldFish(Fish): # 金鱼
pass
class Carp(Fish): # 鲤鱼
pass
class Salmon(Fish): # 三文鱼
pass
class Shark(Fish): # 鲨鱼
def __init__(self):
self.hungry = True
def eat(self):
if self.hungry:
print("吃货的梦想就是天天有得吃!")
self.hungry = False
else:
print("太撑了,吃不下了!")
self.hungry = True
g = GoldFish()
g.move() # 我的位置 9 4
s = Shark()
s.eat() # 吃货的梦想就是天天有得吃!
s.move()
# AttributeError: 'Shark' object has no attribute 'x'
解决该问题可用以下两种方式:
- 调用未绑定的父类方法
Fish.__init__(self)
- 使用super函数
super().__init__()
类、类对象和实例对象
类对象:创建一个类,其实也是一个对象也在内存开辟了一块空间,称为类对象,类对象只有一个。
实例对象:就是通过实例化类创建的对象,称为实例对象,实例对象可以有多个。
类属性:类里面方法外面定义的变量称为类属性。类属性所属于类对象并且多个实例对象之间共享同一个类属性,说白了就是类属性所有的通过该类实例化的对象都能共享。
class MyClass:
# 类属性
class_attribute = "I am a class attribute"
def __init__(self, instance_attribute):
# 实例属性
self.instance_attribute = instance_attribute
# 创建两个实例
obj1 = MyClass("Instance 1 attribute")
obj2 = MyClass("Instance 2 attribute")
# 访问类属性
print(MyClass.class_attribute) # 输出: I am a class attribute
print(obj1.class_attribute) # 输出: I am a class attribute
print(obj2.class_attribute) # 输出: I am a class attribute
# 修改类属性
MyClass.class_attribute = "Class attribute changed"
# 再次访问类属性
print(MyClass.class_attribute) # 输出: Class attribute changed
print(obj1.class_attribute) # 输出: Class attribute changed
print(obj2.class_attribute) # 输出: Class attribute changed
# 访问实例属性
print(obj1.instance_attribute) # 输出: Instance 1 attribute
print(obj2.instance_attribute) # 输出: Instance 2 attribute
# 修改实例属性
obj1.instance_attribute = "Instance 1 attribute changed"
# 再次访问实例属性
print(obj1.instance_attribute) # 输出: Instance 1 attribute changed
print(obj2.instance_attribute) # 输出: Instance 2 attribute
-
类属性定义 :
class_attribute
是类属性,它在类的主体中定义,不在任何方法内。 -
实例属性定义 :实例属性
instance_attribute
是在__init__
方法中定义的,这些属性属于各自的实例。 -
共享类属性:所有实例对象都可以访问类属性,并且类属性的修改会影响所有实例对象。
-
独立实例属性:实例属性是独立的,每个实例对象有自己的一份实例属性,修改一个实例的实例属性不会影响其他实例的实例属性。
class A(object):
def init(self, value):
print("into A init")
self.value = valuedef __new__(cls, *args, **kwargs): print("into A __new__") print(cls) return object.__new__(cls)
class B(A):
def init(self, value):
print("into B init")
self.value = valuedef __new__(cls, *args, **kwargs): print("into B __new__") print(cls) return super().__new__(cls, *args, **kwargs)
b = B(10)
结果:
into B new
<class 'main.B'>
into A new
<class 'main.B'>
into B init
class A(object):
def init(self, value):
print("into A init")
self.value = valuedef __new__(cls, *args, **kwargs): print("into A __new__") print(cls) return object.__new__(cls)
class B(A):
def init(self, value):
print("into B init")
self.value = valuedef __new__(cls, *args, **kwargs): print("into B __new__") print(cls) return super().__new__(A, *args, **kwargs) # 改动了cls变为A
b = B(10)
结果:
into B new
<class 'main.B'>
into A new
<class 'main.A'>
若__new__没有正确返回当前类cls的实例,那__init__是不会被调用的,即使是父类的实例也不行,将没有__init__被调用。所以这没有调用__init__
属性访问
__getattr__(self, name)
: 定义当用户试图获取一个不存在的属性时的行为。__getattribute__(self, name)
:定义当该类的属性被访问时的行为(先调用该方法,查看是否存在该属性,若不存在,接着去调用__getattr__
)。__setattr__(self, name, value)
:定义当一个属性被设置时的行为。__delattr__(self, name)
:定义当一个属性被删除时的行为。
迭代器
- 迭代是 Python 最强大的功能之一,是访问集合元素的一种方式。
- 迭代器是一个可以记住遍历的位置的对象。
- 迭代器对象从集合的第一个元素开始访问,直到所有的元素被访问完结束。
- 迭代器只能往前不会后退。
- 字符串,列表或元组对象都可用于创建迭代器:
- 生成器可以用来实现复杂的逻辑,例如产生无限序列或处理大型数据流而不会一次性将所有数据加载到内存中。
主要参考资料:阿里云《龙珠训练营》 AI学习课程 AI学习 - 阿里云天池
使用ChatGPT做学习