Python相关知识点
迭代器
__getitem__
当类中定义了该__getitem__方法时,该实例对象p可以实现通过p[key]进行取值操作。当该实例对象执行p[key]运算时,会调用定义在类中的__getitem__方法。
通常,当我们需要通过索引访问元素时,可以通过在类中定义这个方法(__getitem__(self, key))来实现功能。
还可以用在对象的迭代上
class STgetitem:
def __init__(self, text):
self.text = text
def __getitem__(self, index):
result = self.text[index].upper()
return result
p = STgetitem("Python")
print(p[0])
print("------------------------")
for char in p:
print(char)
# output
P
------------------------
P
Y
T
H
O
N
AI助手
__len__
如果一个类表现得像一个list,要获取有多少个元素,就得用len()函数。
要让 len() 函数工作正常,类必须提供一个特殊方法__len__(),它返回元素的个数。
例如,我们写一个 Students 类,把名字传进去:
class Students(object):
def __init__(self, *args):
self.names = args
def __len__(self):
return len(self.names)
AI助手
只要正确实现了__len__()方法,就可以用len()函数返回Students实例的“长度”:
>>> ss = Students('Bob', 'Alice', 'Tim')
>>> print len(ss)
3
AI助手
迭代器__iter__()和__next()__
__iter__(): 迭代器方法,用于在生成迭代对象的过程中调用**(仅在生成迭代对象的过程中调用一次)**,返回值必须是对象自身。通过for循环可以调用next方法逐次获取迭代内容;若对同一个对象生成两个迭代器实例,则这两个迭代器实例实际上是同一个对象,对其中一个实例进行修改,另一个实例也会相应地发生改变。
__next()__:每一次for循环都调用该方法(优先级高于索引__getitem__)
class A(object):
def __init__(self,num):
self.num = num
self.start_num = -1
def __iter__(self):
'''
@summary: 迭代器,生成迭代对象时调用,返回值必须是对象自己,然后for可以循环调用next方法
'''
print "__iter__"
return self
def __next__(self):
'''
@summary: 每一次for循环都调用该方法(必须存在)
'''
self.start_num += 1
if self.start_num >= self.num:
raise StopIteration()
return self.start_num
if __name__ == "__main__":
for i in A(10):
print i,
# >>>>>>>>>>>>输出>>>>>>>>>>>
__iter__
0 1 2 3 4 5 6 7 8 9
AI助手
for i in A:的本质
for i in A:
...
"""
本质是
1. 自动使用iter(A),执行A类的__iter__()方法为可迭代对象A创建一个迭代器tmp_iter
2. 每次循环(迭代)都调用一次next(tmp_iter)执行A类对应的__next__()方法将返回值赋予i
"""
AI助手
在深度学习中的运用
__getitem__:常用于数据加载任务中,实现对象的逐个访问。每次迭代时,会调用__getitem__方法来加载数据,以获取相应的数据样本。
__len__:主要应用于数据加载类(如Dataset)中,便于调用len()函数获取数据总量,从而方便计算一个epoch所需处理的batch数量。其主要作用是辅助性功能。
__iter__():常用于数据加载类(Dataset)中,固定写法返回self
__next__()和__getitem__()具有相似性。在深度学习中,__getitem__()主要依赖于数据加载器不断读取数据,而__next__()方法较少调用。
三者的区别**
- 都支持for循环执行迭代操作。
__getitem__用于可迭代对象的索引操作(如p[key]),同时也能用于for循环的迭代操作(但调用优先级低于__next__)。__iter__和__next__仅用于对象的迭代操作(如for循环),在调用时分别实现生成迭代器和执行迭代操作的功能。- 可迭代对象必须具备
__getitem__方法,或者同时支持__iter__和__next__方法,或者两者兼有。当两者同时存在时,在循环操作中,__next__方法的优先级高于__getitem__方法,因此只会调用__next__。
参考资料
实现getitem、迭代器迭代以及__next__方法的应用(具体实例,特别关注__getitem__)
迭代、可迭代对象,迭代器、生成器、装饰器及yield与return的区别
生成器
使用了 yield 的函数被称为生成器(generator)。
生成器是一种返回迭代器的工具,它仅支持迭代操作。更直观地说,生成器就是一个专门用于执行迭代操作的机制。
调用生成器运行的过程中,每次当遇到 yield 时,函数会暂停运行并保存所有当前运行状态,并返回 yield 的值,并在下一次调用 next() 时从当前位置继续执行。
调用一个生成器函数,返回的是一个迭代器对象 。
参考链接
该网页介绍了Python3中的迭代器与生成器(Generator)的相关知识。迭代器是一种用于遍历序列的工具,而生成器则是一种在Python中用于生成序列的高效工具,能够通过调用next()方法逐步提供其元素,而不预先计算整个序列。生成器的创建通常使用yield关键字,这使得生成器代码具有惰性执行特性。与迭代器相比,生成器在内存占用上更为高效,尤其适用于处理大型数据集或需要分阶段生成内容的场景。通过阅读该内容,可以更好地理解生成器的创建方式及其在实际编程中的应用。
装饰器
实现的技术前提
高阶函数:函数参数可以是函数名,或者返回值是函数名。
函数嵌套:在一个函数内部定义了另一个新函数。
闭包:在函数嵌套中,内部函数引用外部函数作用域变量(除全局变量外),则称内部函数为闭包。
装饰器,意为通过装饰(装饰函数)来强化function或Class的功能的一个函数。即为,它通过前后处理来实现对function的增强,而无法直接修改function的内部结构。主要进行前后处理,而无法直接修改function的内部结构。
使用方法
假设decorator是定义好的装饰器。
方法一:不用@符号
# 装饰器不传入参数时
f = decorator(函数名)
# 装饰器传入参数时
f = (decorator(参数))(函数名)
AI助手
方法二:采用@符号
# 已定义的装饰器
@decorator
def f():
pass
# 执行被装饰过的函数
f()
AI助手
装饰器可以传参,也可以不用传参。
自身不传入参数的装饰器(采用两层函数定义装饰器)
def login(func) :
def wrapper(*args ,**kargs):
print( '函数名:%s '% func. __name__)
return func(*args,**kargs)
return wrapper
@login
def f():
print( " inside decorator ! ')
f()
# 输出:
# >>函数名:f
# >>函数本身:inside decorator !
AI助手
自身传入参数的装饰器(采用三层函数定义装饰器)
def login(text):
def decorator(func) :
def wrapper(*args,**kargs ):
print( "%s----%s "%(text, func.__name__))
return func(*args ,**kargs)
return wrapper
return decorator
# 等价于=->( login(text))(f)==>返回wrapper
@login( 'this is a parameter of decorator' )
def f():
print( ' 2019-06-13')
#等价于-=>(login(text))(f)()==>调用wrapper()并返回f()
f()
# 输出:
# => this is a parameter of decorator-- --f
# =>2619-e6-13
AI助手
本质上@就是用装饰器来装饰@下方的那个函数
内置装饰器:
@property
把类内方法当成属性来使用 ,必须要有返回值,相当于getter;
假如没有定义 @func.setter 修饰方法的话,就是只读属性,不可修改。
"以下就是将类内方法,装饰成属性值(属性值就是类内方法的返回值)"
class Car:
def __init__(self, name, price):
self._name = name
self._price = price
@property
def car_name(self):
return self._name
# car_name可以读写的属性,以下是固定写法,如果不加,car_name就是一个只读属性
@car_name.setter
def car.name(self, value):
self._name = value
# car_price是只读属性
@property
def car_price(self):
return str(self._price) + '万'
benz = Car('benz', 30)
print(benz.car_name ) # benz
benz.car_name = "baojun'
print(benz.car_name ) # baojun
print(benz.car_price) # 30万
AI助手
@classmethod
类方法 ,不需要self参数,但第一个参数需要是表示自身类的cls参数 。
@staticmethod
静态成员方法,无需通过自身对象的self属性或自身类的cls属性来获取,类似于函数的行为。
总结:实际上,这等同于将类方法转换为可以直接通过类调用的函数,等同于C++定义静态成员函数。
不同点:
@staticmethod不需要传入任何参数,但在类方法内部仍需要明确指定类名。
@classmethod需要传入一个名为cls的参数,并且在类方法内部可以直接使用cls来代替类名。
相比于@staticmethod,@classmethod在使用上更为便捷,尤其是在类名较为冗长的情况下,可以显著简化代码书写。
class Demo(object):
text = "三种方法的比较"
def instance_method(self):
print("调用实例方法")
@classmethod
def class_method(cls):
print("调用类方法")
print("在类方法中 访问类属性 text: {}".format(cls.text))
print("在类方法中 调用实例方法 instance_method: {}".format(cls().instance_method()
@staticmethod
def static_method():
print("调用静态方法")
print("在静态方法中 访问类属性 text: {}".format(Demo.text))
print("在静态方法中 调用实例方法 instance_method: {}".format(Demo().instance_method
if __name__ == "__main__":
# 实例化对象
d = Demo()
# 对象可以访问 实例方法、类方法、静态方法
# 通过对象访问text属性
print(d.text)
# 通过对象调用实例方法
d.instance_method()
# 通过对象调用类方法
d.class_method()
# 通过对象调用静态方法
d.static_method()
# 类可以访问类方法、静态方法
# 通过类访问text属性
print(Demo.text)
# 通过类调用类方法
Demo.class_method()
# 通过类调用静态方法
Demo.static_method()
AI助手
classmethod与staticmethod
这两个方法具有相似性,在多数情况下,classmethod也可替代使用staticmethod,在类调用时,两者对调用者的作用是相同的。
在两者的区别中,classmethod被引入了一个对被引用对象的引用,这带来了显著的优势。
该方法能够区分自己是通过基类调用还是某个子类调用。
当通过子类调用时,该方法将返回子类实例而非基类实例。
当通过子类调用时,该方法支持调用子类的其他classmethod。
通常情况下,classmethod可以完全替代staticmethod。然而,staticmethod的一个显著优点在于,当调用时它返回的是一个真实的函数对象,而且每次调用都会返回同一个实例(而classmethod则会分别返回基类和子类的bound method实例)。实际上,这一点在大多数情况下几乎从未被利用过。尽管如此,staticmethod和classmethod在某些特定场景下是可以相互替代的,因此建议根据具体需求灵活使用,无需将staticmethod全部改为classmethod,按需要使用即可。
参考资料
如何理解Python装饰器?(很详细,直接看原博即可)
在Python中,classmethod和staticmethod各自具备哪些具体功能?对于用户而言,目前仍存在一些困惑,认为这两个概念(classmethod和staticmethod)在功能上具有相似性,类似于静态方法。然而,classmethod相较于staticmethod具有显著的优势,即其没有硬编码的限制,因此在某些场景下更为灵活和方便。
*args和**kargs的区别和使用
这一项是必要的,后续的参数可以采用其他名称,而默认情况下则使用当前的名称。
*args为可变位置参数 ,传入的参数会被放进元组 里。
**kargs为可变关键字参数 ,传入的参数以键值对的形式存放到字典 里。
def f(a, *args):
print(a, args)
f(1,2,3,4,5)
# output------
1 (2, 3, 4, 5)
AI助手
def f(**kargs):
print(kargs)
f(a=1,b=2)
# output------
{'a': 1, 'b': 2}
AI助手
*号的作用其实与上面的说明相反,如下例子所示:
*将元组转换为多个单元素
**将字典去除key,留下的value变成多个单元素
def f1(a,b,c):
print(a,b,c)
args= (1,2,3)
f1(*args)
def f2(a,b,c):
print(a,b,c)
kargs = {
'a':1,
'b':2,
'c':3
}
f2(**kargs)
1 2 3
1 2 3
AI助手
参考链接
在Python编程中,函数参数中的*args和**kargs具体是哪些内容?它们各自的作用机制又是什么?
enumerate()
在for循环中,将一个可遍历的数据对象(如列表、元组或字符串)转换为一个索引列表,同时列出数据及其对应的索引位置。(普通的for循环仅返回数据值,而这种for循环会返回每个值的索引位置。)
>>>seq = ['one', 'two', 'three']
>>> for i, element in enumerate(seq):
... print i, element
...
0 one
1 two
2 three
AI助手
可调用类型
为什么可以用model(input)而不是model.forword(input)?
可调用类型:若该类定义了__call___(self, param)函数,则该类的对象即可被当作函数使用,同时默认调用的就是__call__()函数。
在PyTorch框架中,nn.Module类的__call__方法内部实现了其forward方法的功能,即当模型被调用时,model(input)等效于执行model.forward()。
class A():
def __call__(self, param):
print('i can called like a function')
print('传入参数的类型是:{} 值为: {}'.format(type(param), param))
res = self.forward(param)
return res
def forward(self, input_):
print('forward 函数被调用了')
print('in forward, 传入参数类型是:{} 值为: {}'.format( type(input_), input_))
return input_
a = A()
input_param = a('i')
print("对象a传入的参数是:", input_param)
AI助手
list和np.array的区别
List: 列表
在 python 中,list 被视为内置数据类型。list 内的数据类型可以是多样的。list 中存储的是数据的存储位置,即以指针形式存储,而非直接存储数据。
array:数组
array() 是 numpy 包 中的一个函数,array 里的元素都是同一类型 。
ndarray:
由多维数组构成的对象,支持高效的矢量运算和复杂的广播机制,同时具备快速执行和高效内存占用的特性。
ndarray 的一个特点是同构 :即其中所有元素的类型必须相同 。
NumPy 的 array() 函数能够将 Python 的任何序列类型转换为 ndarray 数组。
赋值、浅拷贝、深拷贝
- 直接赋值:本质上是对象的引用(别名)。
- 浅拷贝(copy):通过拷贝父对象的方式进行操作,不会复制父对象内部的子对象。
- 深拷贝(deepcopy):使用deepcopy方法,通过copy模块实现了对父对象及其所有子对象的完全复制。
同C++。
浅拷贝:通过重新分配内存并创建新对象,其中元素来自原对象的各个子对象的引用。这种操作不会复制对象的内容,因此不会影响原对象的状态。
深拷贝:重新分配一块新的内存空间,生成一个新的对象实例,并通过递归机制,将原对象中的每个元素逐一复制到新对象中,包括生成新的子对象来完成拷贝。
为新对象预留了一块独立的内存空间,并将原对象的所有元素逐一复制到该新对象中。
函数参数的传递形式
本质上都是值传递。
当实际参数的数据类型为可变对象(如列表或字典)时,函数参数的传递方式将采用引用传递方式。引用传递方式的底层实现仍采用值传递方式,传递的是引用变量,引用变量指向原对象,修改该引用变量会导致原对象发生相应变化。
如果希望函数修改某些数据,则可以将这些数据包装成列表、字典等可变对象,并将这些可变对象作为参数传递给函数。在函数内部,通过处理列表、字典等对象来修改它们,从而实现对这些数据的修改。
参考链接
super()用法
**super()**的主要目标是解决多层继承中查找父类的问题。在单层继承中,使用或不使用super都无所谓。然而,使用super()是一种良好的编程实践。通常,在我们需要调用子类方法时,我们才会使用super()。
**super()**的优势在于能够有效避免直接引用父类名称。主要应用于支持多重继承的场景,例如:
class C(P):
def __init__(self):
super(C,self).__init__() # 调用了父类P的构造函数
print 'calling Cs construtor'
# 等价于
class C(P):
def __init__(self):
P.__init__(self)
print 'calling Cs construtor'
AI助手
参考链接
import和from import的区别
import只能导入库,使用其成员时需要指明是哪个库。
导入时,from关键字已经明确标识了所属的库,后续操作中无需再指定所属库。
