[关闭]
@chengweihuang 2019-01-04T13:00:38.000000Z 字数 8563 阅读 921

面向对象篇

复习篇


学员疑问: 重写,继承,派生,封装,多态,super函数,覆盖

前言:

把数据和函数结合起来,并将其置入一种叫做对象的东西。这就叫做 面向对象 编程范式.在大多数情况下,我们可以使用面向过程的编程,但是当写大型程序或者遇到了一些更加适合这种方法的时候,你可以使用基于对象的编程技术

以一个例子,说明面向对象和面向过程的区别:

管理班上的人员信息

  1. wangkuan = {‘name’:’王宽’,’age’:18}
  2. wangyue = {‘name’:’王越’,’age’:9}
  3. def print_info(stu):
  4. print(‘%s ,%s ,%s’%(stu[‘name’],,,,))
  5. #将人员信息的过程直接反应到代码上 ,这就是一个面向过程的思想.

而面向对象,首先不会思考程序怎么按照流程区实现,而是 Student 这种数据类型应该被视为一个对象

  1. Class Student(object):
  2. def __init__(self,name,age):
  3. self.name = name
  4. self.age = age
  5. def print_info(self):
  6. print(self.name,self.age)

来自 Kenneth Reitz 大神的建议:避免不必要的面向对象编程
https://pythoncaff.com/articles/484


正文开始
类和对象是面向对象编程的两个主要概念。一个类创造了一种新的 类型 ,而对象就是类的实例。一种观点是:你可以把int 类型的变量翻译为存储着整型的变量,这个变量是 int 类的一个实例。 a = 1 print(type(a))

先提一下 self

类的方法与普通的函数相比只有一个区别 - 他们在入口参数表的开头必须有一个额外的形式参数,但是当你调用这个方法的时候,你不会为这个参数赋予任何一个值,Python 会提供给它。这个特别的参数指向对象 本身 ,约定它的名字叫做self
你一定好奇 Python 是如何给 self 赋值的,以及为什么你不必给它赋值。一个例子将会把这些问题说明清楚。假设你有一个类叫做 MyClass 以及这个类的一个对象叫做 myobject 。当你需要这样调用这个对象的方法的时候:myobject.method(arg1, arg2) ,这个语句会被 Python 自动的转换成 MyClass.method(myobject, arg1, arg2) 这样的形式 —— 这就是 self 特殊的地方。


  1. class Person:
  2. pass
  3. P = Person()
  4. Print(p)
  5. 输出 <__main__.Person object at 0x000000AA01AFB0B8>

我们使用 class 语句和类名创建了一个类。在这之后跟着一个语句块形成了类的主体。在这个例子中,我们使用pass 语句声明了一个空的语句块。
之后,我们使用类的名字和一对括号创建了一个类的对象(实例),我们通过简单地打印变量 p 的方法确认这个变量类型。结果证明这是 __main__ 模块中 Person类的一个对象。
注意这个对象在内存中的地址也被显示出来。这个地址可能在你的电脑上有一个不同的值,这是由于 Python 只要找到空闲的内存空间就会在此处存放这个对象。


方法

  1. class Person:
  2. def say_hi(self):
  3. print('Hello, how are you?')
  4. Person().say_hi()

注意到在 say_hi 方法中没有取得任何一个参数,却在方法定义的时候仍然有一个 self 参数。


实例

定义好了类后,可以生产很多的实例,创建实例是通过类名+()实现的


类变量和实例变量

>
实例属性 atrribute(也叫实例变量)
每个实例可以有自己的变量用来保存对象自己的数据, 称为实例变量(也叫属性)
记录每个对象自身的数据
只能自己访问

-

>
类变量
类变量是类的属性,此属性属于类,不属于此类的实例
通常用来存储该类创建对象的共有属性 (可以用来记录实例对象的个数)
类的实例,和类都可以访问

  1. class Human:
  2. total_count = 0 # 类变量,用来记录Human对象的个数
  3. def __init__(self, n):
  4. self.name = n
  5. self.__class__.total_count += 1
  6. print(n, '对象被创建')
  7. def __del__(self):
  8. print(self.name, '对象被销毁')
  9. self.__class__.total_count -= 1
  10. L = []
  11. L.append(Human('张飞'))
  12. L.append(Human('关羽'))
  13. print("当前人物个数是:", Human.total_count)
  14. del L[1]
  15. print("当前人物个数是:", Human.total_count)

类变量在内存中只保存一份
实例变量在每个对象中都要保存一份

魔法方法

__init__

