面向对象
ps:不管是什么语言,面向对象的三要素都是:封装(把变量和函数用类封装起来)、继承、多态
面向对象(OO,object-oriented)是一种编写代码的思维方式:程序是由什么组成的。面向对象编程:OOP(object-oriented programming)
类,其实是一种抽象。所谓抽象,即把具有相同特征的东西进行归类。比如User类,把用户相关信息抽象到一起
类由属性和方法组成。
属性:表示了类的特点
方法(行为):规定了类的功能
Ps:约定,类名采用驼峰结构,并且首字母大写
程序(类) = 数据结构(属性) + 算法(方法,行为)
面向对象就是把现实世界映射到程序世界的过程
实例:类的具体化
self:当前对象(方法的调用者)
cls:对象所属类
注意:不论是self还是cls,都只是一个变量名而已,习惯上用这两个名称,也可以用其他名称(目前的学习中还没有正式讲解,个人理解:实例方法中的第一个参数代表实例对象本身,类方法中的第一个参数代表类本身)
每个对象都有__class__
属性,通过它可以查看是哪个类创建了本实例对象
python里面的构造函数不能有默认参数
函数定义在类里面称之为方法
class Cat:
#__new__方法分配内存,单例模式时会用到此方法
def __new__(cls, *args, **kwargs): #new方法为什么需要这些参数?
# 此处这样写只是为了可以接受任何形式与数目的参数,在new里面一般并用不到,
# 但是执行了new之后会自动调用init方法,那时会用到这些参数。
# 亦即:*args与**kwargs并不是必要的格式。比如,在init方法中,
# 如果需要的是除了self之外的一个参数(如name),
# 那么此处也可以只声明一个变量接受该参数即可
print("__new__")
return super().__new__(cls) #也可以:return = object.__new__(cls) 另,此处以本人目前的知识猜测,在object类中应是用了@property修饰了__new__()方法的,所以此处如果用object可以不加括号。加了括号也是一样的
#注意:__new__方法必须要有返回值,返回实例化出来的实例,可以返回父类__new__返回代实例(此处采用的方法),也可以直接将object的__new__出来的实例返回
#构造方法,该方法会创建对象(实例),自动调用
def __init__(self, color, name):
# 前后有双下划线的方法由程序自动调用
# 注意:此处的self只是一个变量名,指向了方法的调用者,也可以用其他名字,但是一般不用改
self.color = color
self.name = name
def catch_mouse(self):
print(self.name + " 正在抓老鼠")
def __del__(self):
print("对象空间已经被释放") #当对象的空间被释放的时候(引用对象的个数为0的时候)会自动调用此方法
#ps:对象的引用个数可以用`import sys \n sys.getrefcount(Object)`来获取,比实际的对象多1,如果该对象空间已经被释放,则会报错
#创建实例
tom = Cat("Blue","Tom")
print(tom.name)
实例化对象的过程:
- 先分配一块内存(__new__()),单例设计模式时会用到此方法。当new了一块空间之后,再自动调用init方法,此时会将new方法接收的参数自动传给init方法。将new的空间的引用传给init方法的self
- 初始化值(__init__())
__new__()方法的注意事项:__new__方法必须要有返回值,返回实例化出来的实例,可以返回父类__new__返回代实例(此处采用的方法),也可以直接将object的__new__出来的实例返回
属性私有化:
- _xx:前单置下划线,私有化属性或方法,在这个模块内可以直接使用,但是from somemodule import *禁止导入,类对象和子类可以访问
- 属性起名的时候前面加"_",即两个下划线,这是语法规定。
s:xx,单后置下划线,用于避免与Python关键词的冲突
对于私有属性或私有方法,在类里面使用时也必须加__
,即双下划线 - 如果是前后都有
__
,一般是魔法方法,不建议自己定义方法时使用
为什么双下划线能够实现私有化(隐藏变量):
__dict__:任何对象都有这个属性,里面装的是实例的所有属性。双下划线开头的属性,会被名称转换:_类名__+属性名(名字重整),此时可以通过对象名._类名__属性名
访问
如何访问私有属性:通过方法
class Student:
"""
学生类
"""
def __init__(self, name, age):
self.__name = name
self.__age = age
def set_name(self, name):
if not len(name) < 3:
self.__name = name # 私有属性,ps:如果是动态添加的属性(__属性名),不是私有
def get_name(self):
return self.__name
#标注,默认是get的。@就是一个标记,比如下面的@age.setter,当用“=”时,就调用该函数。如果是用的".",就调用此方法
@property #@语法糖,前面加这个的可以像使用属性一样使用方法,即不用加括号
def age(self): # 用property修饰的方法,参数只能为self(只能有一个参数——对象本身)
# property的作用:把复杂的过程进行了封装
return self.__age
#使用了@后方法名必须与属性名一致
@age.setter #让方法可以像属性一样使用
def age(self,age):
self.__age = age
@age.deleter # 当对age属性调用del 的时候,自动调用此方法
def age(self):
del self.age
tom = Student("tom", 23)
print(tom.get_name())
tom.set_name("jack")
print(tom.get_name())
tom.hight = 23 #为tom额外添加hiht属性。
print(tom.age) #可直接获得年龄,其实tom.age是在调用方法
tom.age = 18 #使用语法糖的方式更改属性值
print(tom.age) #这里也使用了语法糖,实际上是调用的age方法
del tom.age
print(tome.age) # 此句报错
#输出:
tom
jack
23
18
关于使用property的第二种方法
class Foo:
def __init__(self, bar):
self.__bar = bar
def get_bar(self):
return self.__bar
def set_bar(self, new_bar):
self.__bar = new_bar
def del_bar(self):
del self.__bar
Bar = property(fget=get_bar, fset=set_bar, fdel=del_bar)
foo = Foo("first")
print(foo.Bar)
foo.Bar = "seconed"
print(foo.Bar)
del foo.Bar
print(foo.Bar)
'''运行结果如下:
first
seconed
Traceback (most recent call last):
File "G:/Program/Python/ChuanZhi/就业班08深浅拷贝/深浅拷贝.py", line 116, in
print(foo.Bar)
File "G:/Program/Python/ChuanZhi/就业班08深浅拷贝/深浅拷贝.py", line 100, in get_bar
return self.__bar
AttributeError: 'Foo' object has no attribute '_Foo__bar'
'''
类本身也是对象
通常:要对类属性操作时,用类方法;要对实例对象操作时,用实例方法
self.属性名:实例属性
class User:
# 在类里面定义的属性称为类属性,由所有实例共享。一般不在这里面写东西。
num = 0 # 类属性,在类的实例中可以调用,但是不能更改,如果试图更改(eg:ogj.num = 100),只会实例中添加一个实例属性
# 如果在对象中要更改,应该用:obj.__class__.num = 100
# 通常对类属性的更改是通过类方法
def __init__(self, name):
self.__name = name
User.num += 1
def print_name(self):
print(self.__name)
print(User.num)
@property
def name(self):
return self.__name
@classmethod # 标注,表明这是类方法。即可以通过类名调用,必须用一个cls参数,cls就是类本身,与对象的self相当。
def creat_user(cls, name, age): # cls其实就是类本身,类方法传的类本身,而不是对象本身
user = cls(name)
user.age = age
return user
@staticmethod # 静态方法,可以直接通过类名调用。当一个方法即和类没有关系又和对象没有关系时,就用静态方法,就不用cls和self参数
#
def sum(a, b):
return a + b
def __str__(self): #当print对象的时候会调用这个方法。这个方法必须返回一个字符串
_str = ""
for k,v in self.__dict__.items():
_str += str(k)
_str += ":"
_str += str(v)+" "
return _str
u = User("zhangsan")
u1 = User("lisi")
u.print_name()
u1.print_name()
User.num += 1
print(User.num)
u2 = User.creat_user("wangwu", 20)
# @classmethod修饰的方法是类方法,类方法可以通过类名调用。类方法必须有一个参数,cls,即类本身
print(u2.name)
print(User.sum(1, 3)) #通过类名调用静态方法
print(u.sum(2,4)) #通过实例对象调用静态方法
print(u1.sum(1,3))
u3 = u1.creat_user("laoli",50)
print(u3.name)
print(u1)
继承
python支持多继承,继承的内容与继承的顺序相关(属性来自先继承的类)
子类对象可以直接调用父类的方法,也拥有父类的属性
所有类都会默认继承object类
继承能够减小代码的冗余
注意:父类的私有属性和私有方法(仅以__开头的)不会被继承,但是如果被继承的方法(没有重写)访问了父类的私有属性或方法,则父类的私有属性或私有方法还是让该方法使用
class A:
def __init__(self):
self.name = "A"
def print_test(self):
print("A"*10)
class B(object): #ps:如果一个类没有继承任何类,建议写上object类,object类是所有类最终的父类,即:所有类都继承自object类
def __init__(self):
self.name = "B"
def print_test(self):
print("B"*10)
class C(A): #A是C的父类,又称基类。C是A的子类,派生类。C继承自A
def __init__(self):
super().__init__() #调用父类的初始化方法
self.age = 20
class D(A,B): #多继承,是按照顺序,比如此处有两个name,那么D就继承到了A的。
def print_test(self):
super().print_test()
self.print_test()
c = C()
print(c.name)
print(c.age)
d = D()
print(d.name)
print(C.__mro__) #mro中的顺序是由C3算法决定的
#上面这句会输出:(, , ),表明调用C类的对象的方法时,搜索的顺序,如果C没有该方法,就用A的,如果A还没有,就调用object类的(caution,object类是所有类的父类,因为这里A没有继承除了object类之外的类,所以是A中找不到就到object中去找,如果A继承了其他类,比如D类,则A中找不到就会到D中去查找。即查找顺序为:本类>父类(按继承的先后顺序,先继承的先用)>父类的父类
print(isinstance(d,A)) #判断一个对象是否是一个类的实例。输出True,子类的对象也是父类的实例
print(issubclass(D,A)) #判断一个类是否是另一个类的子类
#以上的顺序表明了查找属性的顺序(多继承的访问顺序),如果C本身有,就用C本身的,如果C本身没有,而A有,则用A的,再否则用B的
鸭子类型
只要几个类有相似的功能或属性,即使没有继承自同一个父类,也可以认为其属于同一类。比如如下的Programer和Manager,虽然没有继承自同一个父类,但是显然其都是员工,所以可以特意为其定义类似的方法或属性
class Programer:
def dowork(self):
print("编程")
class Manager:
def dowork(self):
print("管理")
p = Programer()
m = Manager()
p.dowork()
m.dowork()
#判断某一对象是否是一个类的实例
print(isinstance(p,Programer))
重写
在子类中定义一个和父类的方法同名的方法,以后调用该方法调用的就是自己的,而不是父类的,这就是方法的重写。注意:重写和重载是不相同的
class Animal:
name = "狗"
def eat(self):
print("-----吃-----")
def run(self):
print("-----跑-----")
class Dog(Animal):
def bark(self):
print("-----汪汪叫-----")
class XiaoTian(Dog):
def fly(self):
print("-----fly-----")
def bark(self):
print("-----狂叫-----“)
#Dog.bark(self) #通过父类的类名,并以self为参数,可以调用父类中被重写了的方法
super().bark() #通过super().方法名(),可以也可以调用父类被重写的方法,此种方法不需要self参数
xt = XiaoTian()
xt.run()
xt.bark()
xt.eat()
print(xt.name)
多态
定义:定义时的类型和运行时的类型不一样,此时就称为多态(基于方法的重写)。(ps:与C++和java中的多态区别较大,C++和Java中的多态,略类似于强制类型转换)
class Dog(object):
def print_self(self):
print("This is dog")
class XiaoTianQuan(Dog):
def print_self(self):
print("This is XiaoTianQuan")
def introduce(temp):
temp.print_self()
dog1 = Dog()
dog2 = XiaoTianQuan()
introduce(dog1)
introduce(dog2)
#所谓多态,即写完程序后,只是知道它调用一个方法,但是不确定它究竟是调用基类的还是调用子类的,在真正执行的一刹那,才根据当前的对象是谁去决定调用哪个方法
#因为python是动态类型的语言,所以python中的多态体现得不态明显
类属性、实例属性
#需求:用一个变量存储一个类创建了多少个对象
class Tool(object):
#属性:定义在类之内,方法之外的属性称为类属性
count = 0
#方法
def __init__(self, name):
self.__name = name
Tool.count += 1 #对于类属性,用类名进行访问(就像实例对象的属性通过实例对象的名字访问一样)
tool1 = Tool("铁锹")
tool2 = Tool("共兵铲")
注意:实际上程序并不是每创建一个对象就在该对象的空间内放一个方法,而是在该空间内多了一个属性(变量),该属性可以知道这个对象的class,即保存了该类的一个引用,然后在调用该方法时到该类中查找该方法。故而一个抽象的类实际上也占用了内存空间,亦即类在程序里面也是一个对象,这个对象称为类对象,通过类创建出来的对象称为实例对象,实例对象里面的属性称为实例属性,类里面的属性称为类属性。
caution:实例属性和某个数据的对象有关系,并且一个实例对象和另外一个实例是不共享属性的。而类属性是属于类对象的,并且多个实例对象之间可以共享同一个类属性。类属性在定义类的时候只会定义一次,与实例对象的创建无关。
- 实例属性
__init__方法中的属性和以及后续可能通过实例对象创建的属性称为实例属性(eg:假设该类初始化时没有定义name属性,而后续有:S.name = "Dog",通过此种方法创建的属性也是实例属性,与init方法中定义的属性都属于实例属性)
实例方法、类方法、静态方法
注意:实例方法中的第一参数为self只是大家都遵循的一个规则,同时,类方法中的第一个参数cls也只是大家都遵循的规则
class Game(object):
#类属性
num = 0
#实例方法
def __init__(self):
self.name = "laowang"
#在方法前加了@classmethod的都称为类方法
@classmethod #装饰器
def add_num(cls): #不同于实例方法中必须有self参数保存该对象(实例),cls就是用来保存类的引用的,是class的简写。
cls.num = 100
game = Game()
Game.add_num() #类方法可以通过类的名字调用。也可以通过这个类创建出来的对象去调用这个类方法(个人理解:该类创建的对象实例,也是该类,所以可以调用该类的方法)
print(Game.num)
注意:以后的实际开发中,如果想要用类去做,通通用类;如果想要用函数去做,通通用函数。即用类又用函数显得不伦不类
单例设计模式
class S:
__instance = None
def __new__(cls, *args, **kwargs):
if S.__instance is None: #类属性通过类名访问
S.__instance = super().__new__(cls) #因为自己重写了__new__方法(分配内存的),所以必须调用父类的__new__()以分配内存
return S.__instance #ps:也可以写成:return cls.__instance
s1 = S()
s2 = S()
print(id(s1),id(s2)) #会发现两个对象的内存地址相同,即他们是同一个对象
#拓展:只初始化一次对象
class S:
__instance = None
__init_flag = False
def __new__(cls, *args, **kwargs):
if S.__instance is None: #类属性通过类名访问
S.__instance = super().__new__(cls) #因为自己重写了__new__方法(分配内存的),所以必须调用父类的__new__()以分配内存
return S.__instance #ps:也可以写成:return cls.__instance
def __init__(self,name):
if Dog.__init_flag == False:
self.name = name
Dog.__init_flag = True