[TOC]

10.Python 文件操作与处理

描述: 我们所知道常用的操作系统有Windows,Mac,LINUX,UNIX,这些操作系统底层对于文件系统的访问工作原理是不一样的,因此您可能要针对不同的操作系统来考虑使用那些系统模块,即修改不同的代码。但是Python中有了OS模块,我们不需要关心什么操作系统下使用什么模块,OS模块会帮你选择正确的模块并调用。

在Python对于FILE文件最重要的方法是open()方法Z,用于打开一个文件,并返回文件对象,对文件进行处理过程都需要使用到这个函数;

open(file, mode='rt')  # 默认为文本模式只读打开,如果要以二进制模式打开,加上 b 。
open(file, mode='rt', [buffering=-1, encoding=None, errors=None, newline=None, closefd=True, opener=None]) #完整语法

#参数说明:
file: 必需,文件路径(相对或者绝对路径)。
mode: 可选,文件打开模式
buffering: 设置缓冲
encoding: 一般使用utf8
errors: 报错级别
newline: 区分换行符
closefd: 传入的file参数类型

#如果该文件无法被打开,会抛出 OSError
#File对象的方法参考笔记中Python3内置函数

10.1 Python文件系统

使用Python进行文件的打开与内容的读取,写入内容,需要注意写入中文字符 (选择utf-8编码)

#!/usr/bin/python3
#coding:utf-8
#原理:文件系统

#/****案例1:文件内容遍历***/
file = open('python.txt','r+',encoding='utf-8')

#不建议使用下面的方式进行输出文件中的内容;
# file.seek(0,0)
# lines = list(file)
# for eachline in lines:
# print(eachline,end="")
#替代的方法直接采用文件对象方式(推荐)

print("文件描述符为: %d" %file.fileno())
print("是否连接到一个终端设备",file.isatty())

file.seek(0,0)
for eachline in file:
print(eachline,end="")
file.close()


#/**** 案例2:文件内容分割 ***/
def save_file(boy,girl,count):
file_name_boy = 'boy_'+str(count)+'.txt'
file_name_girl = 'girl_'+str(count)+'.txt'

afile = open(file_name_boy,'w',encoding='utf-8') #注意写入coding
bfile = open(file_name_girl,'w',encoding='utf-8')

afile.writelines(girl)
bfile.writelines(boy)

afile.close()
bfile.close()


def split_file(filename):
f = open(filename,'r',encoding='utf-8') #注意读取coding
Aauthor = []
Bauthor = []
count = 1
for lines in f:
if lines[:6] != '======':
(role,spoken) = lines.split(':')
if role == 'A':
Aauthor.append(spoken) #列表添加
elif role == 'B':
Bauthor.append(spoken)
else:
save_file(Bauthor,Aauthor,count)
#重新将A/B置为空列表
Aauthor = []
Bauthor = []
count += 1

#在循环结束后需要进行一次文件保持,因为只有两次 '======',初学者容易犯错
save_file(Bauthor,Aauthor,count) #关键点
f.close()
split_file('demo2-10.txt')

########################### 测试结果 ########################################
# C:\Users\Administrator\Desktop\Python\2>python demo2-10.py
# 文件描述符为: 3
# 是否连接到一个终端设备 False
# #1:测试
# #2: nAME = wEIYIgeek
# #3:password = 123456

10.2 Python文件写入反序列化

描述:采用二进制的形式将python各类数据类型存储进磁盘中,相当于数据库反序列化,需要导入pickle包进行读入读出操作;

pickle的实质就是利用一些算法将你的数据对象“腌制”成二进制文件,存储在磁盘上,当然也可以放在数据库或者通过网络传输到另一台计算机上。

#!/usr/bin/python3
#功能:数据序列化(二进制)
import pickle
import os

value = dict.fromkeys((1,2,3,5,6),'pickle')
print(value)
def pkldown(filename):
if os.path.exists(filename):
print("文件已经存在正在打开",end="")
f = open(filename,'wb')
else:
f = open(filename,'wb')

pickle.dump(value,f)
f.close()

def pklget(filename):
f = open(filename,'rb')
getv = pickle.load(f) #注意要先使用'rb'的模式open文件哦
f.close()
print(" %s " %getv)

pkldown('test.pkl')
pklget('test.pkl')

WeiyiGeek.open函数mode属性参数

注意:使用pickle可以保存为”*.txt”类型的文件,但是存入是二进制文件,直接打开是乱码;


11.Python 错误和异常抛出

总结下异常处理机制的重要性:
由于环境的不确定性和用户操作的不可以预知性都可能导致程序出现各种问题,因此异常机制最重要的无非就是:增强程序的健壮性和用户体验,尽可能的捕获所有预知的异常并写好处理的代码,当异常出现的时候,程序自动消化并恢复正常(不至于崩溃);

