Python面向对象 -- slots, @property、多重继承MixIn、定制类(str, iter, getitem, getattr, call, callable函数,可调用对象)、元类(type, metaclass)

时间:2021-07-25 04:50:45

面向对象设计中最基础的3个概念:数据封装、继承和多态

动态给class增加功能

正常情况下,当定义了一个class,然后创建了一个class的实例后,可以在程序运行的过程中给该实例绑定任何属性和方法,这就是动态语言的灵活性

(1)给一个实例绑定的方法,对另一个实例是不起作用的。

(2)为了给所有实例都绑定方法,可以给class绑定方法。

Student.set_age=set_age

注:是set_age,而不是set_age(  )

(3) 给class绑定方法后,所有实例均可调用。

 #定义了Student类
class Student(object):
pass #给实例s添加name属性
s=Student()
s.name="Jane"
print("s.name:",s.name) #给实例绑定一个方法
def set_age(self,age):
self.age=age from types import MethodType s.set_age=MethodType(set_age,s) s.set_age(23)
print("s.age:",s.age) #print("注意使用的是set_age,而不是set_age()")
Student.set_age=set_age s2=Student()
s2.set_age(45)
print("s2.age:",s2.age)

运行结果:

Python面向对象 --  slots, @property、多重继承MixIn、定制类(str, iter, getitem, getattr, call, callable函数,可调用对象)、元类(type, metaclass)

小结

通常情况下,上面的set_score方法可以直接定义在class中,但动态绑定允许在程序运行的过程中动态给class加上功能,这在静态语言中很难实现。

使用_ _slots_ _

(1)__slots__是一个特殊变量,而不是一种方法

(2)为了达到限制实例的属性这一目的,在定义class时,定义一个_ _slots_ _特殊变量,来限制该class实例添加的属性:

注意:1、_ _slots_ _仅对当前类实例起作用,对继承的子类是不起作用的

2、子类中的属性=其子类本身的属性∪继承的父类中的属性,取并集

3、使用_ _slots_ _添加属性时,一定要给属性加上引号,因为用tuple绑定的是属性名称

4、给__slots__添加属性时,使用的是元组tuple

 class Student(object):
__slots__=('name','age') #用tuple定义允许绑定的属性名称 s=Student() #创建一个实例
s.name='Maria'
s.age=23
# s.score=99 由于在类Student中限制了属性,所以添加score就会报错
print("s.name:",s.name)
print("s.age:",s.age) print("给s实例添加score属性后,报错类型为:")
try:
s.score=99
except AttributeError as e:
print("AttributeError:",e) print(' ')
print("下面定义了子类:")
class Graduate(Student): #继承了Student类,那么也继承了Student类的_ _slots_ _
__slots__=('score') #这在子类中也定义了_ _slots_ _
#看出下面g.name和g.age都正常,说明子类继承了Student类的_ _slots_ _
g=Graduate()
g.score=99
g.name='shirley'
g.age=34
print("g.name,g.age:",g.name,g.age)
print("g.score:",g.score)

运行结果:

Python面向对象 --  slots, @property、多重继承MixIn、定制类(str, iter, getitem, getattr, call, callable函数,可调用对象)、元类(type, metaclass)

Python面向对象 --  slots, @property、多重继承MixIn、定制类(str, iter, getitem, getattr, call, callable函数,可调用对象)、元类(type, metaclass)


使用__slots__变量后,子类中属性:取并集

父类中使用了__slots__变量:

a. 子类中没有使用该变量,取并集之后,子类中的属性仍然为所有属性

b.  子类中使用了__slots__变量,取并集之后,子类中的属性为子类中的属性+父类中的属性

注:如果父类中的属性定义了私有变量,子类中使用这个属性时,要注意变形

这个结论可以从上面的代码运行查看结果


@property

装饰器(decorator):

(1)可以给函数动态加上功能

(2)对于类的方法,@property 装饰器负责把一个方法变成属性调用

在类的方法中,如何使用@property?

把一个getter方法变成属性,只需要加上@property就可以了,此时,@property 本身又创建了另一个装饰器 @score.setter,负责把一个setter方法变成属性赋值。

只读属性:只定义getter方法,不定义setter方法的属性

可读写属性:既定义getter方法,又定义setter方法的属性

 class Student(object):

     @property
def score(self): #为什么要用score,因为score是属性名
return self._score @score.setter
def score(self,value): #为什么要用score,因为score是属性名
if not isinstance(value,int):
raise ValueError("score must be an integer!")
if value<0 or value>100:
raise ValueError("score must between 0 ~ 100!")
self._score=value s=Student()
s.score=78 #OK,实际转化为s.set_score(60)
print("s.score:",s.score) #OK,实际转化为s.get_score()

运行结果:

Python面向对象 --  slots, @property、多重继承MixIn、定制类(str, iter, getitem, getattr, call, callable函数,可调用对象)、元类(type, metaclass)

多重继承

通过多重继承,一个子类就可以同时获得多个父类的所有功能