__init__ 方法将在类的对象被初始化(也就是创建)的时候自动的调用。这个方法将按照你的想法 初始化 对象(通过给对象传递初始值)。请注意这个名字的开头和结束都是双下划线。

  1. class Person:
  2. def __init__(self, name):
  3. self.name = name
  4. def say_hi(self):
  5. print('Hello, my name is', self.name)
  6. p = Person('Swaroop')
  7. p.say_hi()

这里,我们定义了 init 方法。这个方法除了通常的 self 变量之外,还有一个参数 name 。 这里我们创建了一个新的也叫做 name 的域。注意这里有两个不同的变量却都被叫做 'name' 。这是没有问题的,因为带点的标记self.name 表示有一个叫做 "name" 的域是这个类的一部分,而另外一个 name 是一个局部变量。这里我们显式地指出使用哪个变量,因此没有任何冲突。
当新建一个新的 Person 类的实例 p 的时候,我们通过调用类名的方式来创建这个新的实例,在紧跟着的括号中填入初始化参数: p = Person('Swaroop') 。
我们没有显式的调用 init 这个方法,这是这个方法特殊之处。
正如 say_hi 方法所示的,现在我们在我们的方法之中可以使用 self.name 这个域了。

__new__

在 Python 中存在于类里面的构造方法 __init__() 负责将类的实例化,而在 __init__() 启动之前,__new__返回实例,然后调用__init__(),__new__() 决定是否要使用该 __init__() 方法,因为__new__() 可以调用其他类的构造方法或者直接返回别的对象来作为本类的实例。使用方法 之前每日一讲说过的 单例模式

  1. class Singleton(object):
  2. _instance = None
  3. def __new__(cls, *args, **kw):
  4. if not cls._instance:
  5. cls._instance = super(Singleton, cls).__new__(cls)
  6. return cls._instance

__str__和__repr__

str是针对于让人更好理解的字符串格式化,而repr是让机器更好理解的字符串格式化。

  1. class Test(object):
  2. def __init__(self, value='hello, world!'):
  3. self.data = value
  4. #交互模式下
  5. >>> t = Test()
  6. >>> t
  7. <__main__.Test object at 0x000000166B9E4470>
  8. >>> print (t)
  9. <__main__.Test object at 0x000000166B9E4470>

打印效果不是太友好

  1. class TestRepr(Test):
  2. def __repr__(self):
  3. return 'TestRepr(%s)' % self.data
  4. >>> tr = TestRepr()
  5. >>> tr
  6. TestRepr(hello, world!)
  7. >>> print tr
  8. TestRepr(hello, world!)

重构 repr 之后, 不管直接输出对象还是通过print打印的信息都按我们repr方法中定义的格式进行显示了

  1. calss TestStr(Test):
  2. def __str__(self):
  3. return '[Value: %s]' % self.data
  4. >>> ts = TestStr()
  5. >>> ts
  6. <__main__.TestStr at 0x7fa91c314e50>
  7. >>> print ts
  8. [Value: hello, world!]

你会发现,直接输出对象ts时并没有按我们str方法中定义的格式进行输出,而用print输出的信息却改变了


__del__

析构方法,当实例化对象被彻底销毁时被调用(实例化对象的所有指针都被销毁时被调用)


__call__
允许一个类的实例像函数一样被调用

  1. class Test(object):
  2. def __init__(self, word):
  3. self.word = word
  4. def __call__(self):
  5. print('this is call')
  6. def print_test(self):
  7. print('this is normal')
  8. t = Test('hello kugou')
  9. t.print_test()
  10. t()
  11. #this is normal
  12. #this is call

__iter__

如果一个类想被用于for ... in循环,类似list或tuple那样,就必须实现一个__iter__()方法,该方法返回一个迭代对象,然后,Python的for循环就会不断调用该迭代对象的next()方法拿到循环的下一个值,直到遇到StopIteration错误时退出循环。
__iter__()无论何时创建新的迭代器,都会调用该方法
__next__()每当你从迭代器中检索一下个值的时候,都会调用该方法

  1. class Fib(object):
  2. def __init__(self):
  3. self.a, self.b = 0, 1
  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 > 100:
  9. raise StopIteration()
  10. return self.a
  11. for i in Fib():
  12. print(i)

__getattr__