try 语句语法:

try:
检测的代码块
execpt Exception [as reaon]:
出现异常后执行的代码
else:
当没有异常发生时,else中的语句将会被执行

try - finally 语句:

try:
检测的代码块
execpt Exception [as reaon]:
出现异常后执行的代码
else:
当没有异常发生时,else中的语句将会被执行
finally:
无论怎么样都会被执行的代码


#raise 语句 抛出异常
raise 系统异常名称('错误描述')

#with 语句 关注文件I/O自动释放(不在需要手动释放了) , 语句处理多个项目的时候,可以用逗号隔开写成一条语句)e
with open('data.txt','w') as 文件对象:
#案例
with A() as a, B() as b:
suite

错误与异常抛出案例:

#!/usr/bin/python3
#程序异常处理

#案例1:无论何种错误都将执行我们处理代码
try:
sum = 1 + 'a'
except:
print("程序出现了错误BUG , [在实际中不建议使用这样的方法]")


#安装2:指定的异常进行指定的操作
try:
f = open('noexsitfile.txt') #运行到这里直接跳到OSerror异常执行响应的代码
print("----------------------")
f.write("我存在了")
sum = 1 + 'b'
except OSError as err:
print("OSError:",str(err))
except TypeError as err:
print("TypeError:",str(err))

print("----------------------")

#案例3:指定不同的异常执行相同的处理异常的代码,最后不过抛没抛出异常都将执行
try:
f = open('noexsitfile.txt','w',encoding='utf-8') #运行到这里直接跳到OSerror异常执行响应的代码
print(f.write("我存在了"))
sum = 1 + 'b'
except (OSError,TypeError) as identifier:
print("程序异常了:",str(identifier))
finally:
print("不管抛没抛出异常都要将打开的文件关闭")
f.close()

print("-------------------------")

#案例4:抛出指定的异常
try:
if 'a' > 'b':
raise TypeError("我们异常了")
else:
raise NameError("我就要异常") #抛出指定异常
except (TypeError,NameError) as errvalue:
print("自定义异常原因:",errvalue)


print("-------------------------")
#案例5: try ....except...else ...finally 语句,当没有异常发生时,else中的语句将会被执行
try:
print( 1 / 2)
except TypeError as identifier:
print("错误原因:",str(identifier))
else:
print("没有异常错误我才执行")
finally:
print("不管有木有错我都要执行")

print("----------------------")
#案例6 with语言的使用
try:
with open('with.txt','w',encoding='utf-8') as f:
f.write("Test ... Test")
print("完成文件写入")
except OSError as reson:
print("出错误了",str(reson))
else:
print("什么错误都没出,打开的文件将自动关闭")

WeiyiGeek.错误与异常抛出案例

With 语句案例:
使用 with 语句处理文件可以减少需要编写的代码量和粗心的错误;

def file_compare(file1, file2):
with open(file1) as f1, open(file2) as f2: # 值得学习
count = 0 # 统计行数
differ = [] # 统计不一样的数量

for line1 in f1:
line2 = f2.readline()
count += 1 #文件函数比较
if line1 != line2:
differ.append(count)

return differ

file1 = input('请输入需要比较的头一个文件名:')
file2 = input('请输入需要比较的另一个文件名:')

differ = file_compare(file1, file2)

if len(differ) == 0:
print('两个文件完全一样!')
else:
print('两个文件共有【%d】处不同:' % len(differ))
for each in differ:
print('第 %d 行不一样' % each)


12.Python 面向对象

Q:类和对象是什么关系呢?
答:类和对象的关系就如同模具和用这个模具制作出的物品之间的关系。一个类为它的全部对象给出了一个统一的定义,而他的每个对象则是符合这种定义的一个实体,因此类和对象的关系就是抽象和具体的关系;

OOP思想:

  • 类的属性定义应该尽可能抽象,因为这样更符合面向对象的思维;
  • 对象中的属性和方法,在实际编程中是变量(属性)和函数(方法);

面向对象的几个特征:

  1. 封装:对外部隐藏对象的工作细节
  2. 继承:子类自动共享父类之间数据和方法的机制 (子类继承父类)
  3. 多态:可以对不同类的对象调用相同的方法,产生不同的结果 (不同对象对同一方法响应不同行动)

