一、类与对象介绍
1.面向对象
面向对象就是一种编程思想
面向过程也是一种编程思想
# 核心就是过程
# 过程就是'流水线',用来分步骤解决问题的
# 过程的核心思想就是将程序流程化进而简单化
面向对象 # 核心是对象二字
对象:对象就是容器,容器是用来盛放东西的(盛放什么东西)
用来盛放数据与功能的。
---所以说对象就是'数据'跟'功能'的集合体
---对象就是将程序'整合'
程序=数据+功能
学习对象就是将数据与功能整合到一起的方法
函数是放功能的容器
面向对象是一种更好的实现数据和功能整合到一起的方法
类:就是将对象做一个个区分,为了解决对象重复存取的问题
所以说类也是一种容器,该容器用来存放同类对象共有的数据与功能。
也可以将类叫做对象。
我们在访问数据的时候是先从对象里面的空间里面找,然后在从类的空间里面找
----------------类的语法:----------------
1.先定义类
class 定义类, 类中可以有任何的代码
1.变量的定义,2.功能的定义
类体代码在定义时候就运行了,就会产生名称空间。
这个时候对比一下(函数会在调用的时候才产生名称空间,而类在定义阶段就产生名称空间)
print(Student.__dict__) # 可以查看类的名称空间里面的名字
类中存放的是对象共有的东西,本质是给对象用的,类自己也可以用
print(Student.__dict__['sss']) # 可以取出类里面的值
python给我们提供了一种专门的取值方法
属性访问语法,这个sss不能称为是字符串,而是Student这个类的一个属性
类的属性无非就两种(1.访问数据属性,2.访问函数属性)
print(Student.sss) # 本质就跟上面的取值一样,但是简便了很多
类跟对象是有关联的
2.在调用类产生对象
类跟对象是有关联的,如果想让对象跟类有关联,必须用刚刚造出来的类调用一 下。 需要强调的是这个调用不会执行代码,因为类在定义的时候就执行代码过 了。只要调用Student()就会帮你产生一个对象,就会建立类和这个对象的关联 此时这个对象还是空的对象。需要你往里面添加。然后可以得到他的返回值,这个返回值就是对象。对象放的也是数据跟功能
所以对象在刚刚造出来的时候,应该为他初始化定制他独有的属性(数据初始化)
最好是不用自己产生,在调用的时候会自动把对象初始化
所以把下面的函数也放进类里面。只要调用类,就会把空对象传给__init__这个函数,调用的时候后三个参数需要自己来传
def __init__(obj,参数1,参数2,参数3.....):
obj.stu_name = 参数1
obj.stu_age = 参数2
obj.stu_gender = 参数3
在类定义阶段不会触发,在调用阶段自动触发
Stu1_obj = Student(参数1,参数2,参数3)
调用类的过程又称为实例化(你属于人类,但是不能说人类是你,你只是人类实际存在的一个案例,这就叫实例)
调用类是为了产生对象的,这个过程有人称之为实例化,将类这个抽象的东西,实例化
-----------------调用类发生了三件事------------
1.先产生一个空对象
2.会调用类中的__init__方法,然后将空对象以及调用类时括号内传入的参数一同传给__init__方法
3.返回初始化完的对象,然后可以做一个赋值操作可以看到对象里面的结果
------------总结__init__方法------------
1.会在调用类时自动触发执行,用来为对象初始化自己独有的数据
2.__init__内应该存放的是为对象初始化属性的功能,当然你也可以存放任意你想写的代码,想要在类调用时就立即执行的代码都可以放到该方法内
3.调用类的返回值并不是__init__, __init__方法必须返回None,不能返回非None的值
-------------------一个完整的类的形式----------
class Student:
# 1.变量的定义
stu_school='oldboy'
# 空对象
def __init__(obj,x,y,z):
obj.stu_name=x
obj.stu_age=y
obj.stu_gender=z
# 功能的定义
def tell_stu_info(stu_obj):
print('学生信息:名字%s 年龄%s 性别%s' % (
stu_obj['stu_name'],
stu_obj['stu_age'],
stu_obj['stu_gender']))
def set_info(stu_obj,x,y,z):
stu_obj['stu_name']=x
stu_obj['stu_age']=y
stu_obj['stu_gender']=z
stu1_obj = Student('egon',18,'male')
3.属性查找的顺序
类中存放是与对象共有的数据与功能
1.类可以访问:访问的是类的数据属性或者是类的函数属性
2.其实类中的东西是给对象用的:类的数据属性是共享给所有对象用的,大家访问的地址都一样
类的函数属性是绑定给对象用的,不同的对象
绑定方法的特殊之处,谁来调用谁当做第一个参数传入该函数内
隐藏属性
一:封装介绍
封装是面向对象三大特性最核心的一个特性
封装<->整合
二、将封装的属性进行隐藏操作
1、如何隐藏:在属性名前加__前缀,就会实现一个对外隐藏属性效果
该隐藏需要注意的问题:
I:在类外部无法直接访问双下滑线开头的属性,但知道了类名和属性名就可以拼出名字:_类名__属性,然后就可以访问了,如 Foo._A__N,所以说这种操作并没有严格意义上地限制外部访问,仅仅只是一种语法意义上的变形。
class Foo:
__x = 1 # _Foo__x
def __f1(self): # _Foo__f1
print('from test')
print(Foo.__dict__)
print(Foo._Foo__x)
print(Foo._Foo__f1)
II:这种隐藏对外不对内,因为__开头的属性会在检查类体代码语法时统一发生变形
class Foo:
__x = 1 # _Foo__x = 1
def __f1(self): # _Foo__f1
print('from test')
def f2(self):
print(self.__x) # print(self._Foo__x)
print(self.__f1) # print(self._Foo__f1)
print(Foo.__x)
print(Foo.__f1)
obj=Foo()
obj.f2()
III: 这种变形操作只在检查类体语法的时候发生一次,
# 之后定义的__开头的属性都不会变形
class Foo:
__x = 1 # _Foo__x = 1
def __f1(self): # _Foo__f1
print('from test')
def f2(self):
print(self.__x) # print(self._Foo__x)
print(self.__f1) # print(self._Foo__f1)
Foo.__y=3
print(Foo.__dict__)
print(Foo.__y)
class Foo:
__x = 1 # _Foo__x = 1
def __init__(self,name,age):
self.__name=name
self.__age=age
obj=Foo('egon',18)
print(obj.__dict__)
print(obj.name,obj.age)
2、为何要隐藏?
I、隐藏数据属性将数据隐藏起来就限制了类外部对数据的直接操作,然后类内应该提供相应的接口来允许类外部间接地操作数据,接口之 上可以附加额外的逻辑来对数据的操作进行严格地控制:
class People:
def __init__(self, name):
self.__name = name
def get_name(self):
# 通过该接口就可以间接地访问到名字属性
# print('小垃圾,不让看')
print(self.__name)
def set_name(self,val):
if type(val) is not str:
print('小垃圾,必须传字符串类型')
return
self.__name=val
obj = People('egon')
# print(obj.name) # 无法直接用名字属性
# obj.set_name('EGON')
obj.set_name(123123123)
obj.get_name()
II、隐藏函数/方法属性:目的的是为了隔离复杂度
property
# property 其实就是装饰器
class People:
def __init__(self, name, height, weight):
self.name = name
self.height = height
self.weight = weight
@property
def blm(self):
return self.weight / (self.height ** 2)
obj1 = People('ame', 1.75, 85)
print(obj1.blm)
# 在隐藏属性的时候 property的用法
class People:
def __init__(self, name):
self.__name = name
@property
def name(self):
print(self.__name)
@name.setter
def name(self, val):
if type(val) is not str:
print('请输入字符串')
self.__name = val
@name.deleter
def name(self):
print('不允许删除')
obj1 = People('Ame')
# obj1.get_name()
# obj1.set_name('Yuki')
# obj1.get_name()
obj1.name
obj1.name = 'Yuki'
obj1.name
obj1.name
del obj1.name
继承
1.什么是继承
'''
继承其实就是新创建的类,新创建的子类可以又叫派生类,父类又可以叫基类或者超类
需要注意的是python支持多继承 即一个字累可以有一个父类或者多个父类,子类可以继承父类的数据
ps: python2的继承分为经典式和新式类之分
新式类:继承了object的子类,以及子类的子类子子类
经典类:没有继承了object的子类,以及子类的子类子子类
但是python3里没有这么分,所有的都会默认继承object,所以都是新式类
多继承的优点缺点
优点 子类继承了多父类所有东西,代码能更简洁,更加被利用化
缺点 1 违背了人的正常逻辑,不可能拥有多个“父亲”
2 代码可读性会变得很差
3 不建议用到多继承,有可能会遇到可恶的菱形问题,扩展性也会变差
如果真得一个子类要多继承的话,得使用Mixins
'''
2.为什么用继承
# 因为要解决类与类之间的代码冗余问题
3.怎么用继承
class School:
school = 'Oldboy'
def __init__(self, name, age, gender):
self.name = name
self.age = age
self.gender = gender
class Student(School):
def choose(self):
print(f'{self.name}正在选课')
class Teacher(School):
def __init__(self, name, age, gender, level, salary):
School.__init__(self, name, age, gender)
self.level = level
self.salary = salary
def check(self):
print(f'{self.name}正在批改作业')
stu_obj = Student('Ame', 18, 'male')
stu_obj.choose()
tea_obj = Teacher('Yuki', 18, 'female', 10, 10000)
tea_obj.check()
单继承背景下的属性查找
class Foo:
def f1(self):
print('Foo.f1')
def f2(self):
print('Foo.f2')
self.f1()
class Bar(Foo):
def f1(self):
print("Bar.f1")
obj = Bar()
obj.f2() # Foo.f2 Bar.f1
# 我们会发现后来没有找到Foo.f1,找到了Bar.f1,因为self是obj给的,所以后来就去Bar里找f1,那么我们怎么找到Foo.f1呢
# 示范一
class Foo:
def f1(self):
print('Foo.f1')
def f2(self):
print('Foo.f2')
Foo.f1(self)
# self.f1()
class Bar(Foo):
def f1(self):
print("Bar.f1")
obj = Bar()
obj.f2()
# 示范二
class Foo:
def __f1(self):
print('Foo.f1')
def f2(self):
print('Foo.f2')
self.__f1()
class Bar(Foo):
def f1(self):
print("Bar.f1")
obj = Bar()
obj.f2()
菱形问题(钻石问题)
1.什么是菱形问题
class A(object):
def test(self):
print('A')
class B(A):
def test(self):
print('B')
class C(A):
def test(self):
print('C')
class D(B, C):
pass
obj = D()
obj.test() # 像这样的就是菱形问题,D------B, C------A 注意 C--------B, A------object 这样不是菱形问题
# 像这样遇到B, C同级应该会选择B还是C呢,查找方法得依靠C3算法得出来的MRO,注意 python2没有MRO
class A(object):
def test(self):
print('A')
class B(A):
def test(self):
print('B')
class C(A):
def test(self):
print('C')
class D(B, C):
pass
obj = D()
obj.test()
print(D.mro())
'''
[<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>]
就会依靠这个去寻找
注意 1.子类会先于父类被检查
2.多个父类会根据他们在列表中的顺序检查
3.下一类存在多个合法的选择,就会选择第一个父类
'''
非菱形问题的属性查找顺序
# 经典式与新式类 之间查找顺序都是一样的
菱形问题的属性查找顺序
# 经典类是深度查找,即一路走到黑,找不到再往右边去搜寻
# 新式类是广度查找,即每一路都走,但是不找最后,往右边全部搜寻完了,再去找最后一个
Mixins机制
class Vehicle: # 交通工具
pass
class Helicopter(Vehicle): # 直升飞机
def fly(self):
pass
class CivilAircraft(Vehicle): # 民航飞机
def fly(self):
pass
class Car(Vehicle): # 小轿车
pass
'''
从这可以看出 飞机们都有飞行的功能,我们应该把飞行功能代码都写入父类,防止代码冗余,但是这样的话
小汽车:???,我也会飞了
所以我们并不能把飞行功能加入交通工具这个父类
'''
# 处理办法
class Vehicle: # 交通工具
pass
class Aircraft: # 飞行器
def fly(self):
pass
class Helicopter(Aircraft, Vehicle): # 直升飞机
pass
class CivilAircraft(Aircraft, Vehicle): # 民航飞机
pass
class Car(Vehicle): # 小轿车
pass
'''
我们加入了个新父类,来解决汽车不能飞,但是飞机能飞的问题,
但是直升飞机和民航飞机就发出了疑问:??怎么我们有了两个亲爹
所以这样是不合人的逻辑了,所以mixin跳了出来,摇了摇头,帮助他们解决了问题
'''
class Vehicle: # 交通工具
pass
class AircraftMixin: # 飞行器
def fly(self):
pass
class Helicopter(AircraftMixin, Vehicle): # 直升飞机
pass
class CivilAircraft(AircraftMixin, Vehicle): # 民航飞机
pass
class Car(Vehicle): # 小轿车
pass
'''
mixin其实不是特殊的直白有效的代码,只是一个标志,一个功能的标志,往一个类加入这个Mixin就代表他其实不是真正的父亲,只是一个功能的代表,真正的亲爹是交通工具
同时也要注意mixin用法也有限制!!!!
1. mixin代表的是一个功能,不要代表一个物品,python 对于mixin类的命名方式一般以 Mixin, able, ible 为后缀
2. 然后他的责任很单一,如果有多种类功能,则要写多个mixin,不能挤一块,一个类是可以多继承很多mixin的
3.他不依赖于子类的实现
4.子类就算没有继承mixin,他还是能进行,只不过缺少了功能,比如,飞机没有继承飞,但是他还是可以载客
Mixins是从多个类中重用代码的好方法,但是需要付出相应的代价,我们定义的Minx类越多,子类的代码可读性就会越差,并且更恶心的是,在继承的层级变多时,代码阅读者在定位某一个方法到底在何处调用时会晕头转向
'''
在子类派生的新方法中重用父类的功能
# 在子类中直接调用父类的功能
class School:
school = 'Oldboy'
def __init__(self, name, age, gender):
self.name = name
self.age = age
self.gender = gender
class Teacher(School):
def __init__(self, name, age, gender, level, salary):
School.__init__(self, name, age, gender)
self.level = level
self.salary = salary
# super()调用父类提供给自己的方法----严格依赖继承关系
# 调用super()会得到一个特殊的对象,会自动追寻当前类MRO,去当前父类中找属性
class School:
school = 'Oldboy'
def __init__(self, name, age, gender):
self.name = name
self.age = age
self.gender = gender
class Teacher(School):
def __init__(self, name, age, gender, level, salary):
super().__init__(name, age, gender) # 传入的是方法,自动调用对象,所以不用加self
self.level = level
self.salary = salary
tea_obj = Teacher('Ame', 18, 'male', 10, 10000)
print(Teacher.mro())
print(tea_obj.__dict__)
多态与鸭子类型
1.什么是多态
其实就是一个事物有多种形态
2.在程序中表达多态
class Animal:
pass
class People(Animal):
pass
class Dog(Animal):
pass
class Pig(Animal):
pass
# 动物就有多种形态,称之为多态,那么多态的好处是什么呢,就比如动物都会叫
class Animal:
def say(self):
print('动物都会叫....')
class People(Animal):
def say(self):
super().say()
print('嘤嘤嘤')
class Dog(Animal):
def say(self):
super().say()
print('汪汪汪')
class Pig(Animal):
def say(self):
super().say()
print('哼哼哼')
obj1 = People()
obj2 = Dog()
obj3 = Pig()
obj1.say()
obj2.say()
obj3.say()
# 好处就是我们在调用子类的时候并不用想子类是什么,只用想着父类就是动物,肯定会叫,这是所有的语言多态的优点,但是python有更好玩的,那就是鸭子类型
3.什么是鸭子类型
其实就是比如 一个人在街上模仿鸭子叫声,走路,甚至还去河里游泳,我们就可以去把这个当成鸭子,代码也一样的道理,如果都没有指定父类,但是他们都有相似之处,就直接当成是一样来看
class Cpu:
def read(self):
print('cpu read')
def write(self):
print('cpu write')
class Memory:
def read(self):
print('Memory read')
def write(self):
print('Memory write')
class Txt:
def read(self):
print('txt read')
def write(self):
print('txt write')
obj1 = Cpu()
obj2 = Memory()
obj3 = Txt()
obj2.write()
obj1.write()
obj3.write()
# 正如cpu Memory都是硬件,TXT则是软件,真要分父类是分开的,但是他们都有读写功能,所以就可以看作一块,这就是鸭子类型
classmethod与绑定方法
1.绑定方法
特殊之处在于将调用者本身传入给第一个参数
# 绑定给对象的方法 调用者是对象 传入的也是对象
class Mysql:
def __init__(self, ip, port):
self.ip = ip
self.port = port
obj1 = Mysql('1,0,1,0', 3306)
# 绑定给类的方法 调用者是类,传入的也是类
import setting
class Mysql:
def __init__(self, ip, port):
self.ip = ip
self.port = port
def func(self):
print(f'{self.ip}{self.port}')
@classmethod
def from_conf(cls):
return cls(setting.IP, setting.PORT)
obj1 = Mysql.from_conf()
print(obj1.__dict__)
# 在setting文件里放了IP 和 PORT,直接这里调用
staticmethod与非绑定方法
# 非绑定方法其实就是静态方法,当一个函数不需要对象也不需要类的时候,但是想放在类下面,就用@staticmethod装饰器
# 没有绑定给任何人,调用者可以是类 可以是对象 不会有自动传入参数的效果
class Mysql:
def __init__(self, ip, port):
self.ip = ip
self.port = port
@staticmethod
def creat_id():
import uuid
return uuid.uui4()
反射
# 什么是反射
反射就是一个对象(不见棺材不落泪)到最后才知道他是什么属性
# 为什么要用反射
因为假如在一个类中,传入了对象,然后想找到对象的属性,用反射就很容易找到
# 怎么用反射
class School(object):
def __init__(self, name, age):
self.name = name
self.age = age
def say(self):
print(f'<{self.name},{self.age}>')
obj = School('Ame', 10)
# 假如在以后的工作中,你的同事写好了代码跟你交接,你只知道有个对象obj,该怎么知道obj的属性是什么呢
# 要用到dir()方法
print(dir(obj)) # ['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', .......]
# dir()会显示出obj所有的属性,所以在运用反射的4个内置函数便OK
print(hasattr(obj, 'name')) # hasattr 是判断有没有这个属性 True
print(getattr(obj, 'name')) # getattr 是调出这个属性的值 Ame
setattr(obj, 'name', 'Yuki')
print(obj.name) # setattr 是修改这个属性 Yuki
delattr(obj, 'name') # delattr 删除这个属性
# 用途案例
class People(object):
def put(self):
print("lalala")
def get(self):
print("lblblb")
def interactive(self):
methon = input('------>')
if hasattr(self, methon):
print(self, methon)
else:
print('指令不存在')
obj = People()
obj.interactive()
# 从这可以看出,发射就是用来判断未知的对象属性的
内置方法
# 什么是内置方法
以__开头__结尾的就是内置方法
特点 会自动触发调用
# 为什么用内置方法
为了定制化我们的类或者对象
# __str__ :打印对象时,自动触发,然后会将返回值显示出来
class School:
def __init__(self, name, age):
self.name = name
self.age = age
def __str__(self):
return f'{self.name},{self.age}'
obj = School('Ame', 18)
print(obj) # Ame,18 由此可以看出来 直接打印对象,然后就会输出__srt__
# __del__:删除的时候会自动调用
class School:
def __init__(self, name, age):
self.name = name
self.age = age
def __del__(self):
print('lalala')
obj = School('Ame', 18)
print("-------------")
'''
-------------
lalala
'''
# 从输出结果可以看 lalala自动打印了出来 这是为什么,因为程序结束后,pycharm要回收空间,所以会自动删除,然后__del__就打印了出来,如果在print之前删除呢
class School:
def __init__(self, name, age):
self.name = name
self.age = age
def __del__(self):
print('lalala')
obj = School('Ame', 18)
del obj
print("-------------")
'''
lalala
-------------
'''
# 所以__del__是删除的时候会自动调用,那么这个用途在哪的,我们在一个程序结束后,pycharm会自动回收,但是只是回收自己的空间,操作系统的并没有回收,所以__del__是用来告诉pycharm回收系统空间的
元类
# 引入
一切皆是对象
# 什么是元类
用来实例化产生的类的类
关系: 元类-------实例化-------类---------实例化--------对象
# 查看内置的元类:我们用class关键字定义的所有的类以及内置的类都由内置的元类type实例化产生的
class的机制
# class其实是由以下的步骤产生
class_name = 'People'
class_bases = (object,)
class_dir = {}
class_body = '''
def __init__(self, name, age):
self.name = name
self.age = age
def __del__(self):
print('lalala')
'''
exec(class_body, {}, class_dir)
print(type(class_name, class_bases, class_dir)) # <class '__main__.People'>
# 这种方法是自定义元类的精髓
# 其实平时 class People 的People 并不是类, 只是个变量名而已, 其中的功能代码集体才是一个类
自定义元类
class Mymeta(type):
def __init__(self,x ,y ,z): # 这边必须要传四个参数, 因为class people的存在会把class过程的参数传给Mymeta
print('123')
class People(metaclass=Mymeta):
def __init__(self, name, age):
self.name = name
self.bases = age
def say(self):
print(f'<{self.name},{self.age}>')