正常情况下,当我们调用类的方法或属性时,如果不存在,就会报错

  1. class Test(object):
  2. def __init__(self, word):
  3. self.word = word
  4. def __call__(self):
  5. print('this is call')
  6. def print_test(self):
  7. print('this is nor')
  8. def __getattr__(self,attr):
  9. if attr == 'name':
  10. return 'Bob'
  11. if attr == 'get_name':
  12. return lambda : 'Bob'
  13. t = Test('hello kugou')
  14. print(t.name)
  15. print(t.get_name())
  16. # Bob
  17. # Bob

关于下划线问题

  • 把属性的名称前加上两个下划线__ ,让内部属性不被外部访问,实例的变量名如果以__开头,就变成了一个私有变量(private),只有内部可以访问,外部不能访问.
  • 在Python中,变量名类似__xxx__的,也就是以双下划线开头,并且以双下划线结尾的,是特殊变量,特殊变量是可以直接访问的,不是private变量,所以,不能用__name__、__score__这样的变量名。
  • _ 一个下划线 , 规定为私有变量,虽然可以被外部访问

特殊变量

__slots__ 限制实例的属性

  1. class Student(object):
  2. __slots__ = ('name', 'age') # 用tuple定义允许绑定的属性名称
  • __name__ 一个模块被另一个程序第一次引入时,其主程序将运行。如果我们想在模块被引入时,模块中的某一程序块不执行,我们可以用__name__属性来使该程序块仅在该模块自身运行时执行。
  1. if __name__ == '__main__':
  2. main()
  • __all__ , from module import * 此时被导入模块若定义了__all__属性,则只有__all__内指定的属性、方法、类可被导入


三种方法

>
@classmethod 类方法
类方法是用于描述类的行为的方法,类方法属于类,不属于类的实例对象
第一个形参 通常是 cls
类和该类的实例都可以调用类方法
类方法不能访问此类创建的对象的实例属性

-

>
静态方法 @staticmethod
静态方法只能凭借该类或类创建的实例调用
静态方法是定义在类的内部的函数,此函数的作用域是类的内部
静态方法不能访问类变量和实例变量

除了类方法和静态方法,别的就是实例方法
实例方法只能实例访问


Python内置类属性

  • _dict__ : 类的属性(包含一个字典,由类的数据属性组成)
  • __doc__ :类的文档字符串
  • __name__: 类名
  • __module__: 类定义所在的模块(类的全名是'main.className',如果类位于一个导入模块mymod中,那么className.__module__ 等于 mymod)

获取对象信息

type() 如果一个变量指向函数或者类,可以用type()判断

  1. class Test(object):
  2. @classmethod
  3. def app(self):
  4. print('aa')
  5. a = Test()
  6. print(type(a))
  7. # <class '__main__.Test'>

isinstance() 对于class的所属关系

  1. class A(object):
  2. pass
  3. class B(A):
  4. pass
  5. b = B()
  6. print(isinstance(b,A))
  7. print(isinstance([1, 2, 3], (list, tuple)))

继承和多态

面向对象编程的主要优势之一就是代码的重用,一种获得代码重用的主要方式就是继承体系,通过继承,子类就可以扩展父类的功能。。继承可以被想象成为类之间的一种类型和子类型的关系的实现。 通过继承创建的新类称为子类或派生类
多态 字面意思: '多种状态'
多态是指在有继承/派生关系的类中,调用基类对象的方法,实际能调用子类的覆盖方法的现象叫多态

  1. class Car:
  2. '''汽车类'''
  3. def run(self, speed):
  4. print("汽车以", speed, '公里/小时的速度行驶')
  5. class Plane:
  6. '''飞机类'''
  7. def fly(self, height):
  8. print("飞机以海拔", height, '米的高度飞行')
  9. class PlaneCar(Car, Plane):
  10. '''PlaneCar类同时继承自汽车类和飞机类'''
  11. p = PlaneCar() # 创建一个飞行汽车对象
  12. p.fly(10000)
  13. p.run(300)

方法重写 --- 多态 --- 鸭子类型

当子类和父类都存在相同的 draw()方法时,子类的 draw() 覆盖了父类的 draw(),在代码运行时,会调用子类的 draw()

多态的好处就是,当我们需要传入更多的子类, ,我们只需要继承 Shape 类型就可以了。这就是多态的意思。调用方只管调用,不管细节,而当我们新增一种Shape的子类时,只要确保新方法编写正确,而不用管原来的代码。这就是著名的“开闭”原则:

  1. class Shape:
  2. def draw(self):
  3. print("Shape的draw()被调用")
  4. class Point(Shape):
  5. def draw(self):
  6. print('正在画一个点!')
  7. class Circle(Point):
  8. def draw(self):
  9. print('正在画一个圆!!!')
  10. def my_draw(s):
  11. s.draw() # <<<--- 此处显示出多态中的"动态"
  12. s1 = Circle() # 创建一个圆对象
  13. s2 = Point() # 创建一个点对象
  14. my_draw(s1)
  15. my_draw(s2)
  1. class Duck():
  2. def walk(self):
  3. print('I walk like a duck')
  4. def swim(self):
  5. print('i swim like a duck')
  6. class Person():
  7. def walk(self):
  8. print('this one walk like a duck')
  9. def swim(self):
  10. print('this man swim like a duck')