Q:面向对象技术名词?

  • 类定义: 指的是类定义,用来描述具有相同的属性和方法的对象的集合,特点“Python无处不对象”
  • 类对象: 类定义完之后自然就是类对象,这时你可以对类的属性(变量)进行直接访
  • 实例对象: 一个类可以实例化出无数的对象(实例对象,类的具体对象),为了区分是哪个实例对象调用了方法所以采用上面所说的self;
  • 数据成员:类变量或者实例变量用于处理类及其实例对象的相关的数据。
  • 局部变量:定义在方法中的变量,只作用于当前实例的类。
  • 实例变量:在类的声明中,属性是用变量来表示的。这种变量就称为实例变量,是在类声明的内部但是在类的其他成员方法之外声明的。
  • 方法重写:如果从父类继承的方法不能满足子类的需求,可以对其进行改写,这个过程叫方法的覆盖(override),也称为方法的重写。
  • 继承:即一个派生类(derived class)继承基类(base class)的字段和方法,继承也允许把一个派生类的对象作为一个基类对象对待。
    WeiyiGeek.类/类对象与实例对象

类属性与方法

  • 私有变量: private_attrs:两个下划线开头,声明该属性为私有,不能在类的外部被使用或直接访问,但可以通过 对象._类名属性名称 在外部进行调用;
  • 类的私有方法:private_method:两个下划线开头,声明该方法为私有方法,只能在类的内部调用( self.private_methods), 在类的外部调用类似于上面;
  • self参数的作用是绑定方法,有了这个参数Python 再也不会傻傻分不清是哪个对象在调用方法了,self 其实就是实例对象的唯一标志。
class Test:
count = 0 # 静态属性
public_attrs = 0 #公共属性
__private_attrs = 0 #私有

def __init__(self):
C.count = C.count + 1 # 类名.属性名的形式引用 (每实例化一次+1)

def prt(self):
print('这是公共方法') # 公共方法
print(self)
print(self.__class__)
self.__foo() #调用私有方法

def __foo(self): # 私有方法
print('这是私有方法')

def getCount(self):
return C.count #类的静态属性应用(计算该类被实例化的次数)

t = Test()
t.prt()

# 注意 #
t._Test__private_attrs #外部直接调用私有属性
t._Test__foo() #外部直接调用私有方法

############### 以上实例执行结果为:#############################
# 这是公共方法
# <__main__.Test instance at 0x100771878> #self 代表的是类的实例,代表当前对象的地址
# __main__.Test #self.class 则指向类
# 这是私有方法
# 这是私有方法 #外部调用的私有方法

12.1 封装

对象封装案例:

#!/usr/bin/python3
#实例化对象和类型

#案例1:封装特性
class Person:
name = 'WeiyiGeek' #属性
age = 20
msg = 'I Love Python'
#定义私有属性,私有属性在类外部无法直接进行访问
__weight = 120
def printname(self): #方法
print(self.name,self.age,self.msg,self.__weight)

people = Person() #实例化
people.printname() #对象.方法

#python事实上是采用一种叫“name mangling”技术,将以双下划线开头的变量名巧妙的改了个名字而已,我们仍然可以在外部通过“_类名__变量名”的方式访问:
print("体重:",people._Person__weight,'KG')

#案例2:封装特性
class Rectangle:
length = 5
width = 6
#设置长宽
def setRect(self,length,width):
self.length = length
self.width = width

#获取长宽
def getRect(self):
print("矩形的长宽为:%d , %d" %(self.length,self.width))

#获取面积
def getArea(self):
print("矩形面积 =",self.width * self.length)

#获取周长
def getGirth(self):
print("矩形周长 =",2 * (self.width + self.length))

rect = Rectangle()
rect.setRect(15,16)
rect.getRect()
rect.getArea()
rect.getGirth()


############ 执行结果 ##############
# WeiyiGeek 20 I Love Python 120
# 体重: 120 KG
# 矩形的长宽为:15 , 16
# 矩形面积 = 240
# 矩形周长 = 62

总结:

  • 利用继承和组合机制来进行扩展时候,在类名/属性名(用名词)/方法名(用动词)的命名建议要规范化
  • 私有属性就是在属性或方法名字前边加上双下划线,从外部是无法直接访问到并会显示AttributeError错误
  • 当你把类定义完的时候,类定义就变成类对象,可以直接通过“类名.属性”或者“类名.方法名()”引用或使用相关的属性或方法。
  • 类中属性名与方法名一定不要一致,否则属性会覆盖方法,导致BUG的发生;


12.2 继承

Q:继承机制给程序猿带来最明显的好处是?
答:如果一个类 A 继承自另一个类 B,就把这个 A 称为 B 的子类,把 B 称为 A 的父类、基类或超类。继承可以使得子类具有父类的各种属性和方法,而不需要再次编写相同的代码(偷懒)。
在子类继承父类的同时,可以重新定义某些属性,并重写overwrite某些方法,即覆盖父类的原有属性和方法,使其获得与父类不同的功能。另外为子类追加新的属性和方法也是常见的做法。

