我们的时代策略大本营:自由,平等,友爱。百年工匠

Python面向对象编程(下)

2020-04-14  本文已影响0人  夏海峰

数据封装、继承和多态是面向对象程序设计中最基础的3个概念,今天学习下Python中的高级特性——多重继承、定制类、元类等。

1、使用 __slots__

class Student(object):
    pass

# 给实例绑定新的属性和方法
s1 = Student()
s1.name = 'hello'
from types import MethodType
def set_age(self, age):
    self.age = age
s1.set_age = MethodType(set_age, s1)

# 测试一下
print(s1.name)  # 'hello'
s1.set_age(30)
print(s1.age)  # 30
s2 = Student()
print(s2.name)  # 报错'Student' object has no attribute 'name'


# 给类绑定新的属性和方法
Student.grade = '一年级'
def set_score(self, score):
    self.score = score
Student.set_score = set_score


# 测试一下
print(s2.grade)  # '一年级'
s2.set_score(100)
print(s2.score)  # 100
class Student(object):
    # 用tuple定义允许绑定的属性名称
    __slots__ = ('name', 'age')

s = Student()
s.name = 'geekxia'
print(s.name) # 'geekxia'
s.grade = '一年级'  # 会报错

2、Getters 与 Setters

class Student(object):
    
    # getter
    @property
    def score(self):
        return self._score
    
    # setter
    @score.setter
    def score(self, score):
        if not isinstance(score, int):
            raise ValueError('分数必须是int类型')
        if score<0 or score>100:
            raise ValueError('分数必须在0~100之间')
        self._score = score
    
    @property
    def grade(self):
        return '一年级'

s = Student()
s.score = 90
print(s.score)  # 90
print(s.grade)  # '一年级'

3、多重继承

Python支持多重继承,即一个类允许有多个父类,这是一种Mixin的设计手法。研究下面这个例子:

# 动物基类
class Animal(object):
    pass

# 会跑的,基类
class RunnableMixin(object):
    pass

# 会飞的,基类
class FlyableMixin(object):
    pass

# 哺乳动物,继承自Animal
class Mammal(Animal):
    pass

# 鸟类,继承自Animal
class Bird(Animal):
    pass

# 狗:哺乳动物、会跑
class Dog(Mammal, RunnableMixin):
    pass
# 蝙蝠:哺乳动物、会飞
class Bat(Mammal, FlyableMixin):
    pass
# 鹦鹉:鸟类、会飞
class Parrot(Bird, FlyableMixin):
    pass
# 鸵鸟:鸟类、会跑
class Ostrich(Bird, RunnableMixin):
    pass

在设计类的继承关系时,通常主线都是单一继承下来的,例如Ostrich->Bird->Animal。当需要“混入(Mixin)”额外的功能时,通过多重继承来实现,额外功能的类通常在命名时习惯加上Mixin,如RunnableMixin,FlyableMixin

4、特殊函数与定制类

形如__xxx__的变量或者函数名就要注意,这些在Python中是有特殊用途的,可以帮助我们定制类。

(1)__str____repr__

class Student(object):
    def __init__(self, name):
        self.name = name
    def __str__(self):
        return 'Student实例对象 (name: %s)' % self.name
    __repr__ = __str__

s = Student('geekxia')
print(s)  # Student实例对象 (name: geekxia)

当打印实例对象时,__str__指定了打印的内容。
在交互式控制台,直接输出实例对象时,__repr__指定了显示的内容,常用于调试。

(2)__iter____next__

如果一个类想被用于for...in循环,类似list或tuple那样,就必须实现一个__iter__方法,该方法返回一个迭代对象,然后,Python的for循环就会不断调用该迭代对象的__next__方法拿到循环的下一个值,直到遇到StopIteration错误时退出循环。

# 斐波那契数列 类
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 > 100000:
            raise StopIteration()
        # 返回下一个值
        return self.a

f = Fib() 
for n in f:
    print(n)
    
# 依次打印:
# 1
# 。。。。。
# 46368
# 75025

(3)__getitem__

上述Fib实例虽然能作用于for循环,看起来和list有点像,但使用Fib()[5]Fib()[5:10]的方式取值时会报错,这就需要定义__getitem__了。

# 斐波那契数列 类
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[5])  # 8
print(f[:10])  # [1, 1, 2, 3, 5, 8, 13, 21, 34, 55]

如果把实例对象当成 dict 看,那么还可以定义两个特殊函数,分别是__setitem____delitem__。我们自己定义的类表现得和Python自带的list、tuple、dict没什么区别,这完全归功于动态语言的“鸭子类型”,不需要强制继承某个接口。

