【Python】编程笔记7

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u012736685/article/details/85102904

面向对象高级编程

高级特性:多重继承、定制类、元类等概念。

一、slots

__slots__变量:限制该 class 实例能添加的属性。

class Student(object):
    __slots__ = ('name', 'age')

s = Student()
s.name = 'Michael'
s.age = 25
s.score = 99
print(s.name)
print(s.age)
print(s.score)

==》AttributeError 的错误

AttributeError: 'Student' object has no attribute 'score'

注意__slots__ 定义的属性仅对当前类实例起作用,对继承的子类是不起作用的。除非在子类中也定义 __slots__,这样,子类实例允许定义的属性就是自身的__slots__加上父类的__slots__。

class GraduateStudent(Student):
    pass
g = GraduateStudent()
g.score = 9999
print(g.score)    ## 9999

二、@property装饰器

负责把一个方法变成属性调用的。

class Student(object):
    @property
    def score(self):
        return self._score

    @score.setter
    def 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.score = 60
print(s.score)
s.score = 999
print(s.score)

输出结果

60
Traceback (most recent call last):
  File "E:/codes/python/basic/6.py", line 169, in <module>
    s.score = 999
  File "E:/codes/python/basic/6.py", line 163, in score
    raise ValueError('score must between 0 ~ 100!')
ValueError: score must between 0 ~ 100!

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

三、多重继承——MixIn

MixIn 的目:给一个类增加多个功能。

在设计类的时候,要优先考虑通过多重继承来组合多个 MixIn 的功能,而不是设计多层次的复杂的继承关系。

class Animal(object):
    pass
    
class RunnableMixIn(object):
    def run(self):
        print('Running...')
class Mammal(Animal):
    pass 
           
## Dog为多继承,继承于 Mammal 和 RunnableMixIn
class Dog(Mammal, RunnableMixIn):
    pass

四、定制类——__xxx__

Python 的 class 中还有许多这样有特殊用途的函数,可以帮
助我们定制类。

1、__str__()__repr__()

class Student(object):
    def __init__(self, name):
        self.name = name
    def __str__(self):
        return 'Student object (name: %s)' % self.name

print(Student('Michael'))

结果对比

  • 未自定义 __str__() 的结果
    <__main__.Student object at 0x000001994F615FD0>
  • 自定义 __str__() 的结果
    Student object (name: Michael)

在交互的模式下,直接输出 s,打印出来的 <__main__.Student object at 0x109afb310> 也并不好看。

直接显示变量调用的是 __repr__() 而非 __str__()
==》
区别:__str__()返回用户看到的字符串,而__repr__()返回程序开发者看到的字符串,也就是说,__repr__()是为调试服务的。
==》解决办法:再定义一个__repr__()

2、__iter__()

__iter__():用于 for…in 循环,该函数返回一个迭代对象。

然后,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

for n in Fib():
    print(n)

输出结果

1
1
2
3
5
8
13
21
34
55
89
144
233
377
610
987
1597
2584
4181
6765
10946
17711
28657
46368
75025

3、__getitem__()

可以按照下标取出元素 ==》__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])
print(f[1])
print(f[100])

输出结果

1
1
573147844013817084101

==》切片操作
原因:__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[0:5])
print(f[:10])

输出结果

[1, 1, 2, 3, 5]
[1, 1, 2, 3, 5, 8, 13, 21, 34, 55]

分析:没有对 step 参数、负数等做处理。
==》将对象看出 dict
==》__getitem__() 的参数可能作 key 的 object。其对应的 __setitem__() 方法:将对象看作 list 或 dict 来对集合赋值;__delitem__() 方法:删除某个元素。

4、__getattr__()

为了避免由于类的方法或属性不在造成的 AttributeError 错误,使用 __getattr__() ,动态返回一个属性。

class Student(object):
    def __init__(self):
        self.name = 'Michael'
    def __getattr__(self, attr):
        if attr == 'score':
            return 99
        elif attr == 'age':
        	return lambda:25
        # 若均不匹配,则抛出 AttributeError 的错误
        raise AttributeError('\'Student\'object has no attribute \'%s\'' % attr)

==》当调用不存在的属性时,比如 score, Python 解释器会试图调用__getattr__(self, ‘score’)来尝试获得属性,从而可以返回score的值99。

常约定 class 只响应特定的几个属性,否则抛出 AttributeError 的错误。

5、__call__()

对实例进行直接调用。

class Student(object):
    def __init__(self, name):
        self.name = name

    def __call__(self, *args, **kwargs):
        print('My name is %s' % self.name)

s = Student('Michael')
print(s())
# My name is Michael

判断一个对象是否能被调用,若可被调用,则该对象是一个 Callable 对象——callable()

>>> callable(Student())
True
>>> callable(max)
True
>>> callable([1, 2, 3])
False
>>> callable(None)
False
>>> callable('str')
False

五、枚举类——Enum类

from enum import Enum

# Month类型的枚举类  定义
Month = Enum('Month', ('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'))
print(Month.Jun)

# 枚举所有成员,value属性为int常量,默认从1开始计数
for name, member in Month.__members__.items():
    print(name, '=>', member, ',', member.value)

精确控制枚举类型 ==》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

## 枚举类型的访问
day1 = Weekday.Mon
print(day1)
print(Weekday.Tue)   # 用成员名称引用枚举常量
print(Weekday['Tue'])
print(Weekday.Tue.value)
print(day1 == Weekday.Mon)
print(day1 == Weekday.Tue)
print(Weekday(1))    # 根据 value 的值获得枚举常量
print(day1 == Weekday(1))
# print(Weekday(7))
for name, member in Weekday.__members__.items():
    print(name, '=>', member)

既可以用成员名称引用枚举常量,又可以直接根据 value 的值获得枚举常量。

六、使用元类

1、type()

动态语言的函数和类的定义在运行时动态创建的。

type()函数既可以返回一个对象的类型,又可以创建出新的类型。
。。。。。

七、错误、调试和测试

1、错误处理

处理机制:try…except…finally…(可以多个except)

如果执行出错,则后续代码不会继续执行,而是直接跳转至错误处理代码,即 except 语句块,执行完 except 后,如果有 finally 语句块,则执行 finally 语句块,至此,执行完毕。

若没有错误,执行完try部分,不执行except部分而执行finally部分。

try:
    print('try...')
    r = 10 / int('a')
    print('result: ', r)
except ValueError as e:
    print('ValueError: ', e)
except ZeroDivisionError as e:
    print('ZeroDivisionError: ', e)
finally:
    print('finally...')
print('END')

输出结果

try...
ValueError:  invalid literal for int() with base 10: 'a'
finally...
END

Python 所有的错误都是从 BaseException 类派生的,常见的错误类型和继承关系看这里:
https://docs.python.org/3/library/exceptions.html#exception-hierarchy

2、调用堆栈

解读错误信息是定位错误的关键。我们从上往下可以看到整个错误的调用函数链。

3、记录错误

import logging

def foo(s):
    return 10 / int(s)
def bar(s):
    return foo(s) * 2
def main():
    try:
        bar('0')
    except Exception as e:
        logging.exception(e)

main()
print('END')

4、抛出错误

5、调试

  • print()可能错误的变量值
  • 断言(assert):assert 表达式, ‘输出语句’==》表达式为真,继续执行,否则抛出 AssertionError错误并输出后面的输出语句。
  • logging输出到文件;
  • 调试器 pdb,可以单步调试

6、单元测试

用来对一个模块、一个函数或者一个类来进行正确性检验的测试工作。
==》确保一个程序模块的行为符合我们设计的测试用例。

测试用例:

  • 输入正数;
  • 输入负数;
  • 输入0;
  • 输入非数值类型

猜你喜欢

转载自blog.csdn.net/u012736685/article/details/85102904