继承的搜索:
WeiyiGeek.继承的搜索

继承语法:

class 子类名(基类,父类,或者超类名称)

案例:

#!/usr/bin/python3
#实例化对象和类型

#案例1:单继承示例
#父类
class Person:
name = '' #定义基本属性
__UID = 0 #私有属性
#魔术方法
def __init__(self, *args):
self.name = args[0]
self.__UID = args[1]
#类方法
def speak(self):
print("%s 的身份证号为 %d" %(self.name,self.__UID))

#子类
class Student(Person):
grade = ''
def __init__(self, *args):
#调用父类的构造函数
Person.__init__(self,args[0],args[1])
self.grade = args[2]
#重写覆盖父类的speak方法
def speak(self):
print("姓名: %s , 年级:%s ,身份证号%d 由于是父类的私有变量不能这样调用 self._Person__UID " %(self.name,self.grade,Person._Person__UID))

xs = Student('WeiyiGEEK',512301031998010937,'2018级')
xs.speak()

## 案例2 :多继承
#如下代码,类A,B分别表示不同的功能单元,C为A,B功能的组合,这样类C就拥有了类A, B的功能。
class A:
def get_a(self):
print 'a'

class B:
def get_b(self):
print 'b'

class C(A, B): #C类是A类/B类的子类
pass

c = C()
c.get_a()
c.get_b()


## 案例3:动态继承 继承的基类是动态的(有时候是 A,有时候是 B)
## 实现方式 :先为基类定义一个别名,在类定义的时候,使用别名代替你要继承的基类
BaseAlias = BaseClass # 为基类取别名

class Derived(BaseAlias): #注意这里参数是基类的别名
def meth(self):
BaseAlias.meth(self) # 通过别名访问基类
...


############################## 执行结果 ################################
# 姓名: WeiyiGEEK , 年级:2018级 ,身份证号 0 由于是父类的私有变量不能这样调用 :self._Person__UID
# a
# b
#

(补充):组合介绍
Q:什么是组合(组成)?
答:Python 继承机制很有用,但容易把代码复杂化以及依赖隐含继承。因此经常的时候,我们可以使用组合来代替。在Python里组合其实很简单,直接在类定义中把需要的类放进去实例化就可以了

简单的说,组合用于“有一个”的场景中,继承用于“是一个”的场景中

#*类得组合(横向)*#
#乌龟类
class Turtle:
def __init__(self, x):
self.num = x

# 鱼类
class Fish:
def __init__(self, x):
self.num = x

# 水池类
class Pool:
def __init__(self, x, y):
self.turtle = Turtle(x) # 组合乌龟类进来 (关键点)
self.fish = Fish(y) # 组合鱼类进来 关(关键点)

def print_num(self):
print("水池里总共有乌龟 %d 只,小鱼 %d 条!" % (self.turtle.num, self.fish.num))

>>> pool = Pool(1, 10)
>>> pool.print_num()

(补充):super()超类
Python 严格要求方法需要有实例才能被调用,这种限制其实就是Python所谓得绑定概念;

#!/usr/bin/python3
#功能:super 超类的实现
import random as r #别名

class Fish:
def __init__(self):
self.x = r.randint(0,10)
self.y = r.randint(0,10)

def move(self):
self.x -= 1
print("我得位置是:",self.x,self.y)

class Shark(Fish):
def __init__(self):
super().__init__() #方法1:只需要设置super() 指代 父类
#Fish.__init__(self) #方法2: 调用父类构造方法
self.hungry = True
def eat(self):
if self.hungry:
print("饿了,要吃饭@!")
self.hungry = False
else:
print("太撑了,吃不下了")

demo1 = Shark()
demo1.move()
demo1.eat()
demo1.eat()
demo1.move()

######### 执行结果 ########
# 我得位置是: 0 5
# 饿了,要吃饭@!
# 太撑了,吃不下了
# 我得位置是: -1 5

总结:

  • 当子类与父类定义相属性性或方法时,Python 不会删除父类的相关属性或方法而是将父类属性或方法覆盖;子类对象调用的时候会调用到覆盖后的新属性或方法,但父类的仍然还在,只是子类对象“看不到”
  • 多重继承使用不当会导致重复调用(也叫钻石继承、菱形继承)的问题


12.3 多态


12.4 魔法方法

魔法方法体现再它们总能够再适当得时候被自动调用;
(0) new(cls[,…]) #第一个执行魔术方法,通常返回类得实例化对象,当您继承到不可变类型得时候但又需要对其修改得时候;
(1) init(self[,…])
(2) del(self) #垃圾回收机制,类似于C++里面得析构方法

