Python学习笔记(四)

1. 面向对象编程(OOP)

面向对象编程,简称OOP,是一种程序设计思想。OOP把对象作为程序的基本单元,一个对象包含了数据和操作数据的函数。
在Python中,所有数据类型都可以视为对象,当然也可以自定义对象。自定义的对象数据类型就是面向对象中的(Class)的概念。

  1. class Student(object):
  2. def __init__(self, name, score):
  3. self.name = name
  4. self.score = score
  5. def print_score(self):
  6. print('%s: %s' % (self.name, self.score))

给对象发消息实际上是调用对象对应的关联函数,称之为对象的方法(Method)

1.1 类和实例

面向对象最重要的概念就是类(Class)和实例(Insance)。
在Python中定义类用class关键字,class后紧接着是类名,类名通常是大写开头的单词,紧接着是Object。
由于类可以起到模板的作用,因此,在创建实类的时候,把一些我们认为是必须绑定的属性强制填写进去。通过定义一个特殊的_init_方法。

  1. class Student(object):
  2. def __init__(self, name, score): # 注意_init_第一个参数永远是self,表示创建实例的本身
  3. self.name = name
  4. self.score = score

数据封装:
封装数据的函数是和类本身关联起来的,我们称之为类的方法:

  1. class Student(object):
  2. def __init__(self, name, score):
  3. self.name = name
  4. self.score = score
  5. def print_score(self): # 除了第一个参数是self外,其他和普通函数一样
  6. print('%s: %s' % (self.name, self.score))

1.2 访问限制

如果让内部属性不被外部访问修改,可以把属性的名称的前面加上两个下划线_,在Python中,实例的变量名如果以__开头,就变成了一个私有变量(private),只有内部可以访问,外部不能访问。

  1. class Student(object):
  2. def __init__(self, name, score):
  3. self.__name = name
  4. self.__score = score
  5. def print_score(self):
  6. print('%s: %s' % (self.__name, self.__score))

如果外部代码想获取和修改name和score,可以给类增加get_score和set_score这样的方法。

  1. class Student(object):
  2. ...
  3. def get_score(self):
  4. return self.__score
  5. def set_score(self, score):
  6. self.__score = score

在Python中,以双下划线_开头和以双下划线_结束的变量名,类似__xx__这样的是特殊变量,特殊变量是可以直接访问的,不是private变量。
以一个下划线开头的变量,比如_name,这样的实例变量是可以直接访问的,但是按照约定成俗的规定,意思就是”虽然我可以被直接访问,但是,请把我视为私有变量,不要随意访问”。
双下划线开头的变量,比如__name也不是一定不能被外部直接访问,可以通过_Student__name来访问__name变量。

1.3 继承和多态

在OOP程序设计中,当我们定义一个Class的时候,可以从某个现有的Class继承,新的Class类称为子类,而被继承的类称为基类,父类或者超类。

  1. # 基类
  2. class Animal(object):
  3. def run(self):
  4. print('Animal is running...')
  5. # 子类
  6. class Dog(Animal):
  7. pass

继承的好处:

  1. 子类可以继承父类所有的方法。
  2. 子类可以自己增加方法。

    当子类和父类同时拥有相同的方法时,子类覆盖了父类的方法,执行子类的方法,称之为多态
    多态的好处,就是著名的开闭原则。对拓展开放,对修改关闭。
    静态语言VS动态语言
    对于静态语言(Java)来说,如果需要传入Animal类型,则传入的对象必须是Animal类型或者它的子类,否则,则无法调用run()方法。
    对于动态语言(Python)来说,则不一定需要传入Animal类,我们只需要保证传入的对象一个run()方法就行。

  1. class Timer(object):
  2. def run(self):
  3. print('Start...')

