目录
类和面向对象是python基础中最后一个点了,但同时类和面向对象我觉得也是很难的一个点,语法很多,用处很多,同时我也感觉好多博主虽然讲得好但是总感觉有点乱,我也在这按照我的思路整理一下学习思路,希望对大家有帮助(面向对象都会了,你的对象还会远吗?)
一.什么是类与面向对象
抛开概念来说,首先,大家想想为什么会学函数,其中的一条理由就是,函数会减少你写重复代码的次数,函数相当于对一段重复代码的升级,类和面向对象也一样,你可以理解为对函数的一个升级,来减少函数的重复代码。比如你要写狗,这个狗里可能有狗的名字,狗的年龄,你要定义函数的话你得定义多个函数包括狗的名字年龄,二如果有了类和面向对象,你只需要定义一个狗类,将狗的各种属性写进去,就可以解决所有问题。
从具体的事物来描述,就是,你用class来定义了一个类,而python中一切皆对象,即你定义的这个类里面的所有函数,包括一个类中具体事物的行为都叫对象。
二.类语法
1.类属性和实例属性
下面我们通过这些代码来理解
class Dog:
type = "zangao" #类属性,类变量,公共属性
def __init__(self,name,age):
self.name=name
self.age=age
d=Dog("ooo",3)
d2=Dog("oooo",2)
假如说你定义了一个狗类,这个狗会和人一样有名字有年龄有类型,这是假如这些所有的狗种类都一样,你不需要将他们再一个个列出来,你可以将这个种类定义在类里面但是又在在函数外面,这样这个种类就变成了一个类属性,也叫类变量,它属于公共属性。
紧接着看,在定义了一个函数之后(代码中的__init__我们先暂且不管,我们就把它当成一个函数来理解),但是如果这些狗的年龄和名字都不一样怎么办,这时你可以通过实例方法来实现,在后边你可以将狗的名字、年龄写进去,如最后两段代码,这样就完全定义了一个狗类。
但是问题又来了,中间那个self.name = name 是什么鬼。事实上,你要想把你后面写的名字和年龄真正到你的实例里,你就要先进行一个绑定操作,而中间的代码就相当于你进行了绑定操作。
2.操作对象
学习函数我们知道,定义一个函数之后,我们得调用这个函数让他起作用,你定义的类也一样,你也得对他进行操作也才能让他生效,在这里我们先学习最基本的,在下面的文章里会有更高级的操作方法
class Dog:
type = "zangao" # 类属性,类变量,公共属性
def __init__(self, name, age):
self.name = name
self.age = age
def say_hi(self):
print("hello,i am a dog ",self.name) #绑定name这个实例
d = Dog("ooo", 3)
d2 = Dog("oooo", 2)
d.say_hi() #调用say_hi这个对象 对象.xxx
Dog.say_hi(d) #类.xx
你可以用 类.xx 或者 对象.xx 来调用对象,使对象成功运行,你还可以在你定义的对象中绑定一些实例来让它生效,如以上代码所示
三.三大方法
1.类方法
类方法就是通过一个@classmethod来实现,类方法和普通方法的区别就是类方法只能访问类变量不能访问实例变量,如代码所示
class Dog(object):
name="qaq" #类变量
def __init__(self,name):
self.name = name
@classmethod
def eat (self):
print("%s is eating" % self.name)
d=Dog("ooo")
d.eat()
你运行了这段代码以后你会发现,最后运行的结果即这个name并不是后面实例属性“ooo”,而是前面的类属性“qaq”,这便是类方法
2.静态方法
类方法就是通过一个@staticmethod来实现,而静态方法不能访问类变量也不能访问实例变量,静态方法隔绝了对象跟类或实例的任何关系
class Student:
role="Stu"
def __init__(self,name):
self.name = name
@staticmethod
def fly(self):
print(self.name,"is flying")
s=Student("jack")
# s.fly()
s.fly(s) #必须手动传参
但是,你如果一定要调用这个对象怎么办,你就要进行手动传参,如代码所示,这样就可以成功运行
3.属性方法
属性方法就是把一个对象变成一个静态变量,即在最后调用的时候,你可以在对象的后面什么都不加便可以实现对它的一个调用
class Student:
role="Stu"
def __init__(self,name):
self.name = name
@property
def fly(self):
print(self.name,"is flying")
s=Student("jack")
s.fly
四.继承与多继承
1.继承
在有的类和面向对象的代码里,有的同学可能会发现,有的时候在定义了一个类之后,这个类后面还会加一个括号,里面还会写东西,这便是继承。在一般的代码里,如果只有一个类,我们也可以把我们定义的这个类后面,加一个object,代表我们的类不管这么定义它都是一个东西,所有就都可以属于object这个类
class Animal:
def __init__(self, name,age,sex):
self.name = name
self.age = age
self.sex = sex
def eat(self):
print( "%s is eating" % self.name)
class Person(Animal):
def talk(self):
print("person %s is talking"%self.name)
class Dog(Animal):
def chase_rubbit(self):
print("狗在追兔子")
p1=Person("hu",16,"men")
p1.eat()
p1.talk()
d1=Dog("ooo",3,"f")
d1.eat()
#--------------------运行结果------------------
hu is eating
person hu is talking
ooo is eating
由这段代码我们可以看出,人这个类是继承了动物这个大类(因为人也是动物),而狗也继承了动物这个类,而我的人类和狗类他们都在动物这个类里进行了同样的绑定,并且,他们可以有动物这个大类里共有的操作,也可以有各自的小类里独特的操作
2.多继承
同时多继承也很好了解,就是一个类它既能继承这个类,又能继承另外一个类
class Shenxian:
def fly(self):
print("神仙会飞")
def fight(self):
print("猴子会飞")
class Momkey:
def eat_peach(self):
print("猴子喜欢吃桃子")
class Momkeyking(Shenxian,Momkey):
def play_sticky(self):
print("孙悟空玩金箍棒")
m=Momkeyking()
m.fly()
m.play_sticky()
m.fight()
#-------------------输出结果------------------------
神仙会飞
孙悟空玩金箍棒
猴子会飞
由以上代码可见,这个美猴王类,即继承了神仙这个类又继承了猴子这个类,同样这个美猴王类也拥有以上类的所有特征
3.父类重构
这里穿插一个小知识点叫父类重构,假如你想修改一下父类,你想在父类里面加一些对象,比如你在动物类的对象里向加入这个对象的hobby,有的人说,诶呀这个简单,我直接在绑定操作这里加不就好了?比如直接在第二段代码的sex后面加上一个hobby,但是这样的话,后面所有的类,只要继承了动物这个类的,都会将hobby这个对象写进去,如何才能做到在子类里加上呢,我们可以用这样一段语法实现:super().__init__(name, age, sex)
class Animal:
def __init__(self, name,age,sex):
self.name = name
self.age = age
self.sex = sex
def eat(self):
print( "%s is eating" % self.name)
class Person(Animal):
def __init__(self, name,age,sex,hobby):
super().__init__(name, age, sex)
self.hobby=hobby
def eat(self):
Animal.eat(self) #让他先执行父类,再执行子类
super().eat() #函数中继承父类
print("%s在优雅的吃"%self.name)
def talk(self):
print("person %s is talking"%self.name)
class Dog(Animal):
def chase_rubbit(self):
print("狗在追兔子")
p1=Person("hu",18,"men","eating")
p1.eat()
p1.talk()
另外还有两个小语法 1.在函数中继承父类 2.先执行父类再执行子类的操作 这两个都可以通过上边的代码来实现(旁边会有注释)
五.封装
作为类与面向对象的三大特征之一,同样也十分重要,并且在某些编程上特别实用,什么是封装,就是一个对象在一些编程的过程中他是不能变的,比如在动作游戏中,npc只会说几句话,有的npc永远也动不了,像这些东西你就得封装起来
class Person(object):
def __init__(self,name,age):
self.name = name
self.age = age
self.__life=100 #私有变量
def get_life(self):
print(self.__life)
return self.__life
def got_attacker(self):
self.__life -= 20
print("被攻击了")
return self.__life
def __breath(self): ###加下划线使得函数被封装
print("%s is breath"%self.name)
p=Person("hu",18)
# print(p.__life) #这样直接访问则报错,这样则进行了一个封装
p.got_attacker()
如何进行封装,在你的对象前面加两个_,如上图代码所示,就实现了一个封装,要调用被封装的对象也不能直接访问,被封装的函数按理来说是不能修改的,如果你一定要修改也可以,但是不建议这么做,所有强制修改的办法也先不讲给大家
六.多态
既然在之前说过,类和面向对象能够减少函数的重复代码,多态就清晰的体现了这一点,加入两个类里面都有同一个对象,这时候你就可以进行同时调用
class Dog(object):
def sound(self):
print("wangwangwang")
class Cat(object):
def sound(self):
print("miaomiaomiao")
def make_sound(animal_obj):
animal_obj.sound() #共同调用
d=Dog()
c=Cat()
make_sound(d) #统一调用接口make_sound 只是表现形态不一样
make_sound(c)
七.双下划线方法
所有的双下划线都是方法,就相当于内置函数一样,都有他们独特的用处,方法很多,这里先说几个
1.__init__
init相当于一个初始化方法,也叫构造方法或构造函数,实例化时会自动执行。进行一些初始化工作。
2.__repr__与__str__
改变字符串的显示格式
class School:
def __init__(self,name,addr,type):
self.name = name
self.addr = addr
self.type = type
#
# def _repr__(self): #在交互器下调用
# return "school(%s,%s)"%(self.name,self.addr)
def __str__(self):
return "school(%s,%s)"%(self.name,self.addr)
s=School("1","2","3")
# print("form repr",repr(s))
print("from str:",str(s))
#--------------------------有str-----------------------
from str: <__main__.School object at 0x00000133D9B0E200>
#--------------------------无str-----------------------
from str: school(1,2)
可以自己判断有无str方法时候的输出结果,
而repr是在交互器下实现的,这个在后边网络编程应该会学到
3.__new__
在init之前执行,负责执行init,init是否执行由new决定
class Student:
def __init__(self,name):
self.name = name
print("hhhh")
def __new__(cls,*args,**kwargs):
print(cls,args,kwargs)
p = Student("hu")
p.name
这样你会发现,init方法下的print并没有打印,而执行了new方法,说明当前new方法在init前面执行,但是如何让init方法执行,可以用下面的一段代码
class Student:
def __init__(self,name):
self.name = name
print("hhhh")
def __new__(cls,*args,**kwargs): #init是否执行决定与它
print(cls,args,kwargs)
return object.__new__(cls)
p=Student("hu")
p.name
这时有人会问,这个东西到底有什么用啊,拿来干什么。
假设你现在要修应该楼房,你要设计一个修建的模式,修什么楼你需要注意什么,软件开发也一样,你要开发软件有一套模式,开发网站也有一套模式,其中有一种模式就是单例模式,假设你打开一个窗口,正常情况下你点一下就有一个窗口,点两下就有两个窗口,但是有的文件你会发现点几下都只有一个窗口,开的多的话只会浪费资源,这种情况就需要用new方法实现,至于怎么写,路飞学城有专门的new方法,大家可以去看
4.__call__
class School:
def __init__(self,name,addr,type):
self.name=name
self.addr=addr
self.type=type
def __call__(self,*args,**kwargs):
print(self,args,kwargs)
s=School("ooo","tianjin","master")
s()
有了__call__方法以后,直接实例名加括号 或者类名加括号就可以执行
5.__getitem__与__dict__
只有__getitem__时可以获取任何值,但是加上__dict__可以获得一个字典
class Brand:
def __init__(self,name):
self.name = name
def __getitem__(self, item):
print(self.__dict__) #这样会得到一个字典
b=Brand("pig")
b["name"]
#------------------输出---------------
{'name': 'pig'}
八.反射
概念:可以通过字符串的形式来操作一个对象的属性
什么意思呢,事先你是不知道你的对象里有没有name,age这些属性,你要想调用name,防止报错,这个用字符串的判断过程就是操作对象属性的方法之一
1.hasattr(判断)
class Person(object):
def __init__(self, name, age):
self.name = name
self.age = age
p=Person("hu",22)
if hasattr(p,"name"):
print("right")
2.getattr(获取)
同样在下面这段代码中,假设你收到一段代码,你可以就拿着你输入的字符串age跟传来的代码对应,如果里面也有age则调用
class Person(object):
def __init__(self, name, age):
self.name = name
self.age = age
p=Person("hu",22)
a=getattr(p,"age")
print(a)
3.setattr(赋值)
class Person(object):
def __init__(self, name, age):
self.name = name
self.age = age
p=Person("hu",22)
setattr(p,"sex","female")
print(p.sex)
这样用setattr赋值以后,你会发现你相当于将sex进行了绑定操作
4.delattr(删除)
class Person(object):
def __init__(self, name, age):
self.name = name
self.age = age
p=Person("hu",22)
delattr(p."age")
p.age #此时的age将调用不到
九.__name__与__main__
__name__在被当前模块主动执行的情况下,等于__main__
在被别的模块导入执行的情况下,等于模块名
用来判断模块是否被导入
十.总结
反思:里面我有一些东西没有讲,比如对象之间的关系,动态加载模块,而且我决定我的总结也不是特别到位,我只是给大家列了这样一个框架,我觉得按我这样学习和总结思路会清晰很多
最后本身觉得这些东西全写出来应该到明年了,但是发现其实只要自己弄明白了,思路清晰也没有想象中的那么困难,可能是今天去了半天图书馆让我灵感大现,不仅解决了自己新学习的网络编程的问题,还解决了类和面向对象的复习问题,最后如果代码或者讲解有误,希望各位指正