(4)__getattr__

正常情况下,当我们调用类的方法或属性时,如果不存在,就会报错。使用__getattr__可以增强类的健壮性,示例如下:

class Student(object):
    def __init__(self, name):
        self.name = name
    def __getattr__(self, attr):
        # 属性
        if attr == 'score':
            return 100
        # 方法
        if attr == 'age':
            return lambda x:x+1
        raise AttributeError('Student类,没有 %s 属性' % attr)

s = Student('geekxia')
print(s.name)  # 'geekxia'
print(s.score)  # 100
print(s.age(20))  # 21
print(s.grade)  # AttributeError: Student类,没有 grade 属性

(5)__call__

在Python中,能否直接调用一个对象本身呢?答案是肯定的,使用__call__函数可以实现。callable()函数可以判断一个对象是否是“自身可调用”的对象。示例代码如下:

class Apple(object):
    def __init__(self,color):
        self.color = color
    def __call__(self, arg):
        print('我是苹果:'+arg)
    
a = Apple('red')

# 调用实例对象本身
print(a('哈哈'))  # 我是苹果: 哈哈


# callable() 检测一个对象是否可被调用

print(callable(a))  # True
print(callable(Apple('green')))  # True
print(callable(max))  # True
print(callable([1,2,3]))  # False
print(callable(None))  # False
print(callable('hello'))  # False

5、使用 Enum 枚举类

当需要定义一组常量时,更好办法是使用枚举类(Enum)。比如一年12个月,示例如下:

from enum import Enum

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

print(Month.Jan)  # 'Month.Jan'

for name, member in Month.__members__.items():
    print(name, '=>', member, ',', member.value)
    
# Jan => Month.Jan , 1
# Feb => Month.Feb , 2
# Mar => Month.Mar , 3
# Apr => Month.Apr , 4
# May => Month.May , 5
# Jun => Month.Jun , 6
# Jul => Month.Jul , 7
# Aug => Month.Aug , 8
# Sep => Month.Sep , 9
# Oct => Month.Oct , 10
# Nov => Month.Nov , 11
# Dec => Month.Dec , 12

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

如果需要更精确地控制枚举类型(自定义这些常量的值),可以从Enum派生出自定义类。比如一周7天,示例如下:

from enum import Enum, unique

# 定义Week类,继承自Enum
# @unique装饰器可以帮助我们检查保证没有重复值。
@unique
class Week(Enum):
    Sun = 0 # Sun的value被设定为0
    Mon = 1
    Tue = 2
    Wed = 3
    Thu = 4
    Fri = 5
    Sat = 6

print(Week.Sun)  # 'Week.Sun'
print(Week['Mon'])  # 'Week.Mon'
print(Week.Tue.value)  # 2
print(Week(1))  # 'Week.Mon'

for name, member in Week.__members__.items():
    print(name, '=>', member, ',', member.value)

# Sun => Week.Sun , 0
# Mon => Week.Mon , 1
# Tue => Week.Tue , 2
# Wed => Week.Wed , 3
# Thu => Week.Thu , 4
# Fri => Week.Fri , 5
# Sat => Week.Sat , 6

6、使用元类来创建类

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

(1)使用type()函数来创建类

type()函数除了可以检测对象和变量的类型,还可以动态地创建类,示例代码如下:

def fn(self, name='World'):
    print('Hello, %s.' % name)

# 使用 type() 创建 Hello类
Hello = type('Hello', (object,), dict(hello=fn))

h = Hello()
h.hello()  # 'Hello, World.'
h.hello('GeekXia')  # 'Hello, GeekXia.'
print(type(Hello))  # <class 'type'>
print(type(h))  # <class '__main__.Hello'>

type()函数创建Hello类,依次传入3个参数:

(2)使用 metaclass 元类来创建类

# 定义一个元类
class ListMetaclass(type):
    def __new__(cls, name, bases, attrs):
        attrs['add'] = lambda self, value: self.append(value)
        return type.__new__(cls, name, bases, attrs)

# 根据上面的元类,创建一个新的类
class MyList(list, metaclass=ListMetaclass):
    pass

# 测试一下
ml = MyList()
ml.add(1)
ml.add(2)
ml.add(3)
print(ml)  # [1, 2, 3]

参考资源:
1、廖雪峰Python教程
2、Python官方文档


END!!!

上一篇下一篇

猜你喜欢

热点阅读