__slots__
创建了一个class
的实例后,我们可以给该实例绑定任何属性和方法,这就是动态语言的灵活性。
from types import MethodType#用于给某个实例绑定方法
class Student():
pass
s = Student()
s.age = 12#给实例绑定属性
def method(self,name):#定义一个函数,这个函数是要给实例绑定的方法
self.name = name
s.Method = MethodType(method,s)#给实例绑定方法,后面的参数要与定义的函数名一致
s.Method('faker')#现在就可以像使用正常的方法来使用了。
print(s.name,s.age)
给一个实例绑定的方法,对另一个实例是不起作用的。为了给所有实例都绑定方法,可以给class
绑定方法。给class
绑定方法就没有给实例绑定方法这么麻烦啦。
from types import MethodType#用于给某个实例绑定方法
class Student():
pass
s = Student()
s.age = 12#给实例绑定属性
def set_score(self,score):####增加的代码块
self.score = score
Student.set_score = set_score#增加的代码
def method(self,name):#定义一个函数,这个函数是要给实例绑定的方法
self.name = name
s.Method = MethodType(method,s)#给实例绑定方法,后面的参数要与定义的函数名一致
s.Method('faker')#现在就可以像使用正常的方法来使用了。
s.set_score(20)
print(s.name,s.age,s.score)
为了达到限制实例的属性的目的,python
允许在定义class
的是时候,定义一个特殊的__slots__
变量来限制该class
的实例能添加的属性。试图绑定未在slots
中的属性将得到AttributeError
的错误。
class Student():
__slots__ = ('name', 'age')
pass
使用__slots__
要注意,__slots__
定义的属性仅对当前类的实例起作用,对继承的子类是不起作用的。也就是说,没有声明__slots__
的子类对于属性的命名是任意的。
如果在子类中定义了__slots__
,那么子类实例允许的属性就是父类的slots
加上子类的slots
。
使用@property
python
有既能检查参数,又能用类似属性这样简单的方式来访问类的变量。
对于类的方法,装饰器一样起作用。Python
内置的@property
装饰器就是负责把一个方法变成属性调用的。把一个getter
方法变成属性,只需要加上@property
就可以了,此时,@property
本身又创建了另一个装饰器@score.setter
,负责把一个setter
方法变成属性赋值。
class Student():
@property
def getter1(self):#getter1属性的getter方法
return self.score
#注意,两个方法名应一致,都是属性名
#getter前用@property setter前用@属性名.setter
@getter1.setter
def getter1(self,value):#getter1属性的setter方法
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
pass
s = Student()
s.getter1 = 30
print(s.getter1)
还可以定义只读属性,只定义getter
方法,不定义setter
方法就是一个只读属性。
class Student(object):
@property
def birth(self):
return self._birth
@birth.setter
def birth(self, value):
self._birth = value
@property#只读属性
def age(self):
return 2015 - self._birth
@property
广泛应用在类的定义中,可以让调用者写出简短的代码,同时保证对参数进行必要的检查,这样,程序运行时就减少了出错的可能性。
多重继承
继承是面向对象编程的一个重要的方式,因为通过继承,子类就可以扩展父类的功能。
一个对象,首先是一个人。人可以分为男人和女人(当然啦,不男不女本例不考虑)。
男人中,有可能是男学生,有可能是男老师。女人中,也有可能是女学生,也有可能是女老师。如果单继承,就乱死了。要用多继承来解决这个问题。
class Person():#定义人类
pass
class Men(Person):#男人
pass
class Women(Person):#女人
pass
class Teacher():#老师
def teaching(self):
print('Teaching...')
class Student():#学生
def learning(self):
print('Learning...')
class V(Men,Teacher):#多重继承 下同
pass
class Y(Men,Student):
pass
class H(Women,Teacher):
pass
class Z(Women,Student):
pass
v = V()#实例化
v.teaching()
y = Y()
y.learning()
h = H()
h.teaching()
z = Z()
z.learning()
通过多重继承,一个子类就可以同时获得多个父类的所有功能。
MixIn
在设计类的继承关系时,通常,主线都是单一继承下来的,例如,男人继承自人。但是,如果需要“混入”额外的功能,通过多重继承就可以实现,比如,让男人除了继承自人外,再同时继承教师。这种设计通常称之为MixIn。
为了更好地看出继承关系,我们把Teacher
和Student
改为TeacherMixIn
和StudentMixIn
。类似的,还可以定义出RunnerMixIn
.让每个对象可以有多个MixIn
.
class V(Men,TeacherMixIn,RunningMixIn):
pass
MixIn
的目的就是给一个类增加多个功能,这样,在设计类的时候,我们优先考虑通过多重继承来组合多个MixIn
的功能,而不是设计多层次的复杂的继承关系。这样一来,我们不需要复杂而庞大的继承链,只要选择组合不同的类的功能,就可以快速构造出所需的子类。
定制类
看到类似__slots__
这种形如__xxx__
的变量或者函数名就要注意,这些在Python
中是有特殊用途的。__len__()
方法我们也知道是为了能让class作用于len()
函数。除此之外,Python
的class
中还有许多这样有特殊用途的函数,可以帮助我们定制类。
用__str__
可以打印的好看点。
class Student(object):
def __init__(self, name):
self.name = name
def __str__(self):
return 'Student object (name=%s)' % self.name
s = Student('faker')
print(s)
写一个类想被用于for
循环。
如果一个类想被用于for ... in
循环,类似list
或tuple
那样,就必须实现一个__iter__()
方法,该方法返回一个可迭代对象,然后,Python
的for
循环就会不断调用该可迭代对象的__next__()
方法拿到循环的下一个值,直到遇到StopIteration
错误时退出循环。
class Fib(object):
def __init__(self):
self.a, self.b = 0, 1 # 初始化两个计数器a,b
def __iter__(self):
return self # 实例本身就是迭代对象,故返回自己
def __next__(self):
self.a, self.b = self.b, self.a + self.b # 计算下一个值
if self.a > 100000: # 退出循环的条件
raise StopIteration()
return self.a # 返回下一个值
#这样
f = Fib()
for i in f:
print(i)
#或者这样
for i in Fib():
print(i)
Fib
实例虽然能作用于for
循环,看起来和list
有点像,但是,把它当成list
来使用还是不行。因为它不能取索引。要表现得像list那样按照下标(索引)取出元素,需要实现__getitem__()
方法:
class Fib(object):
def __getitem__(self, n):
a, b = 1, 1
for x in range(n):
a, b = b, a + b
return a
f = Fib()
print(f[0],f[1])
但是这种方法对于切片又不行了。原因是__getitem__()
传入的参数可能是一个int
,也可能是一个切片对象slice
,所以要做判断。
class Fib(object):
def __getitem__(self, n):
if isinstance(n, int): # n是索引
a, b = 1, 1
for x in range(n):
a, b = b, a + b
return a
if isinstance(n, slice): # n是切片
start = n.start
stop = n.stop
if start is None:
start = 0
a, b = 1, 1
L = []
for x in range(stop):
if x >= start:
L.append(a)
a, b = b, a + b
return L
f = Fib()
print(f[3],f[0:3])
此外,如果把对象看成dict
,__getitem__()
的参数也可能是一个可以作key
的object
,例如str
。
与之对应的是__setitem__()
方法,把对象视作list
或dict
来对集合赋值。最后,还有一个__delitem__()
方法,用于删除某个元素。
总之,通过上面的方法,我们自己定义的类表现得和Python
自带的list、tuple、dict
没什么区别,这完全归功于动态语言的“鸭子类型”,不需要强制继承某个接口。
正常情况下,当我们调用类的方法或属性时,如果不存在,就会报错。
class Student(object):
def __init__(self):
self.name = 'vth'
s = Student()
print(s.score)#这种情况会报错
错误信息很清楚地告诉我们,没有找到score
这个attribute
。
要避免这个错误,除了可以加上一个score
属性外,Python
还有另一个机制,那就是写一个__getattr__()
方法,动态返回一个属性。
class Student(object):
def __init__(self):
self.name = 'vth'
def __getattr__(self, attr):
if attr=='score':
return 99
s = Student()
print(s.score)
当调用不存在的属性时,比如score
,Python
解释器会试图调用__getattr__(self, 'score')
来尝试获得属性。
返回函数也是完全可以的,只是需要改变一下调用方式而已。
class Student(object):
def __getattr__(self, attr):
if attr=='age':
return lambda: 25
s = Student()
print(s.age())
注意,只有在没有找到属性的情况下,才调用__getattr__
,已有的属性,比如name
,不会在__getattr__
中查找。
一个对象实例可以有自己的属性和方法,当我们调用实例方法时,我们用实例名.方法
来调用。
任何类,只需要定义一个__call__()
方法,就可以直接对实例进行调用。
class Student(object):
def __init__(self, name):
self.name = name
def __call__(self):
print('My name is %s.' % self.name)
s = Student('vth')
s()
__call__()
还可以定义参数。对实例进行直接调用就好比对一个函数进行调用一样,所以你完全可以把对象看成函数,把函数看成对象,因为这两者之间本来就没啥根本的区别。
通过callable()
函数,我们就可以判断一个对象是否是“可调用”对象。
callable(Student())
枚举类型的每个常量都是class
的一个唯一实例。Python
提供了Enum
类来实现这个功能。
from enum import Enum
Month = Enum('Month', ('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'))
这样我们就获得了Month
类型的枚举类,可以直接使用Month.Jan
来引用一个常量,或者枚举它的所有成员:
for name, member in Month.__members__.items():#name就是Jan,member就是Month.Jan
print(name, '=>', member, ',', member.value)
value
属性则是自动赋给成员的int
常量,默认从1开始计数。
如果需要更精确地控制枚举类型,可以从Enum
派生出自定义类:
from enum import Enum, unique
@unique
class Weekday(Enum):
Sun = 0 # Sun的value被设定为0
Mon = 1
Tue = 2
Wed = 3
Thu = 4
Fri = 5
Sat = 6
@unique
装饰器可以帮助我们检查保证没有重复值。
访问方式:
from enum import Enum, unique
@unique
class Weekday(Enum):
Sun = 0 # Sun的value被设定为0
Mon = 1
Tue = 2
Wed = 3
Thu = 4
Fri = 5
Sat = 6
day1 = Weekday.Mon
print(day1)
print(Weekday.Tue)
print(Weekday['Tue'])
print(Weekday.Tue.value)
print(Weekday(1))
result
:
Weekday.Mon
Weekday.Tue
Weekday.Tue
2
Weekday.Mon
可见,既可以用成员名称引用枚举常量,又可以直接根据value
的值获得枚举常量。
使用元类
动态语言和静态语言最大的不同,就是函数和类的定义,不是编译时定义的,而是运行时动态创建的。比方说我们要定义一个Hello
的class
,就写一个liaoxuefeng.py
模块:
class Hello(object):
def hello(self, name='world'):
print('Hello, %s.' % name)
当Python
解释器载入liaoxuefeng
模块时,就会依次执行该模块的所有语句,执行结果就是动态创建出一个Hello
的class
对象。
from liaoxuefeng import Hello
h = Hello()
h.hello()
print('type(hello)','-->',type(Hello))
print('type(h)','-->',type(h))
result
:
Hello, world.
type(hello) --> <class 'type'>
type(h) --> <class 'liaoxuefeng.Hello'>
type()
函数可以查看一个类型或变量的类型,Hello
是一个class
,它的类型就是type
,而h是一个实例,它的类型就是class Hello
。
我们可以通过type()
函数创建出Hello
类,而无需通过class Hello(object)...
的定义:
def fn(self, name='world'): # 先定义函数
print('Hello, %s.' % name)
Hello = type('Hello', (object,), dict(hello=fn)) # 创建Hello class
h = Hello()
h.hello()
print(type(Hello))
print(type(h))
result
:
Hello, world.
<class 'type'>
<class '__main__.Hello'>
我们说class
的定义是运行时动态创建的,而创建class
的方法就是使用type()
函数。
type()
函数的两大功能:既可以返回一个对象的类型,又可以创建出新的类型。
要创建一个class
对象,type()
函数依次传入3
个参数:
class
的名称;- 继承的父类集合,注意
Python
支持多重继承,如果只有一个父类,别忘了tuple
的单元素写法; class
的方法名称与函数绑定,这里我们把函数fn
绑定到方法名hello
上。通过
type()
函数创建的类和直接写class
是完全一样的,因为Python
解释器遇到class
定义时,仅仅是扫描一下class
定义的语法,然后调用type()
函数创建出class
。也就是说,python
解释器是这么创建类的。
正常情况下,我们都用class Xxx...
来定义类,但是,type()
函数也允许我们动态创建出类来。
元类
基本用不到(廖大说的)
当我们定义了类以后,就可以根据这个类创建出实例,所以:先定义类,然后创建实例。
但是如果我们想创建出类呢?那就必须根据metaclass
创建出类,所以:先定义metaclass
,然后创建类。
连接起来就是:先定义metaclass
,就可以创建类,最后创建实例。
✳所以,metaclass
允许你创建类或者修改类。换句话说,你可以把类看成是metaclass
创建出来的“实例”。
定义ListMetaclass
,按照默认习惯,metaclass
的类名总是以Metaclass
结尾,以便清楚地表示这是一个metaclass
:
# metaclass是类的模板,所以必须从`type`类型派生:
class ListMetaclass(type):
def __new__(cls, name, bases, attrs):
attrs['add'] = lambda self, value: self.append(value)
return type.__new__(cls, name, bases, attrs)
这个metaclass
可以给我们自定义的MyList
增加一个add
方法。
有了ListMetaclass
,我们在定义类的时候还要指示使用ListMetaclass
来定制类,传入关键字参数metaclass
:
class MyList(list, metaclass=ListMetaclass):
pass
当我们传入关键字参数metaclass
时,魔术就生效了,它指示Python
解释器在创建MyList
时,要通过ListMetaclass.__new__()
来创建,在此,我们可以修改类的定义,比如,加上新的方法,然后,返回修改后的定义。
__new__()
方法接收到的参数依次是:
当前准备创建的类的对象;
类的名字;
类继承的父类集合;
类的方法集合。
Mylist
可以使用add
方法,而普通的list
就不能使用。
再话元类#转载
转自http://python.jobbole.com/88795/ 侵删
学懂元类,你只需要知道两句话:
道生一,一生二,二生三,三生万物
我是谁?我从哪来里?我要到哪里去?
在python
世界,拥有一个永恒的道,那就是“type
”,请记在脑海中,type
就是道。如此广袤无垠的python
生态圈,都是由type
产生出来的。
道生一,一生二,二生三,三生万物。
道 即是 type
一 即是 metaclass
(元类,或者叫类生成器)
二 即是 class
(类,或者叫实例生成器)
三 即是 instance
(实例)
万物 即是 实例的各种属性与方法,我们平常使用python
时,调用的就是它们。
道和一,是我们今天讨论的命题,而二、三、和万物,则是我们常常使用的类、实例、属性和方法,用hello world
来举例:
# 创建一个Hello类,拥有属性say_hello ----二的起源
class Hello():
def say_hello(self, name='world'):
print('Hello, %s.' % name)
# 从Hello类创建一个实例hello ----二生三
hello = Hello()
# 使用hello调用方法say_hello ----三生万物
hello.say_hello()
这就是一个标准的“二生三,三生万物”过程。 从类到我们可以调用的方法,用了这两步。
class Hello
其实是一个函数的“语义化简称”,只为了让代码更浅显易懂,它的另一个写法是:
def fn(self, name='world'): # 假如我们有一个函数叫fn
print('Hello, %s.' % name)
Hello = type('Hello', (object,), dict(say_hello=fn)) # 通过type创建Hello class ---- 神秘的“道”,可以点化一切,这次我们直接从“道”生出了“二”
这样的写法,就和之前的Class Hello
写法作用完全相同,你可以试试创建实例并调用。
# 从Hello类创建一个实例hello ----二生三,完全一样
hello = Hello()
# 使用hello调用方法say_hello ----三生万物,完全一样
hello.say_hello()
我们回头看一眼最精彩的地方,道直接生出了二:
Hello = type(‘Hello’, (object,), dict(say_hello=fn))
这就是“道”,python
世界的起源,你可以为此而惊叹。
注意它的三个参数!暗合人类的三大永恒命题:我是谁,我从哪里来,我要到哪里去。
第一个参数:我是谁。 在这里,我需要一个区分于其它一切的命名,以上的实例将我命名为“Hello
”
第二个参数:我从哪里来
在这里,我需要知道从哪里来,也就是我的“父类”,以上实例中我的父类是“object
”——python
中一种非常初级的类。
第三个参数:我要到哪里去
在这里,我们将需要调用的方法和属性包含到一个字典里,再作为参数传入。以上实例中,我们有一个say_hello
方法包装进了字典中。
值得注意的是,三大永恒命题,是一切类,一切实例,甚至一切实例属性与方法都具有的。理所应当,它们的“创造者”,道和一,即type
和元类,也具有这三个参数。但平常,类的三大永恒命题并不作为参数传入,而是以如下方式传入:
class Hello(object){
# class 后声明“我是谁”
# 小括号内声明“我来自哪里”
# 中括号内声明“我要到哪里去”
def say_hello(){
}
}
- 造物主,可以直接创造单个的人,但这是一件苦役。造物主会先创造“人”这一物种,再批量创造具体的个人。并将三大永恒命题,一直传递下去。
- “道”可以直接生出“二”,但它会先生出“一”,再批量地制造“二”。
type
可以直接生成类(class
),但也可以先生成元类(metaclass
),再使用元类批量定制类(class
)。
元类——道生一,一生二
一般来说,元类均被命名后缀为Metalass
。想象一下,我们需要一个可以自动打招呼的元类,它里面的类方法呢,有时需要say_Hello
,有时需要say_Hi
,有时又需要say_Sayolala
,有时需要say_Nihao
。
如果每个内置的say_xxx
都需要在类里面声明一次,那将是多么可怕的苦役! 不如使用元类来解决问题。
以下是创建一个专门“打招呼”用的元类代码:
class SayMetaClass(type):
def __new__(cls, name, bases, attrs):
attrs['say_'+name] = lambda self,value,saying=name: print(saying+','+value+'!')
return type.__new__(cls, name, bases, attrs)
记住两点:
1、元类是由“type
”衍生而出,所以父类需要传入type
。【道生一,所以一必须包含道】
2、元类的操作都在 __new__
中完成,它的第一个参数是将创建的类,之后的参数即是三大永恒命题:我是谁,我从哪里来,我将到哪里去。 它返回的对象也是三大永恒命题,接下来,这三个参数将一直陪伴我们。
在__new__
中,我只进行了一个操作,就是
attrs['say_'+name] = lambda self,value,saying=name: print(saying+','+value+'!')
它跟据类的名字,创建了一个类方法。比如我们由元类创建的类叫“Hello
”,那创建时就自动有了一个叫“say_Hello
”的类方法,然后又将类的名字“Hello
”作为默认参数saying
,传到了方法里面。然后把hello
方法调用时的传参作为value
传进去,最终打印出来。
那么,一个元类是怎么从创建到调用的呢?
来!一起根据道生一、一生二、二生三、三生万物的准则,走进元类的生命周期吧!
# 道生一:传入type
class SayMetaClass(type):
# 传入三大永恒命题:类名称、父类、属性
def __new__(cls, name, bases, attrs):
# 创造“天赋”
attrs['say_'+name] = lambda self,value,saying=name: print(saying+','+value+'!')
# 传承三大永恒命题:类名称、父类、属性
return type.__new__(cls, name, bases, attrs)
# 一生二:创建类
class Hello(object, metaclass=SayMetaClass):
pass
# 二生三:创建实列
hello = Hello()
# 三生万物:调用实例方法
hello.say_Hello('world!')
注意:通过元类创建的类,第一个参数是父类,第二个参数是metaclass
普通人出生都不会说话,但有的人出生就会打招呼说“Hello
”,“你好”,“sayolala
”,这就是天赋的力量。它会给我们面向对象的编程省下无数的麻烦。
现在,保持元类不变,我们还可以继续创建Sayolala
, Nihao
类,如下:
# 一生二:创建类
class Sayolala(object, metaclass=SayMetaClass):
pass
# 二生三:创建实列
s = Sayolala()
# 三生万物:调用实例方法
s.say_Sayolala('japan!')
也可以说中文
# 一生二:创建类
class Nihao(object, metaclass=SayMetaClass):
pass
# 二生三:创建实列
n = Nihao()
# 三生万物:调用实例方法
n.say_Nihao('中华!')
再来一个小例子:
# 道生一
class ListMetaclass(type):
def __new__(cls, name, bases, attrs):
# 天赋:通过add方法将值绑定
attrs['add'] = lambda self, value: self.append(value)
return type.__new__(cls, name, bases, attrs)
# 一生二
class MyList(list, metaclass=ListMetaclass):
pass
# 二生三
L = MyList()
# 三生万物
L.add(1)
而普通的list
没有add()
方法。
=-=留着慢慢看。