【廖雪峰】面向对象高级编程

__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。
为了更好地看出继承关系,我们把TeacherStudent改为TeacherMixInStudentMixIn。类似的,还可以定义出RunnerMixIn.让每个对象可以有多个MixIn.

class V(Men,TeacherMixIn,RunningMixIn):
    pass

MixIn的目的就是给一个类增加多个功能,这样,在设计类的时候,我们优先考虑通过多重继承来组合多个MixIn的功能,而不是设计多层次的复杂的继承关系。这样一来,我们不需要复杂而庞大的继承链,只要选择组合不同的类的功能,就可以快速构造出所需的子类。

扫描二维码关注公众号,回复: 3443192 查看本文章

定制类

看到类似__slots__这种形如__xxx__的变量或者函数名就要注意,这些在Python中是有特殊用途的。__len__()方法我们也知道是为了能让class作用于len()函数。除此之外,Pythonclass中还有许多这样有特殊用途的函数,可以帮助我们定制类。

__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循环,类似listtuple那样,就必须实现一个__iter__()方法,该方法返回一个可迭代对象,然后,Pythonfor循环就会不断调用该可迭代对象的__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__()的参数也可能是一个可以作keyobject,例如str
与之对应的是__setitem__()方法,把对象视作listdict来对集合赋值。最后,还有一个__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)

当调用不存在的属性时,比如scorePython解释器会试图调用__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的值获得枚举常量。

使用元类

动态语言和静态语言最大的不同,就是函数和类的定义,不是编译时定义的,而是运行时动态创建的。比方说我们要定义一个Helloclass,就写一个liaoxuefeng.py模块:

class Hello(object):
    def hello(self, name='world'):
        print('Hello, %s.' % name)

Python解释器载入liaoxuefeng模块时,就会依次执行该模块的所有语句,执行结果就是动态创建出一个Helloclass对象。

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”,这就是天赋的力量。它会给我们面向对象的编程省下无数的麻烦。
现在,保持元类不变,我们还可以继续创建SayolalaNihao类,如下:

# 一生二:创建类
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()方法。
=-=留着慢慢看。

猜你喜欢

转载自blog.csdn.net/weixin_41687289/article/details/81988867