(3) add(self,other) #魔术方法计算-其他也类似
(4) and(self,other) #魔术方法逻辑运算符

(5) radd(self,other) #魔术方法反运算 比如 a + b ,如果a对象不支持 + 操作的时候,这时候就使用b的处理方案进行相加;
(6) rsub(self,other) #魔术方法 同上

(7) str(self) #字符串需要被输出的情况下,必须采用print输出
(8) repr(self) #字符串直接被输出,无需外部函数

(9) getattr(self,name) #属性访问的魔术方法,定义当用户试图获取一个不存在的属性时候;
(10) getattribute(self,name) #定义当该类的属性被访问时的行为
(11) setattr(self,name,value) #定义一个属性被设置时候的行为
(12) delattr(self,name) #定义一个属性被删除时的行为

(13) get(self,instance,owner) #描述符,用于访问属性它返回属性的值
(14) set(self,instance,value) #将在属性分配操作中调用,不返回任何内容
(15) delete(self,instance) #控制删除操作,返回任何内容

(16) len() #容器在进行 len(对象) 方法得意触发
(17) getitem() #获取容器键值的时候触发,比如字典dict[‘key’]
(18) setitem() #设置容器中指定元素的行为比如字典dict[‘key’] = 1024

案例1:按照以下要求定义一个游乐园门票的类,并尝试计算2个成人+1个小孩平日票价,面向对象编程的难点在于思维的转换。

# 平日票价100元
# 周末票价为平日的120%
# 儿童半票

#案例1:
class Ticket():
def __init__(self, weekend=False, child=False):
self.exp = 100
if weekend: #根据星期天/儿童来计算
self.inc = 1.2
else:
self.inc = 1

if child: #儿童的价格
self.discount = 0.5
else:
self.discount = 1

def calcPrice(self, num):
return self.exp * self.inc * self.discount * num #这里关键点

adult = Ticket()
child = Ticket(child=True)
print("2个成人 + 1个小孩平日票价为:%.2f" % (adult.calcPrice(2) + child.calcPrice(1)))


#案例2:
#当实例化一个对象,这个变量+1,当销毁一个对象,这个变量自动-1)
class Count:
count = 0
def __init__(self): #实例 初始化
Count.count += 1
def __del__(self): #del 调用触发 该魔术方法
Count.count -= 1
def getCount(self):
print("当前 count 的 %d 值" %Count.count)

a = Count() #触发init
b = Count()
c = Count()
d = a #这里是d 对 a 得引用
e = a
print(Count.count)
del d #注意:这里不会触发del (只有在所有得引用被删除后才能触发del)
del e #这里同样不能触发
del a #触发del
print(Count.count)


#案例3:
#CapStr 继承一个 不可变得 类
class CapStr(str):
def __new__(cls,string):
string = string.upper() #将不可变类型 变成大小
return str.__new__(cls,string) #返回修改后得字符给对象

a = CapStr('I love Study Python3!')
print(a)

############### 执行结果 #################
# 2个成人 + 1个小孩平日票价为:250.00
# 3
# 2
# I LOVE STUDY PYTHON3!


(2) 类的算法运算魔术方法
描述:在Py2.2以前类和类型是分开的(实际是类和属性的封装),但是在之后作者进行了统一(将Python类型转换成为工厂函数),例如:
工厂函数其实就是一个类对象,当你调用他们的时候,事实上就是创建一个相应的实例对象。

#实际工程函数就是类对象
>>> type(len)
<class 'builtin_function_or_method'> #内置函数

>>> type(int) #类型都是类对象
<class 'type'>
>>> type(tuple)
<class 'type'>

>>> class C:
... pass
...
>>> type(C) #类定义
<class 'type'>
#Py2.2以前
# int('123') #实际是调用了int并返回一个整形值
#py2.2以后
a = int('123') #其实就是一个实例化过后的对象

案例2:

#!/usr/bin/python
#类的魔术算法运行案例
#继承 int 类
class Newint(int):
def __add__(self, value):
return super().__add__(value) #super是超类指代父类
def __sub__(self, value):
#return super().__sub__(value) #方法1
return int(self) - int(value) #方法2,注意这里必须要强制类型转换,如过向以下则会报错递归循环异常
#return self - value #RecursionError: maximum recursion depth exceeded in comparison
def __and__(self, value): #该魔术方法 = &
return super().__and__(value)

a = Newint(8)
b = Newint(6)
print("a + b =",a + b)
print("a - b =",a - b)
print("a & b =",a & b)


