好久没更新了呀,Python的学习可不能落下!😝
面向对象编程
面向对象设计思想:抽象出类(Class),根据类创建实例(Instance)。
面向对象的三大特点:封装,继承,多态。
-
类和实例:
- 类的定义:
class Student(object): pass
Student类继承了object - 创建实例:
yzl = Student()
, 创建实例后,可以自由的给实例绑定属性yzl.age = 18
, 仅对本实例有效 - 类的方法定义:
def __init__(self, name, age):
, 第一个变量必须是self, 但不用传,表示此实例变量。
- 类的定义:
-
访问限制:以
__
开头的是私有变量,外部访问不到(但其实可以访问到,只是Python解释器把私有变量换了个名字,比如_Student__name
),以_
开头的虽然可以访问,但也要当做私有变量,不要轻易访问。总之,Python本身没有任何机制阻止你干坏事,一切全靠自觉。class Student(object): def __init__(self, name, score): self.__name = name self.__score = score def print_score(self): print('%s: %s' % (self.__name, self.__score))
-
继承与多态:对于静态语言,比如Java,一个函数的入参类型必须是确定的,而对于动态语言,只要file-like object即可. 鸭子类型:一个对象只要“看起来像鸭子,走起路来像鸭子”,那它就可以被看做是鸭子。
只要whatever有run()方法就可以。def run_twice(whatever): whatever.run()
-
获取对象信息:
-
type()
判断对象类型type(123): <class 'int'>
-
isinstance()
判断一个对象是否是某种类型isinstance('abc', str): True
-
dir()
获取一个对象的所有属性和方法,它返回一个包含字符串的list -
hasattr(obj, 'x')
获取属性setattr(obj, 'y', 19)
设置属性,注意:只有在不知道对象信息的时候,我们才会去获取对象信息
-
实例属性和类属性:不通过
__init__()
构造,直接在class里定义属性的是类属性。实例属性和类属性名字不要一样,否则类属性在当前实例会被覆盖掉。限制实例的属性:
__slots__ = ('name', 'age')
, 定义一个__slots__
限制类的实例属性只能在tuple里,否则将报AttributeError
错误,注意:__slots__
只作用于当前类实例,对子类不起作用,若子类也定义了slots,则还要加上父类的slots。-
@property : 相当于 getter,@name.setter : 相当于 setter,这两个decorator的目的是让方法变为属性(调用方法生成了属性),可以写出更简短的代码,同时保证对参数进行必要性的检查。
class Screen(object): @property def width(self): return self.__width @width.setter def width(self, w): self.__width = w s = Screen() print(dir(s)) # 结果为: ['__class__', '__delattr__', '__dict__', ...] s.width = 1 print(dir(s)) # 结果为: ['_Screen__width', '__class__', '__delattr__', '__dict__', ...]
多继承:Python支持多继承,
class class Dog(Mammal, Runnable):
-
定制类:重写class的函数,使之符合我们的需求,以下是常用的定制类:
-
__str__
: 自定义打印实例,变量调用的是__repr__
class Student(object): def __init__(self, name): self.name = name def __str__(self): return 'Student object (name=%s)' % self.name __repr__ = __str__
-
__iter__
: 返回一个迭代对象,通过__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 # 返回下一个值
-
__getitem__
: 按照下标或切片取出元素# 重写__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 print(Fib()[9]) print(Fib()[:10])
-
__getattr__
: 动态返回一个属性, 可以写一个链式调用def __getattr__(self, path): return Chain('%s/%s' % (self._path, path))
-
__call__
: 直接在实例本身上调用, 通过callable()
函数,可以判断一个对象是否是“可调用”对象。class Student(object): def __init__(self, name): self.name = name def __call__(self): print('My name is %s.' % self.name) s = Student('DreamYoung') if callable(s): s() # self参数不要传入
-
-
枚举类:
Enum
枚举类,把一组相关常量定义在一个class中,class不可变,成员可直接比较from enum import Enum, unique @unique # @unique装饰器用于检查有没有重复值 class Weekday(Enum): Sun = 0 # Sun的value被设定为0 Mon = 1, 2 Tue = 2 print(Weekday.Sun) # Weekday.Sun print(Weekday.Sun.value) # 0 print(Weekday.Mon.value) # (1, 2) print(Weekday(2)) # Weekday.Tue print(Weekday['Tue']) # Weekday.Tue print(Weekday['Tue'].value) # 2
可见,既可以用成员名称引用枚举常量,又可以直接根据value的值获得枚举常量
-
元类:Python的class的定义是运行时创建的,创建的方法就是使用
type()
函数!type()
函数既可以返回一个对象的类型,又可以创建出新的类型,无需使用class关键字。def fun(self, name='DreamYoung'): print('Hello, ' + name) Hello = type('Hello', (object,), dict(hello=fun)) # 创建Hello class h = Hello() h.hello()
type()
函数需要三个参数:函数名称,继承的父类集合(tuple),方法名绑定的函数。
Python创建类也是扫一下class关键字,然后通过type创建出来类。
如果要控制类的行为,可以使用元类�metaclass,可以把类理解为metaclass创建的实例。 先定义metaclass,就可以创建类,最后创建实例。metaclass暂时不会用到,如果需要深入了解,可以参考这篇文章
错误、调试和测试
-
错误:Python内置了
try...except...finally...
用于捕获异常,所有的异常都继承自BaseException
, 查看异常继承关系注意:
- except可以有多个,多个except异常 父类会覆盖子类的异常, except后可以跟else
- finally在最后执行,可以不写
- 可以使用
logging
模块记录日志,通过查看调用堆栈,定位排查错误
写法:
try: print(1/0) except ZeroDivisionError as e: logging.exception(e) else: print('else') finally: print('finally')
-
调试:要想调试起来爽,要善于使用
logging
!logging
有debug、info、warning、error几个级别,通过:import logging logging.basicConfig(level=logging.INFO) # info级别日志,debug日志不会显示
指定当前模块的日志级别,
logging
的好处是通过简单的配置,日志可以输出到文件等等 -
单元测试:编写单元测试需要引入
unittest
模块,并编写一个从unittest.TestCase
继承测试类。
注意,测试方法必须以test
开头(test_xxx()
),否则在测试时不会被执行。unittest
提供了很多内置条件的判断,比如:assertEqual()
、assertRaises()
等等 用于判断输出值是否是我们的期望值。运行前与运行后:编写两个特殊的方法
setUp()
和tearDown()
,在每个单元测试方法执行之前执行setUp()
,执行之后执行tearDown()
。这个用处大大的,比如可以在setUp()
方法连接测试库,tearDown()
方法关闭连接。运行单元测试有两种方法:
-
加上两行代码,这样可以当做正常的Python脚步运行:
if __name__ == '__main__': unittest.main()
-
(荐)通过命令行参数直接运行单元测试 :可以批量执行单元测试
$ python3 -m unittest my_test1.py my_test2.py
-
-
文档测试:Python的很多官方文档都是示例代码,通过
doctest
模块,可以提前并执行文档注释代码。
比如就绝对值的函数abs()
写上这样的注释:def abs(n): """ Function to get absolute value of number. Example: >>> abs('') Traceback (most recent call last): ... TypeError: unorderable types: str() >= int() >>> abs(1) 1 >>> abs(-1) 1 >>> abs(0) 0 """ return n if n >= 0 else (-n + 1) # 正确的代码应为: return n if n >= 0 else (-n) if __name__ == '__main__': import doctest doctest.testmod()
会得到以下输出:
File "xxxx/demo/dream/young/python/Test.py", line 13, in main.abs
Failed example:
abs(-1)
Expected:
1
Got:
2
1 items had failures:
1 of 4 in main.abs
Test Failed 1 failures.
如果没有输出任何信息,说明注释代码没有错误。注意:当模块正常导入时,doctest不会被执行。只有在命令行直接运行时,才执行doctest。所以,不必担心doctest会在非测试环境下执行。