• 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学习笔记系列都基于廖老师的教程。





  • 相关阅读:
    离散数学--第十章 群,环,域
    离散数学--十一章 格与布尔代数
    matplotlib 基础|笔记
    CF Round #632 div2
    Codeforces Round#630 div2
    PVZ 2--攻略合集?
    【POJ
    【POJ
    【Aizu
    【Aizu
  • 原文地址:https://www.cnblogs.com/aniudcs/p/5971163.html
Copyright © 2020-2023  润新知