(一)面向对象的概念
- 历史背景:面向对象编程的产生源于对过程编程局限性的反思。过程编程在构造系统时,存在重用、维护和扩展的问题,且逻辑复杂,代码难以理解。因此,人们开始探索一种新的编程范式,即面向对象编程,试图通过模拟现实环境来设计应用程序。具体参看程序设计思想发展 - linhaifeng - 博客园
- 应用领域:面向对象的概念和应用已经超越了程序设计和软件开发,扩展到了数据库系统、交互式界面、CAD技术、人工智能等多个领域。
1、类
描述一类事物共同特征和行为的抽象概念,在Python中类是对象的模板,定义了一组变量(特征)和方法(行为)属性,可以用来创建具体的对象。
1. 站的角度不同,定义出的类是截然不同的,详见面向对象实战之需求分析
2. 现实中的类并不完全等于程序中的类,比如现实中的公司类,在程序中有时需要拆分成部门类,业务类......
3. 有时为了编程需求,程序中也可能会定义现实中不存在的类,比如策略类,现实中并不存在,但是在程序中却是一个很常见的类
4.python的class术语与c++有一定区别,与 Modula-3更像,python中一切皆为对象,且python3中类与类型是一个概念,类型就是类
#类型dict就是类dict >>> list <class 'list'> #实例化的到3个对象l1,l2,l3 >>> l1=list() >>> l2=list() >>> l3=list() #三个对象都有绑定方法append,是相同的功能,但内存地址不同 >>> l1.append <built-in method append of list object at 0x10b482b48> >>> l2.append <built-in method append of list object at 0x10b482b88> >>> l3.append <built-in method append of list object at 0x10b482bc8> #操作绑定方法l1.append(3),就是在往l1添加3,绝对不会将3添加到l2或l3 >>> l1.append(3) >>> l1 [3] >>> l2 [] >>> l3 [] #调用类list.append(l3,111)等同于l3.append(111) >>> list.append(l3,111) #l3.append(111) >>> l3 [111]
2、对象
类的实例,描述某个具体事务的特征和行为,在Python面向对象编程中,对象是程序的基本单位,包含数据和操作这些数据的方法(实际是调用类的方法属性),现实中先有具体的物体,后有总结共同的特征和行为抽象出类的概念,但在程序中,必须先定义类,后产生对象。
# 现实中类和对象的例子:先有对象,再有类
"""
对象1:张三
特征:
学校=一中
姓名=张三
性别=男
民族=汉
年龄=18
行为:
学习
吃饭
睡觉
对象2:李四
特征:
学校=二中
姓名=李四
性别=女
民族=汉
年龄=15
行为:
学习
吃饭
睡觉
对象3:王五
特征:
学校=五中
姓名=王五
性别=男
民族=汉
年龄=13
行为:
学习
吃饭
睡觉
提炼对象的共同特征和行为,定义学生类
相似的特征:
学校
姓名
性别
年龄
共同的特征:
民族 = 汉
相似的行为:
学习
吃饭
睡觉
"""
# 程序中类和对象的例子:先定义类,后由类实例化对象
"""
1. 在程序中特征用变量标识,行为用方法(函数)标识
2. 因而类中最常见的无非是:变量和方法的定义
"""
# 定义类:
class Student:
ming_zu = "汉" # 类的数据属性,定义所有对象的共同特征
# 除了共同的特征,还有相似但各个对象不尽相同的特征,这就用到了类的__init__构造方法
#注意:__init__方法是在对象产生之后才会执行,只用来为对象进行初始化操作,可以有任意代码,但一定不能有返回值。(原理:实例化对象时,类名加()就会调用内置的__call__方法创建一个空对象(object类实例化的对象),然后自动调用__init__方法,并将创建的空对象作为参数传给该方法进行初始化,然后__call__方法返回这个初始化了的对象给要赋值的变量)
def __init__(self, school, name, sex, age)
self.school = school
self.name = name
self.sex = sex
self.age = age
def learn(self):
print('is learning')
def eat(self):
print('is eating')
def sleep(self):
print('is sleeping')
"""
#注意:
1.类中可以有任意python代码,这些代码在类定义阶段便会执行
2.因而会产生新的名称空间,用来存放类的变量名与函数名,可以通过Student.__dict__查看
3.对于经典类来说我们可以通过该字典操作类名称空间的名字(新式类有限制),但python为我们提供专门的.语法
4.点是访问属性的语法,类中定义的名字,都是类的属性
"""
# 程序中类的用法
# .:专门用来访问属性,本质操作的就是__dict__
OldboyStudent.school #等于经典类的操作OldboyStudent.__dict__['school']
OldboyStudent.school='Oldboy' #等于经典类的操作OldboyStudent.__dict__['school']='Oldboy'
OldboyStudent.x=1 #等于经典类的操作OldboyStudent.__dict__['x']=1
del OldboyStudent.x #等于经典类的操作OldboyStudent.__dict__.pop('x')
#程序中的对象
#调用类,或称为实例化,得到对象
s1=Student("一中", "张三", "男", 18)
s2=Student("二中", "李四", "女", 15)
s3=Student("五中", "王五", "男", 13)
#程序中对象的用法
#执行__init__,s1.name='张三',很明显也会产生对象的名称空间
s2.__dict__
{'school':'二中', 'name': '李四', 'age': '女', 'sex': 15}
s2.name # s2.__dict__['name']
s2.name='史籍' # s2.__dict__['name']='史籍'
s2.course='python' # s2.__dict__['course']='python'
del s2.course # s2.__dict__.pop('course')
(二)面向对象设计
- 面向对象的程序设计:核心是对象,更加注重对现实世界的模拟,是一种“上帝式”的思维方式,不像面向过程的程序设计(流水线式),可以很精准的预测问题的处理流程与结果,面向对象的程序一旦开始就由对象之间的交互解决问题,即便是上帝也无法准确地预测最终结果,但解决了程序的扩展性,对某一个对象单独修改,会立刻反映到整个体系中,如对游戏中一个人物参数的特征和技能修改都很容易。
- 面向对象的程序设计并不是全部。对于一个软件质量来说,还关注成本、性能、可靠性、安全性、可维护性、可移植性、可伸缩性,面向对象的程序设计只是用来解决扩展性,一些扩展性要求低的场景使用面向对象会徒增编程难度,比如管理linux系统的shell脚本就不适合用面向对象去设计,面向过程反而更加适合。
- 应用场景:需求经常变化的软件,一般需求的变化都集中在用户层,互联网应用,企业内部软件,游戏等都是面向对象的程序设计大显身手的好地方
- 并不是只有class关键字定义类的方式才是面向对象的程序设计,用函数也可以实现面向对的程序设计,如下例子:
def dog(name, age, type): #定义一个狗的类
def init(name, age, type): #定义一个初始化函数,用来实例化具体的狗对象,将它的特征和行为封装到字典里
dog = {
"name":name,
"age":age,
"type":type,
"eat":eat
}
return dog
def eat(dog): #定义行为函数
print("狗%s在吃饭" % dog["name"])
return init(name, age, type) # 运行dog函数即初始化,并返回一个狗对象的字典
dog1 = dog("andy", 5, "藏獒") # 创建一个狗对象,即运行dog函数,获取具有狗对象特征和行为的字典
dog1["eat"](dog1) # 通过狗对象字典调用它的eat行为函数
(三)面向对象编程(OOP)
- 面向对象编程(OOP)是一种编程范式,它基于“万物皆对象”的理念,通过将现实世界中的事物抽象为计算机能够理解的模型,使得解决现实问题变得更加简单。
- 在OOP中,程序被视为对象之间相互协作的过程,每个对象都具有自己的属性和方法,通过发送消息来告知彼此要做的事情。
核心特性:封装、继承和多态。
优点:数据封装、易于维护、重复使用代码。
缺点:如果不恰当地应用OOP原则,可能导致过度设计和代码膨胀。
未来发展趋势:OOP具有强大的生命力和广泛的适用性,未来可能会更加注重跨平台兼容性、并发编程和分布式系统的支持。
class Dog: #类名首字母大写
def __init__(self, name, age, type):
self.name = name
self.age =age
self.type = type
def eat(self):
print("狗%s在吃饭" % self.name)
dog1 = Dog("andy", 5, "藏獒") # 类加括号,运行类就是实例化一个对象
dog.eat()
(四)类的相关知识
1、新式类和经典类
- python2才这么区分,python3中统一都是新式类(不管是否显式继承父类)。
- 新式类和经典类声明的最大不同在于,所有类必须继承一个父类。
- object类是所有类的父类,不管是否显式声明父类,都有一个默认继承的object类。
- python2中经典类和新式类的区分:
经典类:
class 类名:
pass
新式类:
class 类名(父类):
pass
- 注:python3中这两种方式定义都是新式类,python2中继承经典类的类也是经典类,新式类要顶层父类显式继承object类。
2、类的属性
所有这类对象共有的属性,分为数据属性和函数属性
2.1)数据属性:变量,即类的特征,所有对象共享,id都一样,如dang="共产党"print(id(Student.ming_zu)) print(id(s1.ming_zu)) print(id(s2.ming_zu)) print(id(s3.ming_zu)) ''' 4377347328 4377347328 4377347328 4377347328 '''
2.2)函数属性:函数,即类的行为,如def eat()或def shuijiao(self),命名规则动词_名词,绑定给对象用,obj.method称为绑定方法,虽然所有对象指向的都是相同的功能,但是绑定到不同的对象就是不同的绑定方法,内存地址都不一样,绑定给谁就由谁来调用,谁来调用,就会将‘谁’本身当做第一个参数传给方法,即自动传值(方法__init__也是一样的道理)
print(Student.learn) print(s1.learn) # 等同于Student.learn(s1) print(s2.learn) # 等同于Student.learn(s2) print(s3.learn) # 等同于Student.learn(s3) # 注意:绑定到对象的方法的这种自动传值的特征,决定了在类中定义的函数都要默认写一个参数self,self可以是任意名字,但是约定俗成地写出self。 ''' <function Student.learn at 0x1021329d8> <bound method Student.learn of <__main__.Student object at 0x1021466d8>> <bound method Student.learn of <__main__.Student object at 0x102146710>> <bound method Student.learn of <__main__.Student object at 0x102146748>> ''' #ps:id是python的实现机制,并不能真实反映内存地址,如果有内存地址,还是以内存地址为准
2.3)类和对象都用点.来调用自己的属性,如Chinese.dong
2.4)dir(类名):查看类的属性列表(只存放属性名)
2.5)类名.__dict__:查看类的属性字典(属性名:属性值,用.调用属性实质上就是在这个属性字典里找该属性对应的值)
2.6)类的内置属性:
__name__:类的名字(字符串)
__doc__:类的文档字符串
__base__:类的第一个父类
__bases__:类的所有父类构成的元组
__dict__:类的属性字典
__module__:类定义所在的模块,如果就在执行文件,值为__main__
__class__:实例对应的类(仅新式类中)
__init__:类的初始化函数,实例化自动调用
__mro__:经典类没有这个属性,新式类的继承顺序MRO元组(定义每个类时python会根据C3线性算法构造出一个方法解析顺序(MRO)元组,就是一个简单的所有基类的线性顺序元组,遵循三条准则:a、子类先于父类被检查;b、多个父类会根据它们在元组中的顺序被检查;c、如果对于下一个类有两个合法的选择,选择第一个,即找到后停止寻找)
2.7)不能直接修改类的属性字典,会导致oop(面向对象)不稳定
2.8)类属性又称为静态变量,或静态数据,与所属的类对象绑定,不依赖任何类实例,相当于Java和c++中在一个变量声明前加static关键字
2.9)修改属性:类名.属性名=新的值
2.10)增加属性:类名.新的属性名=值
2.11)删除属性:del 类名.属性名
2.12)不是.调用的变量(包括在类函数中),不会找类属性和实例属性,只会找普通变量
2.13)是.调用的变量(包括在类函数中),只会找类属性和实例属性,不会找普通变量
3、面向对象之绑定方法与非绑定方法
3.1 类中定义的函数分类
一:绑定方法(绑定给谁,谁来调用就自动将它本身当作第一个参数传入):
1. 绑定到类的方法:用classmethod装饰器装饰的方法。
为类量身定制
类.boud_method(),自动将类当作第一个参数传入
(其实对象也可调用,但仍将类当作第一个参数传入)
2. 绑定到对象的方法:没有被任何装饰器装饰的方法。
为对象量身定制
对象.boud_method(),自动将对象当作第一个参数传入
(属于类的函数,类可以调用,但是必须按照函数的规则来,没有自动传值那么一说)
二:非绑定方法:用staticmethod装饰器装饰的方法
1. 不与类或对象绑定,类和对象都可以调用,但是没有自动传值那么一说。就是一个普通工具而已
注意:与绑定到对象方法区分开,在类中直接定义的函数,没有被任何装饰器装饰的,都是绑定到对象的方法,可不是普通函数,对象调用该方法会自动传值,而staticmethod装饰的方法,不管谁来调用,都没有自动传值一说
3.2 绑定方法
- 绑定给对象的方法: 没有任何装饰器修饰的方法,通常由实例化的对象调用。
- 绑定给类的方法:@classmethod装饰的方法,即类方法,通常由类本身调用。
import settings class MySQL: def __init__(self,host,port): # 绑定到对象的方法 self.host=host self.port=port @classmethod def from_conf(cls): # 类方法-绑定到类的方法 print(cls) return cls(settings.HOST,settings.PORT) print(MySQL.from_conf) #<bound method MySQL.from_conf of <class '__main__.MySQL'>> conn=MySQL.from_conf() conn.from_conf() #对象也可以调用,但是默认传的第一个参数仍然是类
3.3 非绑定方法
- 在类内部用staticmethod装饰的函数即非绑定方法,就是普通函数
- statimethod不与类或对象绑定,谁都可以调用,没有自动传值效果
import hashlib import time class MySQL: def __init__(self,host,port): self.id=self.create_id() self.host=host self.port=port @staticmethod def create_id(): #就是一个普通工具 m=hashlib.md5(str(time.time()).encode('utf-8')) return m.hexdigest() print(MySQL.create_id) #<function MySQL.create_id at 0x0000000001E6B9D8> #查看结果为普通函数 conn=MySQL('127.0.0.1',3306) print(conn.create_id) #<function MySQL.create_id at 0x00000000026FB9D8> #查看结果为普通函数
3.4 classmethod和staticmethod
classmethod是类方法,staticmethod是静态方法,如果修饰同名方法,通过类名调用会调用classmethod装饰的方法。
host = "127.0.0.1"
port = 8080
class MySQL:
def __init__(self,host,port):
self.host=host
self.port=port
@staticmethod
def from_conf():
return MySQL(host,port)
# @classmethod #哪个类来调用,就将哪个类当做第一个参数传入
# def from_conf(cls):
# return cls(host,port)
def __str__(self):
return '就不告诉你'
class Mariadb(MySQL):
def __str__(self):
return '<%s:%s>' %(self.host,self.port)
m=Mariadb.from_conf()
print(m) #我们的意图是想触发Mariadb.__str__,但是结果触发了MySQL.__str__的执行,打印就不告诉你,因为m是返回的MySQL类的实例
# 放开classmethod装饰的from_conf(cls)方法,就不会触发staticmethod装饰的from_conf()方法了,结果就是我们要的<127.0.0.1:8080>
3.5 练习
定义MySQL类
1.对象有id、host、port三个属性
2.定义工具create_id,在实例化时为每个对象随机生成id,保证id唯一
3.提供两种实例化方式,方式一:用户传入host和port 方式二:从配置文件中读取host和port进行实例化
4.为对象定制方法,save和get_obj_by_id,save能自动将对象序列化到文件中,文件路径为配置文件中DB_PATH,文件名为id号,保存之前验证对象是否已经存在,若存在则抛出异常,;get_obj_by_id方法用来从文件中反序列化出对象
(1)创建唯一id之UUID
原文链接:http://www.cnblogs.com/dkblog/archive/2011/10/10/2205200.html
Python官方Doc:《20.15. uuid — UUID objects according to RFC 4122》
UUID的算法介绍:《A Universally Unique IDentifier (UUID) URN Namespace》
概述:
UUID是128位的全局唯一标识符,通常由32字节的字符串表示。
它可以保证时间和空间的唯一性,也称为GUID,全称为:
UUID —— Universally Unique IDentifier Python 中叫 UUID
GUID —— Globally Unique IDentifier C# 中叫 GUID
它通过MAC地址、时间戳、命名空间、随机数、伪随机数来保证生成ID的唯一性。
UUID主要有五个算法,也就是五种方法来实现:
1、uuid1()——基于时间戳
由MAC地址、当前时间戳、随机数生成。可以保证全球范围内的唯一性,
但MAC的使用同时带来安全性问题,局域网中可以使用IP来代替MAC。
2、uuid2()——基于分布式计算环境DCE(Python中没有这个函数)
算法与uuid1相同,不同的是把时间戳的前4位置换为POSIX的UID。
实际中很少用到该方法。
3、uuid3()——基于名字的MD5散列值
通过计算名字和命名空间的MD5散列值得到,保证了同一命名空间中不同名字的唯一性,
和不同命名空间的唯一性,但同一命名空间的同一名字生成相同的uuid。
4、uuid4()——基于随机数
由伪随机数得到,有一定的重复概率,该概率可以计算出来。
5、uuid5()——基于名字的SHA-1散列值
算法与uuid3相同,不同的是使用 Secure Hash Algorithm 1 算法
使用方面:
首先,Python中没有基于DCE的,所以uuid2可以忽略;
其次,uuid4存在概率性重复,由无映射性,最好不用;
再次,若在Global的分布式计算环境下,最好用uuid1;
最后,若有名字的唯一性要求,最好用uuid3或uuid5。
编码方法:
# -*- coding: utf-8 -*-
import uuid
name = "test_name"
namespace = "test_namespace"
print uuid.uuid1() # 带参的方法参见Python Doc
print uuid.uuid3(namespace, name)
print uuid.uuid4()
print uuid.uuid5(namespace, name)
(2)练习代码
(2.1)settings.py
HOST='127.0.0.1'
PORT=3306
DB_PATH=r'E:\CMS\aaa\db'
(2.2)test.py
import settings
import uuid
import pickle
import os
class MySQL:
def __init__(self,host,port):
self.id=self.create_id()
self.host=host
self.port=port
def save(self):
if not self.is_exists:
raise PermissionError('对象已存在')
file_path=r'%s%s%s' %(settings.DB_PATH,os.sep,self.id)
pickle.dump(self,open(file_path,'wb'))
@property
def is_exists(self):
tag=True
files=os.listdir(settings.DB_PATH)
for file in files:
file_abspath=r'%s%s%s' %(settings.DB_PATH,os.sep,file)
obj=pickle.load(open(file_abspath,'rb'))
if self.host == obj.host and self.port == obj.port:
tag=False
break
return tag
@staticmethod
def get_obj_by_id(id):
file_abspath = r'%s%s%s' % (settings.DB_PATH, os.sep, id)
return pickle.load(open(file_abspath,'rb'))
@staticmethod
def create_id():
return str(uuid.uuid1())
@classmethod
def from_conf(cls):
print(cls)
return cls(settings.HOST,settings.PORT)
# print(MySQL.from_conf) #<bound method MySQL.from_conf of <class '__main__.MySQL'>>
conn=MySQL.from_conf()
conn.save()
conn1=MySQL('127.0.0.1',3306)
conn1.save() #抛出异常PermissionError: 对象已存在
obj=MySQL.get_obj_by_id('7e6c5ec0-7e9f-11e7-9acc-408d5c2f84ca')
print(obj.host)
3.6 其他练习
class Date:
def __init__(self,year,month,day):
self.year=year
self.month=month
self.day=day
@staticmethod
def now(): #用Date.now()的形式去产生实例,该实例用的是当前时间
t=time.localtime() #获取结构化的时间格式
return Date(t.tm_year,t.tm_mon,t.tm_mday) #新建实例并且返回
@staticmethod
def tomorrow():#用Date.tomorrow()的形式去产生实例,该实例用的是明天的时间
t=time.localtime(time.time()+86400)
return Date(t.tm_year,t.tm_mon,t.tm_mday)
a=Date('1987',11,27) #自己定义时间
b=Date.now() #采用当前时间
c=Date.tomorrow() #采用明天的时间
print(a.year,a.month,a.day)
print(b.year,b.month,b.day)
print(c.year,c.month,c.day)
#分割线==============================
import time
class Date:
def __init__(self,year,month,day):
self.year=year
self.month=month
self.day=day
@staticmethod
def now():
t=time.localtime()
return Date(t.tm_year,t.tm_mon,t.tm_mday)
class EuroDate(Date):
def __str__(self):
return 'year:%s month:%s day:%s' %(self.year,self.month,self.day)
e=EuroDate.now()
print(e) #我们的意图是想触发EuroDate.__str__,但是结果为
'''
输出结果:
<__main__.Date object at 0x1013f9d68>
'''
因为e就是用Date类产生的,所以根本不会触发EuroDate.__str__,解决方法就是用classmethod
import time
class Date:
def __init__(self,year,month,day):
self.year=year
self.month=month
self.day=day
# @staticmethod
# def now():
# t=time.localtime()
# return Date(t.tm_year,t.tm_mon,t.tm_mday)
@classmethod #改成类方法
def now(cls):
t=time.localtime()
return cls(t.tm_year,t.tm_mon,t.tm_mday) #哪个类来调用,即用哪个类cls来实例化
class EuroDate(Date):
def __str__(self):
return 'year:%s month:%s day:%s' %(self.year,self.month,self.day)
e=EuroDate.now()
print(e) #我们的意图是想触发EuroDate.__str__,此时e就是由EuroDate产生的,所以会如我们所愿
'''
输出结果:
year:2017 month:3 day:3
'''
(五)对象的相关知识
1、实例化
运行类的__init__初始化函数,self就是实例本身,产生一个对象的属性字典
p = Chinses() # 会将对象名p传递给Chinses类的__init_构造方法_的self参数 # 实际上是调用了Chinses类的__call__方法,该方法自动调用__init__方法 # 原理:实例化对象时,类名加()就会调用内置的__call__方法创建一个空对象(object类实例化的对象),然后自动调用__init__方法,并将创建的空对象作为参数传给该方法进行初始化,然后__call__方法返回这个初始化了的对象给要赋值的变量
2、对象的属性
实例属性
2.1)对象只有数据属性:变量,即对象的特征,如self.name=值
2.2)对象可以调用类的数据属性和函数属性(会自动将对象本身传递给该函数的第一个位置参数,一般是self),但类不能访问对象的属性,因为对象的作用域就是init函数的作用域,对象调用属性时先在自己的属性字典里面找,如果找不到再到类的属性字典里面找,如果再找不到报错
2.3)用点.来调用自己的属性,如c.name
2.4)dir(对象名):查看对象的属性列表(只存放属性名)
2.5)对象名.__dict__:查看对象的属性字典(属性名:属性值,用.调用属性实质上就是在这个属性字典里找该属性对应的值)
2.6)对象的内置属性:
__dict__:对象的属性字典
__class__:实例对应的类(仅新式类中)
2.7)不能直接修改对象的属性字典,会导致oop不稳定
2.8)修改数据属性:对象名.属性名=新的值
2.9)增加数据属性:对象名.新的属性名=值,对象只有调用类的函数属性时会自动传递对象给self,所以如果对象增加函数属性,传参时需要手动传递对象
2.10)删除数据属性:del 对象名.属性名
2.11)不是.调用的变量(包括在类函数中),不会找类属性和实例属性,只会找普通变量
2.12)是.调用的变量(包括在类函数中),只会找类属性和实例属性,不会找普通变量
2.13)对象调用类属性,如列表变量,通过apeend增加元素,修改的是类属性的值,通过赋值修改值,才是新增或修改实例属性
3、对象之间的交互
class Garen: #定义英雄盖伦的类,不同的玩家可以用它实例出自己英雄;
camp='Demacia' #所有玩家的英雄(盖伦)的阵营都是Demacia;
def __init__(self,nickname,aggressivity=58,life_value=455): #英雄的初始攻击力58...;
self.nickname=nickname #为自己的盖伦起个别名;
self.aggressivity=aggressivity #英雄都有自己的攻击力;
self.life_value=life_value #英雄都有自己的生命值;
def attack(self,enemy): #普通攻击技能,enemy是敌人;
enemy.life_value-=self.aggressivity #根据自己的攻击力,攻击敌人就减掉敌人的生命值。
class Riven:
camp='