1.4 获取对象信息

  1. 判断对象类型,使用type()。
  2. 对于class继承关系来说,使用type()就不方便,要判断class的类型,可以使用isinstance()函数。能用type()判断的基本类型也可以用isinstance()判断。
  3. 如果要获得一个对象的所有属性和方法,可以使用dir()函数,返回一个包含字符串的list。
  1. # type()判断对象类型
  2. >>> type(abs)
  3. <class 'builtin_function_or_method'>
  4. >>> type(a)
  5. <class '__main__.Animal'>
  6. # isinstance()判断对象类型
  7. >>> isinstance([1, 2, 3], (list, tuple))
  8. True
  9. >>> isinstance((1, 2, 3), (list, tuple))
  10. True
  11. # dir()获取对象的所有属性和方法
  12. >>> dir('ABC')
  13. ['__add__', '__class__', '__contains__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__getnewargs__', '__gt__', '__hash__', '__init__', '__iter__', '__le__', '__len__', '__lt__', '__mod__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__rmod__', '__rmul__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'capitalize', 'casefold', 'center', 'count', 'encode', 'endswith', 'expandtabs', 'find', 'format', 'format_map', 'index', 'isalnum', 'isalpha', 'isdecimal', 'isdigit', 'isidentifier', 'islower', 'isnumeric', 'isprintable', 'isspace', 'istitle', 'isupper', 'join', 'ljust', 'lower', 'lstrip', 'maketrans', 'partition', 'replace', 'rfind', 'rindex', 'rjust', 'rpartition', 'rsplit', 'rstrip', 'split', 'splitlines', 'startswith', 'strip', 'swapcase', 'title', 'translate', 'upper', 'zfill']

1.5 实例属性和类属性

由于Python是动态语言,根据类创建的实例可以绑定任意属性。
在编写程序的时候,千万不要把实例属性和类属性使用相同的名字,因为相同名称的实例属性将屏蔽掉类属性,当删除实例属性的时候,再使用相同的名称,访问到的将是类属性。

  1. >>> class Student(object):
  2. ... name = 'Student'
  3. ...
  4. >>> s = Student() # 创建实例s
  5. >>> print(s.name) # 打印name属性,因为实例并没有name属性,所以会继续查找class的name属性
  6. Student
  7. >>> print(Student.name) # 打印类的name属性
  8. Student
  9. >>> s.name = 'Michael' # 给实例绑定name属性
  10. >>> print(s.name) # 由于实例属性优先级比类属性高,因此,它会屏蔽掉类的name属性
  11. Michael
  12. >>> print(Student.name) # 但是类属性并未消失,用Student.name仍然可以访问
  13. Student
  14. >>> del s.name # 如果删除实例的name属性
  15. >>> print(s.name) # 再次调用s.name,由于实例的name属性没有找到,类的name属性就显示出来了
  16. Student

2. 面向对象高级编程

2.1 使用_slots_

为了限制实例的属性,Python允许在定义class的时候,定义一个特殊的_slots_变量。

  1. class Student(object):
  2. __slots__ = ('name', 'age') # 用tuple定义允许绑定的属性名称
  1. >>> s = Student() # 创建新的实例
  2. >>> s.name = 'Michael' # 绑定属性'name'
  3. >>> s.age = 25 # 绑定属性'age'
  4. >>> s.score = 99 # 绑定属性'score'
  5. Traceback (most recent call last):
  6. File "<stdin>", line 1, in <module>
  7. AttributeError: 'Student' object has no attribute 'score'

使用_slots_要注意,_slots_定义的属性仅对当前类实例起作用,对继承的子类是不起作用的。

2.2 使用@property

在绑定属性的时候,我们不能直接把属性暴露出去,为了限制属性的范围,可以通过get和set方法检查参数。
Python内置的@property装饰器就是负责把一个方法变成属性调用。

  1. class Student(object):
  2. @property
  3. def score(self):
  4. return self._score
  5. @score.setter # 只定义getter方法,不定义setter方法就表明该属性只读
  6. def score(self, value):
  7. if not isinstance(value, int):
  8. raise ValueError('score must be an integer!')
  9. if value < 0 or value > 100:
  10. raise ValueError('score must between 0 ~ 100!')
  11. self._score = value

2.3 多重继承

通过多重继承,一个子类就可以同时获得多个子类的所有功能。
在设计类的继承关系时,通常,主线是单一继承下来的,如果需要混入”额外”的功能,通常就需要多重继承来实现。这种设计称之为”MixIn”。
我们不需要负责庞大的继承链,只有选择组合不同类的功能,就可以快速构造出所需的子类。

2.4 定制类

_str_
print打印实例的时候,会出现一堆类似<main.Student object at 0x109afb190>,为了打印好看,我们只需要定义一个_str_方法。但是直接打印的时候还是会出现类似的问题,那么再定义一个_repr_()就会与_str_()效果一样。

  1. class Student(object):
  2. def __init__(self, name):
  3. self.name = name
  4. def __str__(self):
  5. return 'Student object (name=%s)' % self.name
  6. __repr__ = __str__

_iter_
如果一个类想被用于for…in…循环,类似list和tuple这样,就必须实现一个_iter_方法,该方法返回一个迭代对象。

  1. class Fib(object):
  2. def __init__(self):
  3. self.a, self.b = 0, 1 # 初始化两个计数器a,b
  4. def __iter__(self):
  5. return self # 实例本身就是迭代对象,故返回自己
  6. def __next__(self):
  7. self.a, self.b = self.b, self.a + self.b # 计算下一个值
  8. if self.a > 100000: # 退出循环的条件
  9. raise StopIteration();
  10. return self.a # 返回下一个值
  1. >>> for n in Fib():
  2. ... print(n)
  3. ...
  4. 1
  5. 1
  6. 2
  7. 3
  8. 5
  9. ...
  10. 46368
  11. 75025

_getitem_
按照下标取出元素,实现切片的功能,就要实现_getitem_方法。

  1. class Fib(object):
  2. def __getitem__(self, n):
  3. a, b = 1, 1
  4. for x in range(n):
  5. a, b = b, a + b
  6. return a
  7. if isinstance(n, slice): # n是切片
  8. start = n.start
  9. stop = n.stop
  10. if start is None:
  11. start = 0
  12. a, b = 1, 1
  13. L = []
  14. for x in range(stop):
  15. if x >= start:
  16. L.append(a)
  17. a, b = b, a + b
  18. return L

_getattr_
正常情况下,当我们调用类的方法或属性的时候,如果不存在就会报错,为了避免这个错误,Python中的_getattr_()方法能动态返回一个属性。

  1. class Student(object):
  2. def __init__(self):
  3. self.name = 'Michael'
  4. def __getattr__(self, attr):
  5. if attr=='score':
  6. return 99

_call_
一个对象的实例可以有自己的属性和方法,当我们调用实例方法时,我们用instance.method()来调用,但是任何类只需要定义一个_call_()方法,就可以直接对实例进行调用。

  1. class Student(object):
  2. def __init__(self, name):
  3. self.name = name
  4. def __call__(self):
  5. print('My name is %s.' % self.name)
  6. ---
  7. >>> s = Student('Michael')
  8. >>> s() # self参数不要传入
  9. My name is Michael.

通过callable()函数,我们就可以判定一个对象是否是”可调用”对象。

2.5 使用枚举类

当我们需要定义常量的时候,一个办法就是用大写常量名通过整数定义,更好的办法是为这样的枚举类型定义一个class类型,然后,每个常量是class的唯一的实例。Python提供了Enum类来实现这个功能。

  1. from enum import Enum
  2. Month = Enum('Month', ('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'))
  3. # 这样我们就获得了Month类型的枚举类,可以直接使用Month.Jan来引用一个变量,或者枚举它的所有成员。
  4. for name, member in Month.__members__.items():
  5. print(name, '=>', member, ',', member.value)

如果需要更精确的控制枚举类型,可以从Enum派生出自定义类。

  1. from enum import Enum, unique
  2. @unique # @unique装饰器可以帮助我们检查保证没有重复值
  3. class Weekday(Enum):
  4. Sun = 0 # Sun的value被设定为0
  5. Mon = 1
  6. Tue = 2
  7. Wed = 3
  8. Thu = 4
  9. Fri = 5
  10. Sat = 6
  11. # 访问枚举类型的若干种方法
  12. >>> day1 = Weekday.Mon
  13. >>> print(day1)
  14. Weekday.Mon
  15. >>> print(Weekday.Tue)
  16. Weekday.Tue
  17. >>> print(Weekday['Tue'])
  18. Weekday.Tue
  19. >>> print(Weekday.Tue.value)
  20. 2
  21. >>> print(day1 == Weekday.Mon)
  22. True
  23. >>> print(day1 == Weekday.Tue)
  24. False
  25. >>> print(Weekday(1))
  26. Weekday.Mon
  27. >>> print(day1 == Weekday(1))
  28. True
  29. >>> Weekday(7)
  30. Traceback (most recent call last):
  31. ...
  32. ValueError: 7 is not a valid Weekday
  33. >>> for name, member in Weekday.__members__.items():
  34. ... print(name, '=>', member)
  35. ...
  36. Sun => Weekday.Sun
  37. Mon => Weekday.Mon
  38. Tue => Weekday.Tue
  39. Wed => Weekday.Wed
  40. Thu => Weekday.Thu
  41. Fri => Weekday.Fri
  42. Sat => Weekday.Sat
  43. # 既可以根据成员名称引用枚举常量,又可以根据value的值获得枚举常量。

2.6 使用元类

class的定义是运行时动态创建的,而创建class的方法就是使用type()函数。
type()函数可以查看一个类的类型或变量的类型,既可以返回一个对象的类型,又可以创建出新的类型。

  1. >>> def fn(self, name='world'): # 先定义函数
  2. ... print('Hello, %s.' % name)
  3. ...
  4. >>> Hello = type('Hello', (object,), dict(hello=fn)) # 创建Hello class
  5. >>> h = Hello()
  6. >>> h.hello()
  7. Hello, world.
  8. >>> print(type(Hello))
  9. <class 'type'>
  10. >>> print(type(h))
  11. <class '__main__.Hello'>

除了使用type()动态创建类以外,要控制类的创建行为,还可以使用metaclass。
metaclass,直译为元类,简单的解释就是,当我们定义了类之后,就可以根据类创建出实例,所以,先定义类,然后创建实例。
先定义metaclass,就可以创建类,最后创建实例。


感谢廖雪峰的官方网站提供的教程。Python学习笔记系列都基于廖老师的教程。





原文地址:https://www.cnblogs.com/aniudcs/p/5971163.html