python进阶语法
说明
补充MOOC上嵩老师的python语言程序设计课程未涉及的语法部分以及一些python相关基础,个人笔记。
一.交互式命令
python的一种使用方式是交互式,即写一行命令执行一行并出结果,就像matlab一样,可以当做一个简单的计算器,进行基本的四则运算和简单内置数学函数运算(如sin,cos,tan等,差不多就像所有编程语言里都带的一样,需要导入math包,math包提供对底层c函数库的访问);另外python可以向matlab一样清除当前的变量,有一些简单的命令:
命令 | 描述 |
---|---|
del 变量名 | 删除某个变量 |
_ | 下划线表示当前计算结果,就像matlab里的ans一样 |
同样就像matlab一样,脚本方式运行产生的变量也在当前工作空间,脚本运行结束后,在IDLE中仍能使用脚本中定义的变量,而函数中的变量(如果不是全局)就只是函数局部的变量,函数外不可用(这个与其他任何语言一样,很好理解)。
二.面向对象语法
这是我主要想补充的内容:
1.基本
-
基本概念
- 面向对象的基本概念与其他任何OOP编程语言类似,不作赘述
- 类变量与实例变量:类变量就像c++里的静态变量一样,它是属于类的,而不是某个实例对象的;实例变量则是一般实例对象的变量。在python中由于并不需要提前声明变量,所以实例变量都是在函数体里定义的,在类体里(函数外)定义的则都是类变量,为所有实例对象所共有。
-
类的定义
python中定义一个类很简单,如:
class Student: def __init__(self,name,id): self.name=name self.id=id Student.studentCount+=1 def introduce(self): print('my name is {}, my student id \ is {}'.format(self.name,self.id)) studentCount=0
- 类定义用关键字
class
,类名称之后加冒号:
就和函数开头一样,之后是类体 __init__
是构造函数,如果不写会给一个默认的无参构造函数- 类里面成员函数的第一个参数都是
self
,但是self
并不是一个关键字,它只代表实例化的类对象,可以用其他任何词代替(比如c++里的this
),但是惯例是用self
表示该实例 - 类变量定义在类体里函数外(如studentCount),为所有实例共有,使用时用
类名.类变量
的方法,如构造函数中Student.studentCount;实例变量使用时用实例名.实例变量名
的方法,如 self.name - 实例变量对类变量的覆盖。如果构造函数中不小心写成了self.studentCount+=1,则是对具体的一个实例里的studentCount递增1,这里其实是对类变量studentCount在具体的一个实例范围下递增1,相当于把类变量实例化了;而如果误写成self.studentCount=1,那就真是新定义了一个实例变量把类变量覆盖掉了。综上,严格按照上一点说明的方法使用类变量与实例变量是一个好习惯,不容易造成无意识的覆盖
另外需要补充一点的是,构造函数不能像c++一样提供多个版本(不能重载),这其实是所有函数的特性,如果定义两个同名函数,参数个数不一样,后面的函数定义会覆盖之前的;python提供函数“重载”的方法是通过默认参数及可变参数实现的
- 类定义用关键字
-
类的实例化
s1=Student('Li Hua',14131007) print(isinstance(s1,Student))
- 类的实例化。python用类名加括号生成类实例,会自动调用相应的构造函数
__init__
,如上述代码,生成一个Student类的实例,然后赋值给s1变量(python中对象的赋值是引用赋值,即s1与新生成的对象是同一个) - 通过
isinstance(变量名,类名)
判断变量是否为类的一个实例
- 类的实例化。python用类名加括号生成类实例,会自动调用相应的构造函数
-
类的使用
s1.introduce()#访问成员函数 print(s1.name)#访问实例变量 print(Student.studentCount)#访问类变量 print('共有{}位学生'.format(s1.studentCount))#通过实例对象访问类变量 s1.age=18 print("芳华{}".format(s1.age)) Student.school="BUAA" print("学校为:{}".format(s1.school)) del s1.age del Student.school
- python中成员函数的调用。用法为:
实例对象.函数名
,需要注意,虽然成员函数定义时第一个参数都是self
,但是调用的时候并不需要给定这个参数,它会自动把.
成员符之前的实例对象变量名作为第一个参数 - 类变量与实例变量的使用。通过
实例对象.实例变量名
访问实例变量,通过类名.类变量
访问类变量,这是我认为比较好的方法。当然也可以通过实例对象.类变量名
访问类变量,但是建议只读的时候才这么做,因为如果用这种方式更改类变量并不能收效,因为它会把类变量实例化了,或者说局部化了,意思是生成了一个实例变量覆盖了同名类变量 - 类及其实例对象就像一个口袋,可以直接向里面放原本没有的变量。如直接s1.age=18,相当于添加了一个age变量于s1实例中;类也是一样,可以直接向里面放新的类变量。当然,添加的变量也像一般变量一样,可以用
del
删除掉
- python中成员函数的调用。用法为:
2.继承
class Animal:
def __init__(self,legs):
self.legs=legs
def show(self,end="\n"):
print("animal with {} legs".format(self.legs),end=end)
class IBark:
def __init__(self,count):
self.count=count
def bark(self):
print("bark {} times".format(self.count))
def show(self):
print("bark...")
class Dog(Animal,IBark):
def __init__(self,color,count=5):
Animal.__init__(self,4)
IBark.__init__(self,count)
#super(Dog,self).__init__(4)#只会调用Animal的__init__函数
self.color=color
def show(self):
Animal.show(self,",")
print("a {} dog".format(self.color))
def bark(self):
print("a {} dog".format(self.color),end=" ")
IBark.bark(self)
a1=Animal(3)
a1.show()
d1=Dog("black")
d1.show()#覆盖父类同名方法,调用子类的show()方法
d1.bark()
super(Dog,d1).show()#调用父类被覆盖方法
print(issubclass(Dog,IBark))
- 继承的基本语法是
class 子类名(父类名):
然后跟类体,其中父类名可以是多个表示多继承 - 子类定义与父类同名方法则覆盖父类的方法
- 子类如果不定义
__init__
函数则默认调用父类的构造函数;如果是多继承,默认只调用第一个父类的构造函数 - 子类如果定义
__init__
函数,则不自动调用父类构造函数,需要手动调用,方法为父类名.__init__(参数列表)
;也可以用super(子类名,self).__init__(参数列表)
,但是这显然不适合多继承,因为多继承情况下这种方法只会调用第一个父类构造函数。另外super调用父类被覆盖方法也适用于类外,同样的只会调用多继承中第一个含有该方法父类的此方法 - 成员方法调用时,先查找子类中有无该方法,如果没有,按顺序逐个查找父类有无该方法
- 判断两个类是否有继承关系,用
issubclass(子类名,父类名)
判断
3.多态
多态按照百度百科的解释为:同一操作作用于不同的对象可以有不同的解释,产生不同的执行结果。在c++中多态通过虚函数、抽象类、覆盖和模板实现;在java中通过抽象类、覆盖和接口实现。在python中同理也是通过覆盖(函数重写)实现的,差别不大。因为python中没有类型声明,所以也没有什么向上转型的问题。
4.封装
- 封装主要是将实现细节对外隐藏,外部只需要知道类有哪些接口,至于具体怎么实现,外部不关心。封装主要体现在权限控制,比如c++中类中
public
的属性、方法为对外公共接口;private
的为私有属性、方法,外部不可访问;protected
的为外部不可访问,仅自身及其子类可访问 - 在python中,变量或方法前加单下划线
_
的为保护(protected)成员;变量或方法前加双下划线__
且尾部不加下划线的为私有(private)成员 - 在python中对权限设置的也不是那么严格,因为虽然不可以直接用
对象名.__私有属性名
的方式访问私有属性,但是可以通过对象名._类名__私有属性名
(注意先是单下划线后是双下划线)访问私有属性
5.动态
动态语言是一种运行时可以改变自身结构的语言,比如python就是一种动态语言,如上所述,python中的类或实例在运行时可以向其中加入新的属性,原有属性也可以被删除,这是语言的动态性。比如有以下类的定义及其实例:
class Person:
def __init__(self,name,age):
self.name=name
self.age=age
def speak(self):
print("my name is {},I'm {} years old".format(self.name,self.age))
p1=Person("Li Dong",23)
p1.speak()
#------------------------------------------------------#
p1.height=175#向实例中添加属性
print(p1.height)
Person.nation="China"#向类中添加属性
print("I'm form {}".format(p1.nation))
print(getattr(p1,'age'))#访问p1中age属性的值
#访问p1中fat_or_not属性的值,如果该属性不存在返回给定的默认值
print(getattr(p1,'fat_or_not',"we don't know"))
delattr(p1,'height')#输出p1实例中的height属性
print(hasattr(p1,'height'))#判断height属性在实例p1中是否存在
#添加类方法,不需要导入库
@classmethod
def print_msg2(cls):
print("added method2")
Person.print_msg2=print_msg2
Person.print_msg2()#调用增添的方法2
#添加实例方法(要导入types库)
import types
def print_msg1(self):
print("added method1")
p1.print_msg1=types.MethodType(print_msg1,p1)
p1.print_msg1()#调用增添的方法1
del Person.print_msg2
del p1.print_msg1
- 增删访问属性。可以直接按照常规的
对象名.实例变量名
访问属性,也可以用getattr(obj,name[,default])
方法访问属性;可以直接通过对象名.实例变量名=变量值
来设置属性值(如果不存在添加),也可以通过setattr(obj,name,value)
来设置属性;可以通过del 对象名.实例变量名
来删除属性,也可通过delattr(obj,name)
来删除属性;通过hasattr(obj,name)
来判断属性是否存在。注意:上述的属性名称name都是用字符串形式指定 - 如果想在类定义的时候限制有哪些属性,可以定义一个类变量
__slots__=一个由所有属性构成的元组
,这样之后就不能再向该类或者他的实例中添加属性了,但是删除属性仍然是可以的 - 增删方法。添加实例方法和类方法如上代码,其中添加类方法的
cls
参数同样只是一个名称不是关键字,代表当前类,可以通过它使用类变量及其他类方法;另外可以增添静态方法,只要把@classmethod
改成@staticmethod
就可,静态方法不需要第一个参数是self
或者cls
。删除方法直接del 实例对象.实例方法名
及del 类名.类方法名
- 另外要知道,类的归类,实例的归实例,访问实例的变量、方法都用实例名加成员运算符;访问类的变量、方法都用类名加成员运算符。尽管实例也可访问类的变量、方法,尽量不要这么做
6.内置属性
__dict__
:类的属性,包含类的所有数据属性(类变量和类方法)的一个字典;如果用对象.__dict__
则是返回对象的属性、方法字典,不包含类变量、类方法__doc__
:类的文档字符串,在类名冒号之后第二行的字符串为类的说明文档,使用__doc__
属性返回的即是这个字符串__name__
:类名__module__
:类定义所在的模块,一般没声明则为__main__
模块。关于python模块的自定义回头再说,先参考python自定义模块__bases__
:类的所有父类构成的元组
7.下划线特殊命名
仅前面加单、双下划线的是保护、私有成员,这个前面已经说明过了;
前后均加双下划线的是:
-
类的内置属性——如6中所述
-
基础重载方法——比如
__init__
函数、__del__
函数等方法 描述 调用 __init__(self[,args])
构造函数 obj=className(args) __del__(self)
析构函数 del obj __repr__(self)
转化为供解释器读取的形式 repr(obj) __str__(self)
转化为人阅读的形式(转化为字符串) str(obj) -
运算符重载方法
如:
class Complex: def __init__(self,real,imag): self.real=real self.imag=imag def show(self,end="\n"): flag="+" if self.imag>=0 else "-" print("{}{}{}i".format(self.real,flag,abs(self.imag))) def __add__(self,other): return Complex(self.real+other.real,self.imag+other.imag) c1=Complex(1,2) c2=Complex(2,-3) c3=c1+c2 c1.show() c2.show() c3.show()
__add__
函数重载了+
运算符。python中可供重载的运算符有众多算术运算符、比较运算符等,具体参看python运算符重载。
三.其他
1.迭代器
迭代器是用来遍历集合元素的。用迭代器遍历的方法如下:
ls=range(1,11)
it=iter(ls)
while True:
try:
print(next(it))
except StopIteration:
print('over')
break
迭代器遍历是通过抛出异常结束的。实际上,一个类只要实现了__iter__
函数和__next__
函数就可以生成迭代器进行迭代遍历,比如:
class Group:
def __init__(self,start,stop,step=1):
self.ls=range(start,stop,step)
def __iter__(self,count=10):
self.index=0
self.count=min(count,len(self.ls))
return self
def __next__(self):
if self.index<self.count:
x=self.ls[self.index]
self.index+=1
return x
else:
raise StopIteration
s1=Group(1,100) #生成1到100的"群组"
it=iter(s1) #生成前十个元素的迭代器 #调用了__iter__函数
while True:
try:
print(next(it))
except StopIteration:
break
2.列表推导式
列表推导式用于快速由一个列表的元素生成另一个列表,原先我们只能遍历操作,比如:
#生成1-10的平方列表
ls=[]
for i in range(1,11):
ls.append(i**2)
print(ls)
但是使用列表推导式可以直接在ls定义的时候赋值,而不必用一个遍历循环:
ls=[i**2 for i in range(1,11)]
另外,range(1,11)可以换成其他组合数据类型,i**2可以换成其他表达式或者函数:
def aCmplxFunc(x):
return 3*x**2+2*x-4
lt=[1,3,6,4,8,2,9]
ls=[aCmplxFunc(i) for i in lt]
print(ls)
参考链接: