一、继承
在OOP程序设计中,当我们定义一个class的时候,可以从某个现有的class继承,新的class称为子类,二被继承的class称为基类,父类或者超类。
#!/usr/local/python3/bin/python3
# -*- conding:utf-8 -*-
# Animals是父类/基类;
class Animals(object):
def __init__(self, name, age, weight):
self.name = name
self.age = age
self.weight = weight
def eat(self):
print ("%s eating......" % (self.name))
self.weight += 2
def drink(self):
print( "%s is drinking....." % (self.name))
self.weight += 1
def get_weight(self):
pass
# Dog是Animal的子类/派生类;
class Dog(Animals):
# 类里面的方法第一个参数必须是self
def jiao(self):
print ("%s wang wang ......." % (self.name))
# Cat是Animal的子类/派生类;
class Cat(Animals):
def jiao(self):
print( "%s miao miao miao........" % (self.name))
tom = Dog("tom", 12, 10)
tom.eat()
tom.jiao()
输出:
tom eating......
tom wang wang .......
二、子类需要新的属性时
当我们一个雷继承里一个类的时候,这个类就有了他继承的那个类的所有的非私有的属性与方法,当我们的子类想要添加新的属性的时候,就必须重写父类的构造函数
# Dog是Animal的子类/派生类;
class Dog(Animals): # name, age, weight, dogid
def __init__(self, name, age, weight, dogid):
# 第一种重写父类构造方法;
#self.name = name
#self.age = age
#self.weight = weight
# 第二种重写父类构造方法:
# 让Dog的父类执行它的__init__方法;
#Animals.__init__(self, name, age, weight)
# 第三种重写父类构造函数的方法;
super(Dog, self).__init__(name, age, weight)
self.dogid = dogid
super(
自己类的名称
, self).__init__(
形参
)不需要明确告诉父类的名称;
如果父类改变,只需修改 class 语句后面的继承关系即可
三、python2中的经典类,python3中的新式类
#python3 中的新式类:
class 类名(父类):
pass
@python2 中的经典类
class 类名:
pass
新式类方法与属性继承时 子类使用广度优先算法,经典类方法与属性继承时,子类使用深度优先算法
新式类的广度优先算法验证
class D(object):
def test(self):
print("D test")
class C(D):
def test(self):
print("C test")
class B(D):
def test(self):
print("B test")
class A(B,C):
def test(self):
print("A test")
a = A()
a.test()
当我们一次将A B C 的test()方法注释掉时,我们多次运行的结果为
A test -> B test -> C test -> D test
经典类的深度优先算法验证
class D:
def test(self):
print("D test")
class C(D):
pass
def test(self):
print("C test")
class B(D):
pass
# def test(self):
# print("B test")
class A(B,C):
pass
# def test(self):
# print("A test")
a = A()
a.test()
当我们一次将A B ,D 的test()方法注释掉时,我们多次运行的结果为
A test -> B test -> D test -> C test
四、特殊的类属性
1、class.__name__ 、class.__doc__ 、 class.__dict__ 方法
class Base(object):
pass
class Animals(object):
"""
父类Animals:
Attritube:
name:
age:
weight:
"""
def __init__(self, name, age, weight):
self.name = name
self.age = age
self.weight = weight
def eat(self):
print ("%s eating......" % (self.name))
self.weight += 2
class Cat(Animals):
def eat(self):
print( "%s eating......" % (self.name))
self.weight += 1
class Dog(Animals, Base):
def eat(self):
print ("%s eating......" % (self.name))
self.weight += 3
print(Animals.__name__)
print(Animals.__doc__)
# # 打印类的所有父类,以元组类型返回;
print (Animals.__bases__)
print (Dog.__bases__)
# 以字典的方式返回类的方法和属性;
print (Dog.__dict__)
# 如果类不是被导入的, 显示为__main__;
# 如果类是被import导入的, 则显示类所在的模块名
print(Animals.__module__)
2、class.__str__ 、class.__del__ 方法
当我们在类中添加下面方法时:
def __str__(self):
pass
def __del__(self):
pass
当我们打印类的对象时,解释器会自动调用__str__ 方法,当我们需要删除这个对象的时候我们有会调用__del__方法(类的析构函数,与 __init__ 类的构造函数相反)
class Student(object):
def __init__(self, name, age, score):
self.name = name
self.age = age
self.score = score
print("student is create")
def __str__(self):
return "Student:<%s,%s,%s>" %(self.name, self.age, self.score)
def __del__(self):
print("student is delete")
s1 = Student("westos", 19, 100)
print(s1)
3、class.__iter__ 方法
如果一个类想要被for...in循环,必须使用类的内置方法 def __iter__(self) ,该方法返回一个类的迭代对象。当我们的类在外部调用时,需要被使用迭代环境时候,def __iter__(self) 方法会自动被调用,返回一个可迭代的对象,被外部调用使用。
4、类的运算符重载
method | overload | call |
__init__ | 构造函数 | 对象创建: X = Class(args) |
__del__ | 析构函数 | X对象收回 |
__add__ | 云算法+ | 如果没有_iadd_, X+Y, X+=Y |
__or__ | 运算符| | 如果没有_ior_,X|Y, X|=Y |
_repr__, __str__ | 打印,转换 | print(X),repr(X),str(X) |
__call__ | 函数调用 | X(*args, **kwargs) |
__getattr__ | 点号运算 | X.undefined |
__setattr__ | 属性赋值语句 | X.any=value |
__delattr__ | 属性删除 | del X.any |
__getattribute__ | 属性获取 | X.any |
__getitem__ | 索引运算 | X[key],X[i:j] |
__setitem__ | 索引赋值语句 | X[key],X[i:j]=sequence |
__delitem__ | 索引和分片删除 | del X[key],del X[i:j] |
__len__ | 长度 | len(X),如果没有__bool__,真值测试 |
__bool__ | 布尔测试 | bool(X) |
__lt__, __gt__, __le__, __ge__, __eq__, __ne__ | 特定的比较 | X<Y,X>Y,X<=Y,X>=Y, X==Y,X!=Y 注释:(lt: less than, gt: greater than, le: less equal, ge: greater equal, eq: equal, ne: not equal ) |
__radd__ | 右侧加法 | other+X |
__iadd__ | 实地(增强的)加法 | X+=Y(or else __add__) |
__iter__, __next__ | 迭代环境 | I=iter(X), next() |
__contains__ | 成员关系测试 | item in X(任何可迭代) |
__index__ | 整数值 | hex(X), bin(X), oct(X) |
__enter__, __exit__ | 环境管理器 | with obj as var: |
__get__, __set__, __delete__ | 描述符属性 | X.attr, X.attr=value, del X.attr |
__new__ | 创建 | 在__init__之前创建对象 |
from collections import Iterable
class Student(object):
def __init__(self, name, score=(100, 90, 100)):
self.name = name
self.scores = list(score)
def __iter__(self):
# 迭代学生对象时,默认迭代的属性
return iter(self.scores)
def __gt__(self, other): # 运算符 > 号重载
print('运算符 > 重载被调用')
avg_score1 = sum(self.scores) / len(self.scores)
avg_score2 = sum(other.scores) / len(other.scores)
return avg_score1 > avg_score2
def __lt__(self, other):
print('运算符 < 重载被调用')
avg_score1 = sum(self.scores) / len(self.scores)
avg_score2 = sum(other.scores) / len(other.scores)
return avg_score1 < avg_score2
def __le__(self, other):
print('运算符 <= 重载被调用')
avg_score1 = sum(self.scores) / len(self.scores)
avg_score2 = sum(other.scores) / len(other.scores)
return avg_score1 <= avg_score2
def __ge__(self, other):
print('运算符 >= 重载被调用')
avg_score1 = sum(self.scores) / len(self.scores)
avg_score2 = sum(other.scores) / len(other.scores)
return avg_score1 >= avg_score2
def __eq__(self, other):
print('运算符 == 重载被调用')
avg_score1 = sum(self.scores) / len(self.scores)
avg_score2 = sum(other.scores) / len(other.scores)
return avg_score1 == avg_score2
s1 = Student('LI', [100, 109, 90])
s2 = Student('AD', [123, 464, 0])
print(isinstance(s1, Iterable))
print(s1 > s2)
print(s1 < s2)
print(s1 == s2)
print(s1 >= s2)
print(s1 <= s2)
输出:
True
运算符 > 重载被调用
False
运算符 < 重载被调用
True
False
运算符 >= 重载被调用
False
运算符 <= 重载被调用
True
我们通过使用def __iter__方法使类的对象变为了一个可迭代的对象,但是我们还是不能使用索引,或者像字典一样使用关键字进行检索
def __getitem__(self, index):
#当对象名[key] 为右值时会自动调用__getitem__方法
return self.scores[index]
def __setitem__(self, index, value):
#当对象名[key]当左值时被调用
self.scores[index] = value
def __delitem__(self, index):
#del 对象名[key] 时 调用
del self.__dict__[index]
我们将上的类的方法加入,就可以使用索引了,但是不能使用key-value进行检索。
s1 = Student('LI',[100,109,90])
s2 = Student('AD',[123,464,0])
print(s1[0])
print(s1[1])
print(s1[2])
s1[1]=900
print(s1[1])
输出:
100
109
90
900
我们使用了下标作为类的索引,就不能使用key-value值进行检索了,如果需要,我们就在__getitem__方法与__setitem__的最后返回一个__dict__[key]对象,类的__dict__方法会将我们类的属性和属性对应的值变为字典封装在一起。
def __getitem__(self, key):
#当对象名[key] 为右值时会自动调用__getitem__方法
return self.__dict__[key]
def __setitem__(self, key, value):
#当对象名[key]当左值时被调用
self.__dict__[key] = value
def __delitem__(self, key):
#del 对象名[key] 时 调用
del self.__dict__[key]
输出:
{'name': 'LI', 'scores': [100, 109, 90]}
{'name': 'AD', 'scores': [123, 464, 0]}
LI
90
5、property
在绑定属性时,如果我们直接把属性暴露出去,虽然写起来很简单,但是没办法检查参数,导致该对象的这个属性被任意修改,因此我们使用了私有属性,但是当我们想要取者属性时,就比较麻烦了,还要写一个内置的方法,调用也不大方便,
Python中有一个被称为属性函数(property)的小概念, 能做以下几点:
1). 属性方法的作用就是通过@property把一个方法变成一个静态属性
2). 重新实现一个属性的setter和getter方法
class Book(object):
def __init__(self, name, author, state, bookIndex):
self.name = name
self.author = author
# 0:借出 1:未借出
self.__state = state
self.bookIndex = bookIndex
# 打印对象时自动调用;str(对象)
def __str__(self):
# return "书名:{0.name} 状态:{0.state}".format(self)
return "书名:{book.name} 状态:{book.state}".format(book=self)
@property
#把类的方法变为属性
#调用类的方法 : book1.state()
#调用类的属性 : book1.state
def state(self): #当book1.state 当右值时调用
if self.__state == 0:
return '借出'
else:
return '未借出'
@state.setter
def state(self,value): #当book1.state 当左值时调用
if value in [0,1]:
self.__state = value
print('状态更改成功')
else:
raise TypeError('状态只能是0或者1')
@state.deleter #当book1.state 有关键字del 时调用
def state(self):
print('成功删除')
del self.__state
book1 = Book('Python','guido',1,'INDEX313')
print(book1.state)
book1.state = 0
print(book1.state)
del book1.state
输出:
未借出
状态更改成功
借出
成功删除
6、绑定属性与方法
当我们定义一个类,并对该类进行了实例化,此时我们也可以对该实例化对象绑定任何属性与方法,这就是动态语言的灵活性,有如下一个类:
class Student(object):
pass
我们可以发现该类并没有任何属性与方法,我们此时对该类添加一个class.name属性与class.set_name(self,name)方法:
在动态绑定中分为两种绑定方式,一种是实例绑定,另一种是类绑定。我们先看实例绑定
s = Student()
s.name = 'xiao ai'
print(s.name)
输出:
'xiao ai'
绑定一个方法:
for types import MethodType
def set_name(self,name):
self.name = name
s.set_name = MethodType(set_name,s)
s.set_name('na mei')
print(s.name)
输出:
na mei
在实例绑定中还有一种方法可以绑定一个方法:
def set_name(self,name):
self.name = name
s.set_name = set_name
s.set_name(s,'na mei')
print(s.name)
输出:
na mei
以上是一种动态绑定的方式,这种绑定方式只能够对当前实例使用,还有一种绑定是对类进行的,这种绑定方式,对实例化后的所有对象都可以使用。
对Student类绑定一个age属性:
>>> class Student(object):
... pass
>>> Student.age = 18
>>> s1 = Student()
>>> s2 = Student()
>>> print(s1.age , s2.age)
18 18
对Student类绑定一个set_age(age) 方法
>>> def set_age(self,age):
... self.age = age
...
>>> Student.set_age = set_age
>>> s1.set_age(19)
>>> s1.age
19
>>> s2.set_age(25)
>>> s2.age
25
我们使用MethodType的模块,当我们使用MethodType将set方法绑定到Student类中,并不是将这个方法直接写到Student类内部,而是在内存中创建一个link指向外部的方法,在创建Student实例的时候Link也会被复制,所以不论创建多少实例,这些实例和Student类都指向同一个Set_age()方法,同样,s1.set_age(19),也并没有在s1这个实例的内部去创建age这个属性,而是将x属性创建在外部set方法的内存区中。
>>> class Student(object):
... pass
...
>>> Student.age = 18
>>> def set_age(self,age):
... self.age = age
...
>>> from types import MethodType
>>> Student.set_age=MethodType(set_age,Student)
>>> s1 = Student()
>>> s2 = Student()
>>> s1.set_age(100) , s2.set_age(150)
(None, None)
>>> s1.age,s2.age
(150, 150)
因为所有实例享用的是同一个引用的方法,所以整个内存只存在一个。因为s2.set_age(150)比s1.set_age(100)后调用,所以后面的会覆盖掉前面的,所以所有实例该方法的值都是150
但是,如果我们想要限制class的属性怎么办,比如,只允许Student实例添加name和age属性,甚至不允许添加任何属性怎么办?
为了达到这一限制目的,Python允许class在定义的时候,定义一个特殊的__slots__变量,来限制该class可以添加的属性。
>>> class Student(object):
... __slots__ = ('name', 'age')
...
>>> s1 = Student()
>>> s1.age
Traceback (most recent call last):
File "<input>", line 1, in <module>
AttributeError: age
>>> s1.age = 15 #成功
>>> s1.age
15
>>> s1.name = 'da xue'
>>> s1.name
'da xue'
s.score = 99 # 绑定属性'score' 失败
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'Student' object has no attribute 'score'
由于'score'没有被放到__slots__中,所以不能绑定 score 属性,试图绑定 score 将得到AttributeError 的错误
使用__slots__要注意,__slots__定义的属性仅对当前类起作用,对继承的子类是不起作用的:
7、类的反射
有时候我们会碰到这样的需求,需要执行对象的某个方法,或是需要对对象的某个字段赋值,而方法名或是字段名在编码代码时并不能确定,需要通过参数传递字符串的形式输入。这个机制被称为反射或是自省(反过来让对象告诉我们他是什么)1. 访问对象的属性
#
dir([obj]):
hasattr(obj, attr):
getattr(obj, attr):
setattr(obj, attr, val):
2. 确定对象的类型
isinstance(object, classinfo):
# hasattr: 判断类的对象是否拥有该属性或方法;
print(hasattr(open1, 'name'))
print(hasattr(open1, 'mode'))
print(hasattr(open1, 'get_name'))
print(hasattr(open1, 'set_name'))
print(hasattr(open1, '__eq__')) #父类继承的方法和属性也为真
# getattr()获取对象的属性的值,如果指定返回值,对象不存在会报错;
print(getattr(open1, 'name'))
#
if hasattr(open1, 'name1'):
print(getattr(open1, 'name1'))
else:
print("no name1 property")
# 获取对象‘name1’的属性值,如果‘name1’属相不存在,则返回'Error'
print(getattr(open1, 'name1', 'Error'))
# 获取对象的方法的地址,如果该方法存在则返回该方法的内存地址,(该内存地址是一个函数体)
fun = getattr(open1, 'get_name') #变量fun指向了该方法所在的内存地址,成为该方法的另一个别名
print(fun)
print(fun()) #使用fun对该方法进行调用
#setattr设置对象属性的值,该属性存在返回该属性的值,如果不存在,
#则新建对该对象添加该属性,并将该属性的值设置为要修改的值。;
setattr(open1, 'name', '/etc/group')
print("open1.name: ", getattr(open1, 'name'))
setattr(open1, 'ad1111', '/etc/group')
print("open1.name: ", getattr(open1, 'ad1111'))
print(open1.ad1111)
#此时‘__1111’不是私有属性外部可以访问
setattr(open1, '__ads1111', '/etc/group')
print("open1.name: ", getattr(open1, '__ads1111'))
print(open1.__ads1111)
print(dir(open1))
8、类的with ... as ... 语句的实现
我们在打开一根文件时可以使用with语句打开,这样保证了我们在一段代码结束之后我们的文件是关闭的,同样我们也可以对类去实现with语句,这样我们就可以在with语句结束时自动类的实例化对象申请的内存进行释放等工作,保证了内存的安全。
class MyOpen(object):
def __init__(self,filename,mode = 'r'):
self.filename = filename
self.mode = mode
def __del__(self):
pass
def read(self):
return self.file_it.read()
def __enter__(self):
self.file_it = open(self.filename,'r')
return self.file_it
def __exit__(self, exc_type, exc_val, exc_tb):
self.file_it.close()
如上面所示我们通过 def __enter__(self) 和 def __exit__(self, exc_type, exc_val, exc_tb) 魔术方法实现了类对with语句的支持。
def __enter__(self)语句会在下面语句执行时自动被调用。
with MyOpen(pwd) as file_it
其本质的过程是执行了下面两个语句。
file_it = MyOpen(pwd) open(file_it.filename)
def __exit__(self, exc_type, exc_val, exc_tb) 语句会在with语句块执行完毕的时候自动调用,执行。我们在这里保证了,我们打开的每一个文件都会在结束之后关闭,并且,file_it这个对象也会被析构,这一段时间申请的内存也会被释放。