参考:https://kevinguo.me/2018/01/19/python-topological-sorting/

多重:多个, 多重继承:即同时继承多个对象

MixIn

(1)MixIn的目的就是给一个类增加多个功能。    解决对同一对象用不同标准来分类的问题。

举例:从性别划分,你是男人;从国家划分,你是中国人;从职业划分,你是程序员; mixin可以轻松定义具备跟你一样特征的人:中国男程序员(天朝屌丝逗比程序员)

(2)定义

在设计类的继承关系时,通常,主线都是单一继承下来的,但是,如果需要“混入”额外的功能,通过多重继承就  可以实现,这种设计通常称之为MixIn.

(3)小结

python允许使用多重继承,因此,MixIn就是一种常见的设计

只允许单一继承的语言(如JAVA),不能使用MixIn设计

定制类

__str__

将__str__方法理解成,规范化输出格式,输出用户能看懂的格式,在Python编辑器中调用print语句返回结果时使用

__repr__:在Python编辑器中直接输入调用变量返回结果时使用

直接显示变量调用的不是__str__( ),而是__repr__( ),两者的区别:

__str__( )返回用户看到的字符串

__repr__( )返回程序开发者看到的字符串,__repr__( )是为调试服务的

通常情况下,__repr__( )和 __str__( )的代码相同,所以偷懒时可以写成这种形式:__repr__=__str__ ,类似赋值,把__str__ 里的代码赋给__repr__

 class Student(object):
def __init__(self,name):
self.name=name s = Student("Michael")
print(s)

运行结果:

Python面向对象 --  slots, @property、多重继承MixIn、定制类(str, iter, getitem, getattr, call, callable函数,可调用对象)、元类(type, metaclass)

下面使用了__str__方法后:

 class Student(object):
def __init__(self,name):
self.name=name
def __str__(self):
return 'Student Object (name :%s) ' % self.name #返回的内容,自己可以修改 s=Student("Michael")
print(s)

返回用户能够看懂的格式:

Python面向对象 --  slots, @property、多重继承MixIn、定制类(str, iter, getitem, getattr, call, callable函数,可调用对象)、元类(type, metaclass)

__str__方法 return 后面的语句可以自己修改

 class Student(object):
def __init__(self,name):
self.name=name
def __str__(self):
return "Hello, My name is %s" % self.name s=Student("Michael")
print(s)

Python面向对象 --  slots, @property、多重继承MixIn、定制类(str, iter, getitem, getattr, call, callable函数,可调用对象)、元类(type, metaclass)

__iter__

如果一个类想被用于for ……in 循环,类似list、tuple那样,就必须使用一个__iter__( )方法, 该方法返回一个迭代对象

举例:以斐波那契数列为例,写一个Fib类,可以作用于for循环:

 class fib(object):
def __init__(self):
self.a,self.b=0,1 def __iter__(self):
return self #实例本身就是迭代对象,所以返回自己 def __next__(self):
self.a,self.b=self.b,self.a+self.b
if self.a>500:
raise StopIteration()
return self.a for n in fib():
print(n)

Python面向对象 --  slots, @property、多重继承MixIn、定制类(str, iter, getitem, getattr, call, callable函数,可调用对象)、元类(type, metaclass)

__getitem__

Fib实例虽然能作用于for 循环,看起来和list有点像,但是,把它当成list来使用还是不行。比如,根据下标取对应位置的元素,需要实现__getiem__( )方法

 class fib(object):
def __init__(self):
self.a,self.b=0,1 def __iter__(self): #__iter__方法,返回一个迭代对象
return self def __next__(self):
self.a,self.b=self.b,self.a+self.b
if self.a>100:
raise StopIteration
return self.a def __getitem__(self,n): #__getitem__方法
a,b=1,1
for x in range(n):
a,b = b, a+b
return a f=fib()
print('f[0]:',f[0])
print("f[1]:",f[1])
print("f[2]:",f[2])
print("f[3]:",f[3])
print("f[13]:",f[13])

运行结果:

Python面向对象 --  slots, @property、多重继承MixIn、定制类(str, iter, getitem, getattr, call, callable函数,可调用对象)、元类(type, metaclass)

__getattr__

正常情况下,当调用的类的方法或属性不存在时,就会报错。要避免这个错误,Python有一个机制,就是写一个__getattr__( )方法,动态返回一个属性。

注意:只有在没有找到属性的情况下,才调用__getattr__,已有的属性,不会在__getattr__中查找。

 class Student(object):
def __init__(self,name):
self.name=name # 注:如果有多个属性查找不存在时,写在一起,而不是再重新写一个__getattr__方法
def __getattr__(self, attr):
if attr=='score':
return 78
if attr=='age':
return lambda :23 #因为lambda是一个函数,所以调用是要加上括号
raise AttributeError('\'Student\' object has no attrbuite :%s'%attr) s=Student("kitty")
print(s.name)
print(s.score)
print(s.age())
print(s.birth)

运行结果:

Python面向对象 --  slots, @property、多重继承MixIn、定制类(str, iter, getitem, getattr, call, callable函数,可调用对象)、元类(type, metaclass)