#案例3:Python 魔术方法 运算有着类似于C++的 运算符重载
class int(int):
def __add__(self,other):
return int.__sub__(self,other) #重载关键点

a = int('5')
b = int('4')
print("运算符重载:a + b => a - b =", a + b)

######## 执行效果 ###########
# a + b = 14
# a - b = 2
# a & b = 0
# 运算符重载:a + b => a - b = 1


(3) 类的反算法运算魔术方法
案例:

#!/usr/bin/python
#案例1:反运算符
class Newint(int):
def __radd__(self, value): #反运算+
return int.__sub__(value,self) #方法1:执行为减,value self 顺序会影响到 谁是减数 / 被减数

def __rsub__(self, value): #反运算
return super().__add__(value) #方法2:执行为减

def __rmul__(self, value):
return super().__truediv__(value)

a = Newint(5)
b = Newint(3)
print(a + b) # 由于对象 a 可以支持 + / - , 所以不会触发反运算
print(a - b)

# 由于 1 是非对象 则采用 b 的处理方法
print("1 + b =",1 + b) # 1 - 3 => -2 由于改变了value,self顺序
print("1 - b =",1 - b) # 触发反运算 => 3 + 1 = 4
print("1 * b =",5 * b) # 触发反运算 => 3 / 5 = 0.6


#案例2:一元运算符
class OneInt(int):
def __pos__(self): #定义负号行为 -x
return super().__pos__() # -(self)

a = OneInt(-1)
print("-(a) =",-a) #此时触发一元运算符 -(-1)

######### 执行结果 ########
# 8
# 2
# 1 + b = -2
# 1 - b = 4
# 1 * b = 0.6
# -(a) = 1


(4) 类的补充魔术方法

#!/usr/bin/python
class Demo:
def __str__(self):
return '我是__str__魔术方法,需要print()输出'

class Demo1:
def __repr__(self):
return '2 - 我是__repr__魔术方法,直接对象输出'

a = Demo()
print("1 -",a)

b = Demo1()
print(b) #在>>> b 可以直接输出

################ 执行结果 #################
# 1 - 我是__str__魔术方法,需要print()输出
# 2 - 我是__repr__魔术方法,直接对象输出


(5) 类属性访问魔术方法
通过类的属性来设置与调用方法;

#!/usr/bin/python
#属性访问的魔法方法

#方法案例1:
class AttrView:
def __getattribute__(self, name):
print("调用 getattribute 魔法方法")
return super().__getattribute__(name) #super()自动获取基类,这个类没有继承类默认是object类

def __getattr__(self,name):
print('调用 getattr 魔法方法')

def __setattr__(self,name,value):
print("调用 setattr 魔法方法")
super().__setattr__(name,value)

def __delattr__(self, name):
print('调用 delattr 魔法方法')
super().__delattr__(name)

demo = AttrView()
demo.x #尝试不存在属性的时候触发 调用 getattribute / getattr(不存在触发) 魔法方法
setattr(demo,'x',1) #设置属性 调用 setattr 魔法方法
demo.y = 1 #设置属性 调用 setattr 魔法方法
demo.y #获取属性 调用 getattribute 魔法方法
delattr(demo,'y') #删除属性 调用 delattr 魔法方法


#方法案例2:
class Rectangle:
def __init__(self, width = 0, height = 0):
self.width = width #会触发__setattr__魔术方法
self.height = height

def __setattr__(self, name, value):
if name == 'square': #正方形
self.height = value
self.width = value
else:
super.__setattr__(self, name, value) # 方法1:防止无限递归错误 (建议采用基类的setattr方法)
#self.__dict__[name] = value # 方法2

def getArea(self):
return self.width * self.height

def getPeri(self):
return (2 * (self.width) + 2 * (self.height))

r1 = Rectangle(4,5)
print("矩形面积:",r1.getArea())

r1.square = 10 #建立一个属性表明是正方形
print("正方形面积: ",r1.getArea())
print("正方形周长:",r1.getPeri())
print("__dict__",r1.__dict__) #将类的全部属性放返回字典类型

########## 执行结果 ####################
# 矩形面积: 20
# 正方形面积: 100
# 正方形周长: 40
# __dict__ {'width': 10, 'height': 10}

(6) 定制序列的魔术方法
描述:协议(Protocols)与其他的编程语言中的接口很相似,它规定您那些方法必须要定义;然而在Python中的协议就显得不那么正式;事实上更新是一种指南;

要求:编写一个不可改变的自定义列表,要求记录每个元素被访问的次数;

#!/usr/bin/python3
#功能:容器序列定制类型协议()
# -*- coding:utf-8 -*-
class Countnum:
def __init__(self,*args):
self.value = [x for x in args] #列表表达式
self.count = {}.fromkeys(range(len(self.value)),0)

def __len__(self):
return len(self.value)

def __getitem__(self,index):
self.count[index] += 1
return self.value[index]

a = Countnum(1,3,5,7,9)
b = Countnum(2,4,6,8,10)

print(a[1],b[1])
print(a[1],b[1])
print("两个对象数列之和:",a[3]+b[3])

print("A对象访问的次数:",a.count)
print("B对象访问的次数:",b.count)

############ 执行结果 ################
# $ python demo3.23.py
# 3 4
# 3 4
# 两个对象数列之和: 15
# A对象访问的次数: {0: 0, 1: 2, 2: 0, 3: 1, 4: 0}
# B对象访问的次数: {0: 0, 1: 2, 2: 0, 3: 1, 4: 0}


12.5 描述符

描述:描述符就是将某种特殊类型的类的实例指派给另外一个类的属性;比如property() 是一个比较奇葩的BIF,它的作用把方法当作属性来访问,从而提供更加友好访问方式;

描述符就是一个类,一个至少实现 get()、set() 或 delete() 三个特殊方法中的任意一个的类

#!/usr/bin/python
#类属性 - 描述符

#定义一个类,为了实现原生的property原理必须使用下面的三个魔术方法
#案例1
class Decriptor:
def __get__(self,instance,owner):
print("getting ... ",self, instance, owner) #参数分别代表 (Decriptor本身类 , 类对象test , Test类本身)

def __set__(self,instance,value):
print("setting ... ",self, instance, value)

def __delete__(self,instance):
print("deleting ...",self,instance)

class Test:
x = Decriptor() #Decriptor() 类的实例 / 又叫属性x的描述符

test = Test()
test.x
test.x = 'ceshi'
del test.x

############ 执行结果 #######
# getting ... <__main__.Decriptor object at 0x000002443D18E908> <__main__.Test object at 0x000002443D18E940> <class '__main__.Test'>
# setting ... <__main__.Decriptor object at 0x000002443D18E908> <__main__.Test object at 0x000002443D18E940> ceshi
# deleting ... <__main__.Decriptor object at 0x000002443D18E908> <__main__.Test object at 0x000002443D18E940>


#案例2:自定义property
class MyProperty:
def __init__(self, fget=None, fset=None,fdel=None): #其他类的三个方法
self.fget = fget
self.fset = fset
self.fdel = fdel

def __get__(self,instance,owner):
return self.fget(instance) #传入实例对象的方法

def __set__(self,instance,value):
self.fset(instance,value)

def __delete__(self,instance):
self.fdel(instance)


class C:
def __init__(self):
self._x = None

def getX(self):
return self._x

def setX(self, value):
self._x = value

def delX(self):
print("delete 销毁属性!")
del self._x

#x 对象的描述符 (传入类里面的三个方法)
x = MyProperty(getX,setX,delX) #类实例

c = C()
c.x = 'Property'
print(c.x,c._x)
del c.x

######################
# Property Property
# delete 销毁属性!

总结:

  • init 特殊方法不应当返回除了 None 以外的任何对象。
  • add 特殊方法中不应直接return self + other会导致无限递归(深坑)。
  • radd 特殊方法中反运算需要注意 运算符支持前后顺序。

12.6 修饰符(装饰器)

修饰符是一个很著名的设计模式,经常被用于有切面需求的场景,较为经典的有插入日志、性能测试、事务处理等;有了修饰符我们就可以抽离出大量函数中与函数功能本身无关的雷同代码并继续重用。
其实修饰符就是一种优雅的封装,但要注意的是只可以在模块或类定义内对函数进行修饰,不允许修饰一个类; 一个修饰符就是一个函数,它将被修饰的函数做为参数,并返回修饰后的同名函数或其它可调用的东西。

1)@ 修饰符的介绍使用
在Python的函数中偶尔会看到函数定义的上一行有@functionName的修饰,当解释器读到@的这样的修饰符之后,会先解析@后的内容,直接就把@下一行的函数或者类作为@后边的函数的参数,然后将返回值赋值给下一行修饰的函数对象。

使用案例:

#!/usr/bin/python
import time

##############################
class Demo(object):
def __init__(self,foo):
self.foo = foo
self.foo() #调用传入的f函数

@Demo #将 f() 函数作为参数传入到类里
def f():
print("I love Python.org!")


#案例1:funcA(funcB(func(4)))
def funcA(A): #注意形参
print("function A")

def funcB(B):
print(B(2)) #B(2) 实际是传入到func中 func(2) s所以会答应c**2的值
print("function B")

@funcA
@funcB
def func(c):
print("function C")
return c**2

