python入门笔记(二)--用魔法成为py的管理者

1.基本语法的后续

1.1函数的递归使用

 递归实际上只是一个函数调用自己,它实际上是算法范畴,如果能设计出一个好的递归,那就可以用寥寥数行代码来解决一些非常复杂的问题,在此举一个简单的例子来帮助大家理解(如果想深入学习可以看相关的算法书):

def function(m):
	if m==1 or m==2:
		return 1
	else:
		return function(m-1)+function(m-2)

上述递归就输出了斐波那契数列的第m项,需要注意的是,递归对内存空间的占用较大(取决于递归的层数),因此应该注意优化


1.2 字典

  字典是python的的一大利器,他和json差不多,其中有键(key)和值(value)两个属性,实际上,key其实就是列表的索引,只是不再是以数据的形式,而值就是内容,需要注意的的是,他并不是一种序列,而是映射。

1.2.1 字典的基本语法

  用{}表示字典,键和值用:隔开

dict_modle={"我":"swttt.",1:"one"}

如图,其中"我"就是字典的一个键,而swttt.就是他的值,索引方式为dict_modle[“我”],输出的结果就是swttt.

1.2.2 函数dict的一些用法

  dict是一个将对象转换成字典的工厂函数,同时也是一个类(关于类与对象的相关知识可参考其他资料),他的用法非常多,可以在python的idle中利用help查查或者在官方文档里看看,这里举几个简单实用的:

  • 可以利用dict函数将列表或者元组直接转化成一个字典,比如
dict_modle=dict([[1,'one'],[2,'two']])

这便生成了1对应one,2对应two的字典,元组同理,需要注意dict只有一个参数,因此要将所有小列表放入一个列表中

  • dict_modle.fromkeys(a[,b]),内置方法fromkeys,作用是窗含其中参数a需要是一个序列,并返回一个新的列表,原列表并没有变化,b是可选参数,表示新列表中每个键的值,默认为None
  • dict_modle.keys(),返回字典中的所有键
  • dict_modle.values(),返回所有的值
  • dict_modle.items(),返回每一项
  • dict_modle.get(dict_modle[1],b) 用get试图访问字典中的一个项,假如项不存在是不会报错的,默认返回None,b依然是可选参数,假如项不存在则返回b
  • 可以用 in 查找是否有相应的键在字典中
  • dict_modle.clear() 清空字典
1.2.3字典的更新

  直接使用赋值语句:

dict_modle['one']='oneday'

此时,假如one这个键一件存在,那么oneday就会替换原来的值,反之则将创建一个


1.3集合(set)

  语法和字典相同,用{}表示,假如括号内没有体现出映射关系,则这就是一个集合,而非字典,需要注意的是,集合的内容具有唯一性,同时并不支持索引


1.4文件

对于语言而言,文件处理是不可少的一环,直接将某个文件读入并进行操作极大的提升了效率

1.4.1open函数

  python提供了文件处理的函数open,语法规则比较复杂(但多为可选参数),在此介绍使用时的常用参数:
在这里插入图片描述
如图即为python官方文档对open函数的定义,在此介绍三个重要参数:

  • 第一个参数为file参数,官方解释如下:
    在这里插入图片描述
    file实际上就是提供要打开的文件名,当不在一个目录下时需要提供相应的路径(路径的反斜杠注意转义),这是open函数中必不可少的参数

  • 第二个参数名为mode,即打开的模式,将要对文件进行的不同操作决定了打开的模式,默认为‘r’模式:
    在这里插入图片描述
    鉴于官方文档较为生涩,故重新提供一份表格以供参考

打开模式效果
r默认参数,以只读的方式打开文件,无法修改
w以写入的方式打开文件,如果文件不存在,系统将会重新创建,若已经存在,系统将会清空原文件的内容
x创建并打开新的文件,假如文件已经存在会引发异常
a写入模式,和追加补充文件内容
b二进制模式打开,假如我们需要打开一张图片,可与其他模式混合使用,如wb
t文本模式,即默认模式
+读写模式,也是混合使用
  • encoding 参数,多用于有中文的数据,常见的编码有utf-8,gbk等