__call__

调用实例方法,方法 一:使用“实例.方法( )”,”“instance.method( )”形式来调用,如下代码:

 class Student(object):

     def get_score(self):
return self._score def set_score(self,value):
if not isinstance(value,int):
raise ValueError("score must be an integer!")
if value<0 or value>100:
raise ValueError("score must between 0 ~ 100!")
self._score=value s=Student()
s.set_score(78)
#s是实例,调用实例的方法,格式:实例.方法(),instance.method()
print("s.score:",s.get_score())

调用实例的方法,方法二: 直接在实例本身上调用

如何实现:任何类,只需要定义一个__call( )方法,就可以直接对实例进行调用

(我的理解好像只有调用实例的call方法时,才可以直接对实例进行调用,而调用其他的方法时,

仍需要采用 " 实例.方法( )"这种格式。另外,下面代码中定义了__call__ 方法,直接在实例本身上调用时,也没看出什么特别的)

 class Student(object):
def __init__(self, name,score):
self.name = name
self.score=score def __call__(self):
print('My name is %s.' % self.name)
print('My score is %s.' % self.score)
return "" #如果不加这一句,那么返回的结果会有None s=Student('Michael',89)
print(s())
print(s.name)

Python面向对象 --  slots, @property、多重继承MixIn、定制类(str, iter, getitem, getattr, call, callable函数,可调用对象)、元类(type, metaclass)

判断一个对象是否能被调用,能被调用的对象就是一个Callable对象。callable( )函数

函数和带有__call__( )类的实例的实例都是Callable对象

callable()函数,可以判断一个对象是否是“可调用”对象

 class Student(object):
pass s=Student()
print("callable(s):",callable(s))
print("callable(max):",callable(max))
print("callable([1,2,3]):",callable([1,2,3]))
print("callable('str'):",callable('str'))
print("callable(None):",callable(None))

Python面向对象 --  slots, @property、多重继承MixIn、定制类(str, iter, getitem, getattr, call, callable函数,可调用对象)、元类(type, metaclass)

枚举类

from enum import Enum

enum:是一个模块,即一个Python文件,

Enum:是一个类名  , Enum( )相当于创建一个实例

Value属性则是自动赋给成员的int常量,默认从1开始计数

小结:Enum可以把一组相关常量定义在一个class钟,且class不可变,而且成员可以直接比较。

 from enum import Enum

 Month=Enum('hello',('Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'))

 for name, member in Month.__members__.items():
print(name,'==>',member,'==>',member.value) print(Month.Jan)
#print(hello.Jan)#会报错

运行结果:

Python面向对象 --  slots, @property、多重继承MixIn、定制类(str, iter, getitem, getattr, call, callable函数,可调用对象)、元类(type, metaclass)

实例:

 #从Enum派生出自定义类
from enum import Enum,unique @unique #@unique装饰器可以检查保证没有重复值
class weekday(Enum):
Sun=0
Mon=1
Tue=2
Wed=3
Thu=4
Fri=5
Sat=6 print('weekday.Mon:',weekday.Mon)
print("weekday['Mon']:",weekday['Mon'])
#注意:因为中括号[]里有了单引号,所以外面要使用双引号括起来
print("weekday(1):",weekday(1))
print("weekday.Mon.value:",weekday.Mon.value)

运行结果:

Python面向对象 --  slots, @property、多重继承MixIn、定制类(str, iter, getitem, getattr, call, callable函数,可调用对象)、元类(type, metaclass)

实战:把Student类中的gender属性改成枚举类型,可以避免使用字符串:

 from enum import   Enum, unique
@unique
class Gender(Enum):
Male = 0
Female = 1 class Student(object):
def __init__(self, name, gender):
self.name = name
self.gender = gender bart = Student('Bart', Gender.Male)#创建一个实例,性别采用Gender类中的格式
if bart.gender == Gender.Male:
print(bart.gender)
print('测试通过!')
else:
print('测试失败!')

运行结果:

Python面向对象 --  slots, @property、多重继承MixIn、定制类(str, iter, getitem, getattr, call, callable函数,可调用对象)、元类(type, metaclass)

元类

type( )

动态语言和静态语言最大的不同,就是函数和类的定义,不是编译时定义的,而是运行时动态创建的。

作用:(1)可以查看一个类型或变量的类型

(2)创建出新的类

创建class对象时,type( )函数传入的3个参数:

a. 类名

b. 继承的父类集合,注意Python支持多重继承,如果只有一个父类,记住元组tuple的单元素写法,要加上逗号

c. 类的方法名和函数绑定

个人理解:即使我们使用 class Xxx……这种格式来定义类,Python解释器遇到class定义时,仅仅是扫描一下class定义的语法,仍会使用type ( )函数来创建类class.

metaclass 元类

根据metaclass创建出类,即是:先定义metaclass,然后创建类。

连接起来就是:先定义metaclass,就可以创建类,最后创建实例。

metaclass允许创建类或者修改类