######### 执行结果.START########
# I love Python.org!
# function C
# 4
# function B
# function A
######### 执行结果.END########


#案例2:函数
def timeslong(func): #这样这里的 func 形参,就是修饰符下面的
def call():
start = time.perf_counter()
print("It's time starting ! ")
func() #实际调用了f函数 (func()是关键字一样)
print("It's time ending ! ")
end = time.perf_counter()
return "It's used : %s ." % (end - start) #输出打印
return call

#通过 @timeslong 对其进行修饰达到目的,是整个代码美观,而且少些部分代码
@timeslong #先执行 timelong 再调用执行 func => f()
def func():
y = 0
for i in range(5):
y = y + i + 1
print(y)
return y

print(func())
######### 执行结果.START########
# It's time starting !
# 1
# 3
# 6
# 10
# 15
# It's time ending !
# It's used : 0.006445216000000031 .
######### 执行结果.END########


#案例3.修饰符也可以通过类来进行使用
class timeslong1(object):
def __init__(self,func):
self.f = func #传入的实际是f() 函数

def __call__(self):
start = time.perf_counter()
print("It's time starting ! ")
self.f()
print("It's time ending ! ")
end = time.perf_counter()
print("It's used : %s ." % (end - start))

@timeslong1 #将下面的函数或者类作为自己的参数
def f():
y = 0
for i in range(3):
y = y + i + 1
print(y)
return y

f() #调用时候触发@修饰符

######### 执行结果.START########
# It's time starting !
# 1
# 3
# 6
# It's time ending !
# It's used : 0.00160638099999999 .
######### 执行结果.END########


2) 内置的修饰符
内置的修饰符有三个,作用分别是把类中定义的实例方法变成 静态方法( staticmethod ), 类方法 (classmethod) 和 类属性 (property) 与;注意静态方法和类方法的用处并不是太多。

#!/usr/bin/python3
#功能:实现 类方法 / 静态方法 / 类属性

class Hello(object):
def __init__(self):
pass

#方式1.类方法
@classmethod
def print_hello(cls):
print("方式1:调用类方法 print_hello()",)

#方式2: 正是因为设置静态方法和类方法过于讨人吐槽,因此 Python 的作者才开发出了函数修饰符的形式替代。
def foo(cls):
print("方式2:调用类方法 foo()")
# 将 foo() 方法设置为类方法
foo = classmethod(foo)

#方式3:静态方法 (注意这里的巨坑 self )
@staticmethod
def static_hello(arg):
return "方式4:调用静态方法 static_hello Value =" + str(arg)

#静态方法 对比点

Hello.print_hello() # 类方法修饰过后,print_hello() 就变成了类方法,可以直接通过 Hello.print_hello() 调用,而无需绑定实例对象了。
Hello.foo()
print(Hello.static_hello(1),Hello.static_hello) #注意这里传参



######### 执行结果 ################
# 方式1:调用类方法 print_hello()
# 方式2:调用类方法 foo()
# 方式3:调用静态方法 static_hello Value = 1 <function Hello.static_hello at 0x000001F2DB73C950>

# >>> c1 = Hello()
# >>> c2 = Hello()
# # 静态方法只在内存中生成一个,节省开销
# >>> c1.static_hello is C.static_hello #True
# >>> c1.nostatic is C.nostatic # False
# >>> c1.static_hello # <function Hello.static_hello at 0x000001F2DB73C950>
# >>> c2.static _hello # <function Hello.static_hello at 0x000001F2DB73C950>
# >>> Hello.static_hello # <function Hello.static_hello at 0x000001F2DB73C950>

# # 普通方法每个实例对象都拥有独立的一个,开销较大
# >>> c1.nostatic # <bound method Hello.nostatic of <__main__.C object at 0x03010590>>
# >>> c2.nostatic # <bound method Hello.nostatic of <__main__.C object at 0x032809D0>>
# >>> Hello.nostatic # <function Hello.nostatic at 0x0328D2B8>



#案例3:用属性修饰符创建描述符”的方式实现
class C:
def __init__(self, size=10):
self.size = size

@property #关键点 类属性 / 绑定的属性是x
def x(self):
return self.size

@x.setter #
def x(self, value):
self.size = value

@x.deleter
def x(self):
del self.size

demo = C()
print("获取属性的值:",demo.x) #获取属性的值: 10
demo.x = 1024
print("获取更改后属性的值:",demo.x) #获取更改后属性的值: 1024
del demo.x

总结:

  • 静态方法最大的优点是:不会绑定到实例对象上,换而言之就是节省开销。
  • 静态方法并不需要 self 参数,因此即使是使用对象去访问,self 参数也不会传进去。