1.4.2文件对象

  实际上,打开了文件后需要用一个变量(此处用f表示)来保存文件对象,以方便使用相关方法,如写入应该使用write(),也可以使用tell()查找文件位置等等,在此不多介绍
在这里插入图片描述
特别值得一提的是fclose(),文件使用完后应及时关闭(良好习惯)。

1.4.3 特殊语法

  前面说了,需要对文件的关闭,但是当代码多了之后非常容易忘记,因此更推荐使用此处特殊的语法,不用关闭文件,语法结构如下(f保存文件对象):

with open() as f

1.5 模块

 模块的含义(个人理解)其实就是python的功能拓展(封闭好的程序),就像c语言里的头文件一样,实现一些特殊的功能,python可以从外部引入模块,实际上,多个模块的集合又称为包(package),python模块千千万,在此就不再详细介绍,常见的有os,re,random,等等


1.6异常处理

  在一段代码的时候,异常是必不可少的,python为了解决这一问题提供了一套try语句,语法如下
try:
  语法内容
except 错误类型(oserror等等) 【as reason】:(exept可有多个)
  出现对应错误的异常时执行的代码,reason实际上保存的就是错误的提示信息,可以强制转换为字符串后输出。
finally:(可省略)
  必定会被执行的代码


1.7 强大的else语句

 在python中,else语句并不是只能与if搭配,他也可以与循环搭配,甚至与上述的try搭配。

  • 与循环搭配,当循环的条件不成立时,系统将执行else语句:
 while False:
	a=100
else:
	a=200

此时a的值为两百,注意,else属于循环体的一部分,用break可以跳出

  • 与try搭配,else下的语句将在except语句没有成立的情况下进行,此处不再举例

2.类和对象(详解)

  python是一门面向对象的编程语言,然而到底什么是对象,什么又是类,此处将做一个详细的说明。

2.1 面向对象的结构

 python对象其实就是内存中所占的一块区域,面向对象则有三部分构成

  • id,id实际上指的就是对象的内存地址,由解释器生成
  • type,指的是对象的类型,不同类型的对象有不同的功能,如int,float等等
  • value,对象中的数据
2.2 类与对象的概念

 对象(但并非全部)其实是由类实例化而来,这些由类生成的对象称为类的实例,基本的数据类型,如int,其实就是一个python内置的类型,由此可见,类实际上就是一张图纸,而实例化后的对象,才是工具

2.3 类与对象的创建

 在解决问题时,系统所内置的类往往不足以解决问题,因此,我们可以去自定义一个类:

class Modle_class():  #类名要以大写开头
	ten=10
	def fun(self):
		print('have a great time')

以上就是一个类的定义,需要说明的是,类中的变量称为属性,即上例中的ten,而函数称为方法,即上例中的fun,在每一个函数中,都需要加上一个默认的参数self,其相当于一个定位符,区分出实例化后的每一个对象(下面会再次提到)
 其次是类的实例化:

>>> a=Modle_class()  #实例化
>>> b=Modle_class()
>>> a.ten=9
>>> print(a.ten)
9
>>> a.nine=10 #给实例增加了一个属性
>>> print(a.nine)
10
>>> print(Modle_class.ten)
10
>>> print(b.ten)
10
>>> a.fun()
have a great time
>>> print(Modle_class.nine)
此处将报错因为类定义以及后续中并不存在nine属性

如上述所示,a,b都是该类的一个实例对象,然而很明显,a,b之间并没有任何联系(虽然两者都是来自同一个类,但是确实相对独立的,这也就是self的区别作用,实际上self所给到的就是实例对象的地址,也就是说,self的实参实际上是这个方法的调用对象),a可以自己添加新的属性,或是修改原有属性,这对b,对原来的类,并没有造成任何的影响,因此,上述的最后一个print将会报错