可以很明显的看出,Person类拥有跟Duck类一样的方法,当有一个函数调用Duck类,并利用到了两个方法walk()和swim()。我们传入Person类也一样可以运行,函数并不会检查对象的类型是不是Duck,只要他拥有walk()和swim()方法,就可以正确的被调用
再举例,如果一个对象实现了__getitem__方法,那python的解释器就会把它当做一个collection,就可以在这个对象上使用切片,获取子项等方法;如果一个对象实现了iter和next方法,python就会认为它是一个iterator,就可以在这个对象上通过循环来获取各个子项。

函数重写 overwrite

在自定义的类内添加相应的方法,让自定义的类生成的对象(实例)像内建对象一样进行函数操作。 内建对象 字符串,列表,字典,set,文件对象
__str__() 和 __repr__()

  1. a = 1
  2. print(str(a)) # 1
  3. print(repr(a)) # 1 类型都是字符串
  1. class Test(object):
  2. def __init__(self,value):
  3. self.value = value
  4. def __str__(self):
  5. return '自定义的数据 %d'%self.value
  6. def __repr__(self):
  7. return 'repr 自定义的数据 %d'%self.value
  8. b = Test(1)
  9. print(str(b)) # 自定义的数据 1

内建函数重写

  1. class MyList:
  2. '''这是一个自定义的列表类型,
  3. 此类型的对象用data属性绑定的列表来存储数据'''
  4. def __init__(self, iterable=()):
  5. self.data = [x for x in iterable]
  6. def __repr__(self):
  7. return 'MyList(%s)' % self.data
  8. def __len__(self):
  9. return len(self.data)
  10. def __abs__(self):
  11. L = [abs(x) for x in self.data]
  12. return MyList(L)
  13. myl = MyList([1, -2, 3, -4])
  14. print(myl) # MyList([1, -2, 3, -4])
  15. print(len(myl)) # 4
  16. print(abs(myl)) # MyList([1, 2, 3, 4])

数值转换函数的重写
运算符重载

多继承

多继承是指一个子类继承自两个或两个以上的基类

  1. class A:
  2. def go(self):
  3. print("A")
  4. class B(A):
  5. def go(self):
  6. print("B")
  7. super().go() # C
  8. class C(A):
  9. def go(self):
  10. print("C")
  11. class D(B, C):
  12. def go(self):
  13. print("D")
  14. super().go() # 调用谁?
  15. d = D()
  16. d.go()
  17. #print(D.__mro__)

封装

封装是指隐藏类的实现细节,让使用者不关心这些细节
封装的目的是让使用者通过尽可能少的方法(或属性)操作对象

  1. class Student:
  2. def __init__(self, s):
  3. self.__score = s
  4. def setScore(self, s):
  5. '''此方法用设置值加以限制以保证数据的准确性
  6. setter是用来数据的
  7. '''
  8. if 0 <= s <= 100:
  9. self.__score = s
  10. def getScore(self):
  11. '''getter 只用来获取数据'''
  12. return self.__score
  13. s = Student(50)
  14. s.setScore(100

@property

  1. class Student:
  2. def __init__(self, s):
  3. self.__score = s
  4. @property
  5. def score(self):
  6. '''getter 只用来获取数据'''
  7. print("getter被调用")
  8. return self.__score
  9. @score.setter
  10. def score(self, s):
  11. '''此方法用设置值加以限制以保证数据的准确性
  12. setter是用来数据的
  13. '''
  14. print("setter被调用")
  15. if 0 <= s <= 100:
  16. self.__score = s
  17. s = Student(50)
  18. # s.setScore(100)
  19. score = s.score # 访问特性属性score 实质是调用原s.score()
  20. print('成绩是:', score)
  21. s.score = 100
  22. print(s.score) # 100
  23. s.score = 10000
  24. print(s.score) # 100

关于python函数重载

http://www.zhihu.com/question/20053359

本文链接

https://www.zybuluo.com/chengweihuang/note/1321895

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注