2.4 面向对象的三大特性
2.4.1 多态性:为了使代码更加灵活高效

 多态性实际上非常好理解,上述的a,b的实例化例子其实也就体现出了多态性,一个类有多个实例可以分别的利用它,同时,假如一个函数非常具有局限性,那么显而易见的是这个函数是并不符合多态性的。举一个例子:

>>> class Model_class():
		saywords="hello world"
>>> def fun(obj):
		print(obj.saywords)
>>> a=Model_class()
>>> fun(a)
hello world
>>> b=100
>>> fun(b)
>报错

显而易见,fun函数的参数只能用于来自Modle_classd的实例,这样的函数非常具有局限性,不符合多态性。


2.4.2 封装性:为了使代码更加安全以及更高效

 我们平常经常会使用import来将其他py模块定义到我们的代码中,以使用其中的函数,实际上,这些函数模块就是提早封装好的,他们能实现某些功能,想用的时候直接声明一下就行了
 其二就是为了代码的安全性,在开发中,总有一些并不希望外部看见的属性甚至是方法,我们通常设置一个函数来让外部间接的访问数据或者是有意的去修改属性名称仍然举个例子:

>>> class A():
	hidden_name=None
	def get_name(self):
		print(self.hidden_name)
	def set_name(self,new_name):
		self.hidden_name=new_name 
'''
一个注意点,hidden_name
前面的self必不可少,因为
每个实例都有一个不同的名字
'''
		
>>> a=A()
>>> a.set_name('ton')
>>> a.get_name()
ton

如上图,类中对名字(name)加了一个特殊的前缀,使得别人并无法访问到函数的name变量。
 实际上,python有一个特殊的技术,名为name mangling ,意为名字改编,也就是在函数属性名的前面加上__(两条下划线),这时属性就会就会变的私有。例子如下:

>>> class A():
	__name='ton'

>>> a=A()

此时无论是a.__name,或者是a.name 都无法得到ton,因为name已经变成了类中的一个私有属性,理论上要在类中设置一个访问函数,但是其实__name也就是等于_类名_属性名,此处也就是_A_name


2.4.3 继承性:为了让代码更具有扩展性

 在实际的使用类时,两者类很可能是有一部分相通性的,就比如猴子和猩猩虽然是两个物种,实际上却有着相似的地方,此时假如要写两次相差无几的类,很明显是非常浪费时间的,故而引进了继承性:


>>> class A():#父类
	name='ton'
	old='20'
	def fun(self):
		print('in the class a')
	def fun2(self):
		print('have a great time')

		
>>> class B(A):#子类
	old='15'
	def fun(self):
		print('in the class b')

		
>>> b=B()
>>> b.name
'ton'
>>> b.old
'15'
>>> b.fun()
in the class b
>>> b.fun2()
have a great time

可以观察到,class b(子类)继承了a(父类),语法也就是括号内加上父类名,b将会继承a中的所有属性与方法,假如b中定义到了与a重名的属性或方法,将会把a的方法覆盖,注意:python允许多重继承,也就是括号内还可以加上c,但并不建议这样做,非常容易出现错误。
由于父类的内容可能被子类覆盖,因此python提供了两种方法来访问原父类的内容:调用未绑定的父类方法以及super函数(这两个内容在魔法方法中具体解释)

2.5 魔法方法——‘管理者’密匙
2.5.1魔法方法的定义

所谓的魔法方法,其实指的就是在类中存在一些特殊的方法,这类方法的名字以两条下划线开头,同时以两条下划线结尾,他们不需要人为的调用,一些bif就是魔法方法(如int()对应着的是__int__()),同时python中的一些运算符的内核也是魔法方法(比如你可以通过修改让减号去执行加法的功能),再配合上述的类的定义,所有的内容都可以由自己定义,故言进入了“管理者模式”

2.5.2 类中的魔法方法的执行顺序

当一个类中既有着魔法方法又有着其他语句以及普通方法时,在类对象创建时他们的执行顺序又是如何的呢?

1. (类的定义结束后)执行类中的代码块的代码(不包括魔法方法),并且只在类中执行一次
2. (实例化时)创建了一个变量(非必须);
3. 在内存中创建了一个新的地址;(并将变量指向该对象)
4. 满足条件的魔法方法自动执行。(普通方法并不执行)
举个简单的例子:

>>> class Modle_class():
	def __init__(self):
		print("hello word")
	print("it is a test")
	def fun(self):
		print("it can not be load automaticly");

		
it is a test 
'''
此处写完类的内容后,
执行了print(it is a test),并且只会执行一次
'''
>>> a=Modle_class()
hello word #__init__是最常见的魔法方法(多用于初始化),此处可看出他的内容自动执行了
>>> Modle_class()
hello word
<__main__.Modle_class object at 0x00000202C0023508>
#此处就没有变量名保存对象的内存位置
>>> Modle_class()
hello word
<__main__.Modle_class object at 0x00000202BFC17C88>
2.5.3 初始化以及构造与析构
  • 初始化:在上述已经有所提及,就是上例中的init方法,在实例化的同时将自动执行其中所包含的代码,因此一般用作初始化,并且这个方法返回的永远是None。
    在这里值得一提的是,假如定义的一个新类是有父类的(同时父类存在__init__方法),但是此时需要对新类的init方法进行重定义,那么将会覆盖原来的内容,对代码功能造成一些非常隐晦的影响,举一个例子:
>>> class People():
	def __init__(self):
		self.tall=180
		self.weight=150

		
>>> class Man(People):
	def __init__(self):
		self.body_fat_rate=0.18

		
>>> a=Man()
>>> a.tall
Traceback (most recent call last):
  File "<pyshell#14>", line 1, in <module>
    a.tall
AttributeError: 'Man' object has no attribute 'tall'

上例中,a已经只有body_fat_rate一个属性,而我们的本意是让Man通过继承具备三种属性,解决办法如下:

>>> class Man(People):
	def __init__(self):
		People.__init__(self)
		self.body_fat_rate=0.18
#调用未绑定的父类方法
		
>>> a=Man()
>>> a.tall
180
-------------------
#此处为推荐用法
>>> class Man(People):
	def __init__(self):
		super(Man,self).__init__() #py3.0以上可用super().__init__()
		self.body_fat_rate=0.18
...
super函数的使用
super的作用实际上就是找到当前的父类
并将当前的对象转换为父类的对象
若仅仅是单层的继承与另一种方法无异
但是如果使用多继承,会涉及到查找顺序(MRO)、重复调用(钻石继承)等种种问题。
...
		
>>> a=Man()
>>> a.tall
180

  • 构造:python在定义类时,实际上有一个默认的也是第一个执行的魔法方法__new__(cls,【,其他参数】),他的作用是开启一块新的内存区域(在自身实例化时自动调用),因此,他的执行在变量的出现之前,参数中cls指的是类(new方法的返回值是一个类),我们极少去回去重写这个方法,因此稍做了解即可,若需深究可另外查阅相关资料

  • 析构:不知道你有没有思考过这样一个问题,我们不停的创造新的变量,开辟新的内存,然而我们从来没有去手动释放过内存,那么会不会存在某个时候内存会被全部使用完呢?实际上,在python中,存在一个垃圾处理机制,当一个对象并没有任何变量指向它时,python将自动回收对应的内存。这就是__del__(self)方法,举例如下:
>>> class A():
	def __del__(self):
		print('delete!')

		
>>> a=A()
>>> b=a #实际上abc指向了同一个对象
>>> c=b
>>> del a
>>> del b
>>> del c
delete!
>>> 
2.5.4 对象的算数运算

在2.5.1中已经有所提及,运算符的内核实际上就是魔法方法,注意:此处是针对对象而言,部分规则如下(简单列举,若需要完整表格可查阅资料):

魔法方法功能
__add__(self,other)+
__sub__(self,other)-
__mul__(self,other)*
__truediv__(self,other)/
__floordiv__(self,other)//
__pow__(self,other)**
__and__(self,other)&
__radd__反向+

举个小例子以帮助理解:

>>> class Fun(int):
	def __add__(self,other):
		print("the method add is going")
	def __sub__(self,other):
		return super().__mul__(other)
	def __pow__(self,other):
		return self**other
	def __radd__(self,other):
		return int.__sub__(other,self) #此处真正实现的是相对位置不动的减法,注意self与other的位置关系
>>> a=Fun(5)
>>> b=Fun(3)
>>> a-b
15
>>> a+b
the method add is going
>>> 1+b(实现的是加法变减法)
-2
...
在一般情况下,都是以a为主导启动魔法方法,比如a.__add__(b)。
然而有时存在一些特例,比如上述的1+b,此时1是一个常量
因此调用的是反向的加法(个人理解),也就是b.__radd__(1),但根据代码(加法变减法),最终执行的是
1.__sub__(b),注意参数位置的变化
触发条件的官方文档说明(个人理解翻译):
左边的操作数并不支持相应的函数
或者是两个操作数是不同类型
又或者是左边的操作数执行正操作是失败的(返回NotImplemented....

其中,fun类还重写了pow,但是并没有调回父类的方法,实际上这种写法是错误的,当出现a**b的时候将会无限递归(因为Fun类的pow方法已经被重写,此时**的作用是由你自己定义的)

2.5.5 访问类属性时触发的魔法方法

在访问类中的属性时,其实‘幕后主使’也是魔法方法,对应如下,并请仔细看例子:

魔法方法功能
__getattr__(self, name)定义当用户试图获取一个不存在的属性时的行为(name指的是变量名,下同)
__getattribute__(self, name)定义当该类的属性被访问时的行为
__setattr__(self, name, value)定义当一个属性被设置时的行为(value指的是变量的值)
__delattr__(self, name)定义当一个属性被删除时的行为
'''
本例中,试图访问一个变量会打印find
若变量不存在打印missing
改变一个变量打印setting
删除一个变量打印delete
'''
>>> class Modleclass:
	def __getattr__(self,name):
		print('missing!')
	def __getattribute__(self,name):
		print('find')
		super().__getattribute__(name)
	def __setattr__(self,name,value):
		print('setting')
		super().__setattr__(name,value);
	def __delattr__(self,name):
		print('delete');
		super().__delattr__(name)

'''
千万千万注意应用super重置对应函数的功能
否则魔法方法对应的操作将会失效
如上例中访问不存在的变量时已经不会报错
因为getattr并未用super重置
'''
>>> a=Modleclass()
>>> a.x #注意访问时getattribute优先执行
find
missing!
>>> a.x=1
setting
>>> a.x #getattribute执行成功时不再执行getattr
find
>>> del a.x
delete
>>> a.x
find
missing!
>>> 
2.5.6 描述符(装饰器)及其原理

在python中,可以利用描述符,像访问属性一样去访问方法(更好的安全性)

>>> class Modleclass:
	def __init__(self):
		self._secret=1
	@property
	def printf(self):
		print('the method is going ')
		return self._secret

		
>>> a=Modleclass()
>>> a.printf
the method is going 
1

实际上,描述器也是一种类,并且这个类有四个参数,他的原理也就是类的嵌套,也就是在类中再定义了一个类。上例并非是描述器的典型用法,请仔细看下例:

'''
ps:文字抽象,可结合例子以及文档理解
描述器的定义如下(取自官方文档)
class property(fget=None, fset=None, fdel=None, doc=None) 
实际使用时,我们往往会将定义在外层的类(本例中为Modelclass)中的方法作为参数传入
之后对property类进行实例化,并给予一个变量保存
也就是说,参数fget实际上是一个做访问操作的普通方法
当property的对象出现访问操作时
自动调用property中的魔法方法__get__()去执行你传入的方法(fget)
fset同理,对应着__set__(),fdel对应着__delete__()
现在出现了一个疑问,__get__和__getattribute__两个方法
不都是定义着访问对象的行为吗,那两者的区别又是什么?
实际上,get方法是专门为这种嵌套做法定制的魔法方法,
他的必带参数就包含着本身的实例(本例为x),外部类的实例(a),以及拥有他的类(外部类Modleclass)。
官方文档如下:
object.__get__(self, instance, owner) 
另外两种魔法方法(set与delete)大致相同,若需了解可查阅文档
'''
>>> class Modleclass:
	def __init__(self):
		self._secret=1 
	def get(self): #定义访问函数的内容
		return self._secret
	def seet(self,value): #定义修改函数
		self._secret=value
	def delete(self):
		del self._secret
	x=property(get,seet,delete) #将三个方法作为参数传入,并将x作为property的实例化对象

	
>>> a=Modleclass()
>>> a.x  #实际上执行的就是a.x.get()
1
>>> a.x=2
>>> a.x
2
>>> a._secret
2
#实际上,a._secret和a.x是同一个对象,访问a.x是间接得访问了a._secret
#第四个参数指的是文档字符串

到了这里,你可能会觉得第一个例子与第二个例子似乎不是同一个东西,第一个例子中看似甚至没有对property进行实例化,实则并不是这样,@property是定义了访问操作(fget),并将方法名作为实例对象,
也就是说,在第一个例子中,printf就是对象,另外两个行为的添加如下:

>>> class Modleclass:
	def __init__(self):
		self._secret=1
	@property
	def printf(self):
		print('the method is going ')
		return self._secret
	@printf.setter 
	def printf(self,value): #对象即是方法名
		self._secret=value
	@printf.deleter
	def printf(self):
		del self._secret

2.5.7 容器内的魔法方法

上面介绍了一些普通对象的魔法方法,而用来存放对象的类型也可称之为容器,那么譬元组,列表这些容器,又是如何工作的呢?实际上,他们的访问,修改也是依赖着魔法方法:

魔法方法功能
__len__(self)len()被调用时的行为 (返回容器内的个数)
__getitem__(self,key)定义访问容器内元素时的行为
__setitem__(self,key,value)定义修改元素的行为
__delitem__(self,key)删除容器元素的行为
__iter__(self)定义迭代容器内的元素的行为,并返回迭代器
__reversed__(self)调用reversed()时的行为
__contains__(self,item)调用测试运算符的行为

其中,迭代的定义有点难以理解,我个人理解为类似于数据结构中的链表,实际上,列表List、元组Tuple、字典Dictionary、字符串String等数据类型都是可迭代的也就是得益于__iter__()这个魔法方法, __iter__()和__next__()两个魔法就是for循环的内核所在

2.5.8 关于可迭代对象和迭代器
  • 可迭代 (iterable):如果一个对象具备有__iter__() 或者 __getitem__()其中任何一个魔术方法的话,这个对象就可以称为是可迭代的。
  • 迭代器 (iterator): 如果一个对象同时有__iter__()和__next__()魔术方法的话,这个对象就可以称为是迭代器。(for的原理)

区别在于迭代器的iter返回了本身,而容器的iter返回的是一个迭代器。

迭代器工作原理如下(取自py文档):对迭代器的_next__()方法的重复调用(或将其传递给内置函数next())将返回流中的连续项。当没有更多的数据可用时,取而代之的是一个StopIteration异常。此时,迭代器对象已经用完,任何对它的_next__()方法的进一步调用都会再次引发StopIteration。迭代器需要有一个返回迭代器对象本身的__iter__()方法,这样每个迭代器都是可迭代的,并且可以在大多数地方使用其他迭代器。

>>> string='12345'
>>> next(string) #可迭代对象是不具备next方法的
Traceback (most recent call last):
  File "<pyshell#195>", line 1, in <module>
    next(string)
TypeError: 'str' object is not an iterator
>>> it=iter(string) #iter函数返回一个迭代器对象
>>> next(it) #用next找到下一个元素,并用iter(魔法方法)返回本身
'1'
>>> next(it)
'2'
>>> next(it)
'3'
>>> next(it)
'4'
>>> next(it)
'5'
>>> next(it) #溢出
Traceback (most recent call last):
  File "<pyshell#203>", line 1, in <module>
    next(it)
StopIteration

如有错误请联系改正

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值