- 面向对象编程:https://www.liaoxuefeng.com/wiki/897692888725344/923030496738368
- 面向对象高级编程:https://www.liaoxuefeng.com/wiki/897692888725344/923030538012640
1、类、对象
类 和 对象 是面向对象编程的两个主要方面。
- 类 是创建一个 新类型,
- 而对象是这个类 的 实例 。
类(Class)::用来描述具有相同的属性和方法的对象的集合。它定义了该集合中每个对象所共有的属性和方法。对象是类的实例。
对象 :通过类定义的数据结构实例。对象包括两个数据成员(类变量和实例变量)和方法。
实例化 :创建一个类的实例,类的具体对象。
方法:在 Python 中,方法(method)专指 "类中定义的函数",即在类中的函数才叫方法。
方法 和 函数 在 python中是不同的概念,
- 方法是类中定义的函数,即在类中的函数才叫方法。
- 函数是就是一般的函数
数据成员: 类变量或者实例变量用于处理类及其实例对象的相关的数据。
方法重载: 如果从父类继承的方法不能满足子类的需求,可以对其进行改写,这个过程叫方法的覆盖(override),重载。
实例变量: 定义在方法中的变量,只作用于当前实例的类。
类变量 : 类变量在整个实例化的对象中是公用的。类变量定义在类中且在函数体之外。类变量通常不作为实例变量使用。
继承 : 即一个派生类(derived class)继承基类(base class)的字段和方法。继承也允许把一个派生类的对象作为一个基类对象对待。
给 C/C++/Java/C#程序员的注释
注意:Python 中即便是整数也被作为对象 ( 属于int类 )。这和 C++、Java(1.5版之前)把整数纯粹作为类型是不同的。通过 help(int) 了解更多这个类的详情。
C#和Java 1.5程序员会熟悉这个概念,因为它类似与封装与解封装 的概念。
对象可以使用普通的 属于 对象的变量存储数据。属于一个对象或类的变量被称为域。
对象也可以使用 属于类的函数来具有功能。这样的函数被称为类的方法。
这些术语帮助我们把它们与孤立的函数和变量区分开来。域和方法可以合称为类的属性。
域有两种类型——属于每个实例/类的对象或属于类本身。它们分别被称为实例变量和类变量。
类使用class关键字创建。类的域和方法被列在一个缩进块中。
在 Python类中定义的方法通常有三种:实例方法、类方法、静态方法。这三者之间的区别是:
- 实例方法:一般都以 self 作为第一个参数,必须和具体的对象实例进行绑定才能访问。实例方法的第一个参数是实例对象 self,那么通过 self 引用的可能是类属性、也有可能是实例属性(这个需要具体分析)。不过在存在相同名称的类属性和实例属性的情况下,实例属性优先级更高。可以直接在类外通过对象名访问,如果想定义成私有的,则需在前面加2个下划线 ' __'
- 类方法 :以 cls 作为第一个参数,cls表示类本身,定义时使用@classmethod,那么通过 cls 引用的必定是类对象的属性和方法;
- 静态方法:不需要默认的任何参数,跟一般的普通函数类似。定义的时候使用@staticmethod,静态方法中不需要额外定义参数,因此在静态方法中引用类属性的话,必须通过类对象来引用。
构造方法 __init__() 和 析构方法 __del__()
- __init__(self) 方法是一种特殊的方法,被称为类的构造函数或初始化方法,当创建了这个类的实例时就会调用该方法。构造方法支持重载,如果没有构造方法,系统就自动执行默认的构造方法。
-
__del__(self) 方法也是一种特殊的方法,叫做 析构方法。在释放对象时调用,支持重载,可以在里面进行一些释放资源的操作,不需要显示调用。
创建 实例对象( 类的实例 )
要创建一个 类的实例,你可以使用类的名称,并通过 __init__ 方法接受参数。
class Employee(object):def __init__(self, name, age):self.name = nameself.age = agepassdef __del__(self):passdef display_employee(self):print(f'name:{self.name}, age:{self.age}')emp1 = Employee("one", 2000) # "创建 Employee 类的第一个对象"
emp2 = Employee("two", 5000) # "创建 Employee 类的第二个对象"
访问 属性
# 使用点(.)来访问对象的属性。使用如下类的名称访问类变量:
emp1.display_employee() # 你可以添加,删除,修改类的属性,如下所示:
emp1.age = 7 # 添加一个 'age' 属性
emp1.age = 8 # 修改 'age' 属性
del emp1.age # 删除 'age' 属性
示例:
class TestClass(object):name = 'TestClass'passobj = TestClass()t_name = getattr(obj, 'name') # 访问对象的属性。
print(t_name)is_have = hasattr(obj, 'name') # 检查是否存在一个属性。
print(is_have)setattr(obj, 'age', 100) # 设置一个属性。如果属性不存在,会创建一个新属性。
# delattr(obj, 'name') # 删除属性。
Python内置的 类的属性
- __dict__ : 类的属性(包含一个字典,由类的数据属性组成)
- __doc__ : 类的文档字符串
- __name__ : 类名
- __module__: 类定义所在的模块(类的全名是'__main__.className',如果类位于一个导入模块mymod中,那么className.__module__ 等于 mymod)
- __bases__ : 类的所有父类构成元素(包含了以个由所有父类组成的元组)
Python对象 的 销毁(垃圾回收)
在 Python 内部记录着所有使用中的对象各有多少引用。一个内部跟踪变量,称为一个引用计数器。当对象被创建时, 就创建了一个引用计数, 当这个对象不再需要时, 这个对象的引用计数变为0 时,它被垃圾回收。但是回收不是"立即"的, 由解释器在适当的时机,将垃圾对象占用的内存空间回收。
self 参数
类的方法与普通的函数只有一个特别的区别:即 类的方法 第一个参数必须是 self。但是在调用这个方法的时候你不为这个参数赋值,Python会提供这个值。这个特别的变量指对象本身,按照惯例它的名称是self。虽然你可以给这个参数任何名称,但是 强烈建议 你使用self这个名称——其他名称都是不赞成你使用的。使用一个标准的名称有很多优点——你的程序读者可以迅速识别它,如果使用self的话,还有些IDE(集成开发环境)也可以帮助你。
给C/C++/Java/C#程序员的注释
Python 中的 self 等价于 C++ 中的 this 指针。
Python 如何给 self 赋值以及为何你不需要给它赋值?举一个例子会使此变得清晰:假如你有一个类称为 MyClass 和这个类的一个实例MyObject。当你调用这个对象的方法 MyObject.method(arg1, arg2) 的时候,这会由 Python 自动转为 MyClass.method(MyObject, arg1, arg2),这就是self的原理了。这也意味着如果你有一个不需要参数的方法,你还是得给这个方法定义一个self 参数。
创建一个 类
#!/usr/bin/python
# Filename: simplestclass.pyclass Person:pass # An empty blockp = Person()
print(p)# 结果
# <__main__.Person object at 0x0000023C03EA97F0>
使用 class 后跟类名,创建一个新的类。这后面跟着一个缩进的语句块形成类体。这个例子中,使用 pass 语句表示空语句。然后使用类名后跟一对圆括号来创建一个对象/实例。为了验证,我们简单地打印了这个变量的类型。它告诉我们我们已经在__main__模块中有了一个Person类的实例。可以注意到存储对象的计算机内存地址也打印了出来。这个地址在你的计算机上会是另外一个值,因为Python可以在任何空位存储对象。
使用 对象的方法
#!/usr/bin/python
# Filename: method.pyclass Person:def sayHi(self):print('Hello, how are you?')p = Person()
p.sayHi()
# This short example can also be written as Person().sayHi()# 输出
# $ python method.py
# Hello, how are you?
这里可以看到 self 的用法。注意 sayHi 方法没有任何参数,但仍然在函数定义时有 self。
__init__ 方法
在 Python 的类中,有很多方法的名字有特殊的重要意义。现在看下 __init__方法的意义。__init__ 方法在类的一个对象被建立时,马上运行。这个方法可以用来对你的对象做一些你希望的 初始化 。注意,这个名称的开始和结尾都是双下划线 __init__
#!/usr/bin/python
# Filename: class_init.pyclass Person:def __init__(self, name):self.name = namedef sayHi(self):print('Hello, my name is', self.name)p = Person('Swaroop')
p.sayHi()Person('Swaroop').sayHi()# 输出
# $ python class_init.py
# Hello, my name is Swaroop
# Hello, my name is Swaroop
这个__init__方法有两个参数,分别是 self、name,方法里面是 self.name = name。注意它们是两个不同的变量,尽管它们有相同的名字。点号使我们能够区分它们。最重要的是,我们没有专门调用__init__方法,只是在创建一个类的新实例的时候,把参数包括在圆括号内跟在类名后面,从而传递给__init__方法。
给C/C++/Java/C#程序员的注释:__init__方法类似于C++、C#和Java中的 constructor 。
2、类 与 对象的方法
已经讨论了类与对象的功能部分,现在来看一下它的数据部分。
事实上,它们只是与类和对象的名称空间 绑定 的普通变量,即这些名称只在这些类与对象的前提下有效。
两种类型的 域 ——类的变量(属于类 的作用域) 和 对象的变量(属于实例的作用域),它们根据是类还是对象 拥有 这个变量而区分。
类的变量 由一个类的所有对象(实例)共享使用。只有一个类变量的拷贝,所以当某个对象对类的变量做了改动的时候,这个改动会反映到所有其他的实例上。
对象的变量 由类的每个对象/实例拥有。因此每个对象有自己对这个域的一份拷贝,即它们不是共享的,在同一个类的不同实例中,虽然对象的变量有相同的名称,但是是互不相关的。通过一个例子会使这个易于理解。
使用 类与对象 的 变量
#!/usr/bin/python
# Filename: objvar.pyclass Person:"""Represents a person."""population = 0def __init__(self, name):"""Initializes the person's data."""self.name = nameprint('(Initializing %s)' % self.name)# When this person is created, he/she# adds to the populationPerson.population += 1def __del__(self):"""I am dying."""print('%s says bye.' % self.name)Person.population -= 1if Person.population == 0:print('I am the last one.')else:print('There are still %d people left.'% Person.population)def sayHi(self):"""Greeting by the person.Really, that's all it does."""print('Hi, my name is %s.' % self.name)def howMany(self):"""Prints the current population."""if Person.population == 1:print('I am the only person here.')else:print('We have %d persons here.'% Person.population)swaroop = Person('Swaroop')
swaroop.sayHi()
swaroop.howMany()kalam = Person('Abdul Kalam')
kalam.sayHi()
kalam.howMany()swaroop.sayHi()
swaroop.howMany()
结果:
(Initializing Swaroop)
Hi, my name is Swaroop.
I am the only person here.
(Initializing Abdul Kalam)
Hi, my name is Abdul Kalam.
We have 2 persons here.
Hi, my name is Swaroop.
We have 2 persons here.
Swaroop says bye.
There are still 1 people left.
Abdul Kalam says bye.
I am the last one.
这里,
- population 属于 Person类,因此是一个类的变量。
- name变量 属于 对象(它使用self赋值),因此是对象的变量。
在 __init__ 方法中,我们让 population 增加1,这是因为我们增加了一个人。同样可以发现,self.name 的值根据每个对象指定,这表明了它作为对象的变量的本质。
记住,你只能使用 self变量 来 引用 同一个对象的变量和方法。
在这个程序中,还可以看到 docstring 对于类和方法同样有用。我们可以在运行时使用 Person.__doc__ 和 Person.sayHi.__doc__来分别访问类与方法的文档字符串,就如同 __init__ 方法一样,还有一个特殊的方法 __del__,它在对象消逝的时候被调用。对象消逝即对象不再被使用,它所占用的内存将返回给系统作它用。在这个方法里面,我们只是简单地把 Person.population 减 1。
当对象不再被使用时,__del__方法运行,但很难保证这个方法究竟在 什么时候 运行。如果你想要指明它的运行,你就得使用 del 语句。
给C/C++/Java/C#程序员的注释
- Python 中所有的 类成员(包括数据成员)都是 公共的 ,所有的方法都是 有效的 。
- 只有一个例外:如果你使用的数据成员名称以 "双下划线前缀", 比如 __privatevar,Python 的名称管理体系会有效地把它作为私有变量。
- 这样就有一个惯例,如果某个变量 只想在类或对象中使用,就应该以 "单下划线前缀"。而其他的名称都将作为公共的,可以被其他类/对象使用。记住这只是一个惯例,并不是Python所要求的(与双下划线前缀不同)。
- 同样,注意 __del__ 方法与 destructor 的概念类似。
3、继承
面向对象的编程带来的主要好处之一是代码的重用,实现这种重用的方法之一是通过 继承 机制。继承完全可以理解成:类之间的类型和子类型 关系。
假设写个程序来记录学校中教师和学生情况。他们有一些共同属性,比如姓名、年龄和地址。他们也有专有的属性,比如教师的薪水、课程和假期,学生的成绩和学费。
可以为教师和学生建立两个独立的类来处理它们,但这样做的话,如果要增加一个新的共有属性,就意味着要在这两个独立的类中都增加这个属性。这很快就会显得不实用。
一个比较好的方法是创建一个共同的类称为SchoolMember然后让教师和学生的类 继承 这个共同的类。即它们都是这个类型(类)的子类型,然后我们再为这些子类型添加专有的属性。使用这种方法有很多优点。如果我们增加/改变了SchoolMember中的任何功能,它会自动地反映到子类型之中。例如,你要为教师和学生都增加一个新的身份证域,那么你只需简单地把它加到SchoolMember类中。然而,在一个子类型之中做的改动不会影响到别的子类型。另外一个优点是你可以把教师和学生对象都作为SchoolMember对象来使用,这在某些场合特别有用,比如统计学校成员的人数。一个子类型在任何需要父类型的场合可以被替换成父类型,即对象可以被视作是父类的实例,这种现象被称为多态现象。另外,我们会发现在 重用 父类的代码的时候,我们无需在不同的类中重复它。而如果我们使用独立的类的话,我们就不得不这么做了。
在上述的场合中,SchoolMember类被称为 基本类 或 超类 。而Teacher和Student类被称为导出类 或子类 。
现在,我们将学习一个例子程序。
使用继承
# !/usr/bin/python
# Filename: inherit.pyclass SchoolMember:"""Represents any school member."""def __init__(self, name, age):self.name = nameself.age = ageprint('(Initialized SchoolMember: %s)' % self.name)def tell(self):"""Tell my details."""print('Name:"%s" Age:"%s"' % (self.name, self.age))class Teacher(SchoolMember):"""Represents a teacher."""def __init__(self, name, age, salary):SchoolMember.__init__(self, name, age)self.salary = salaryprint('(Initialized Teacher: %s)' % self.name)def tell(self):SchoolMember.tell(self)print('Salary: "%d"' % self.salary)class Student(SchoolMember):"""Represents a student."""def __init__(self, name, age, marks):SchoolMember.__init__(self, name, age)self.marks = marksprint('(Initialized Student: %s)' % self.name)def tell(self):SchoolMember.tell(self)print('Marks: "%d"' % self.marks)t = Teacher('Mrs. Shrividya', 40, 30000)
s = Student('Swaroop', 22, 75)print() # prints a blank linemembers = [t, s]
for member in members:member.tell() # works for both Teachers and Students# 输出
# (Initialized SchoolMember: Mrs. Shrividya)
# (Initialized Teacher: Mrs. Shrividya)
# (Initialized SchoolMember: Swaroop)
# (Initialized Student: Swaroop)
#
# Name:"Mrs. Shrividya" Age:"40"
# Salary: "30000"
# Name:"Swaroop" Age:"22"
# Marks: "75"
为了使用继承,我们把基本类的名称作为一个元组跟在定义类时的类名称之后。然后,我们注意到基本类的__init__方法专门使用self变量调用,这样我们就可以初始化对象的基本类部分。这一点十分重要——Python不会自动调用基本类的constructor,你得亲自专门调用它。我们还观察到我们在方法调用之前加上类名称前缀,然后把self变量及其他参数传递给它。注意,在我们使用SchoolMember类的tell方法的时候,我们把Teacher和Student的实例仅仅作为SchoolMember的实例。另外,在这个例子中,我们调用了子类型的tell方法,而不是SchoolMember类的tell方法。可以这样来理解,Python总是首先查找对应类型的方法,在这个例子中就是如此。如果它不能在导出类中找到对应的方法,它才开始到基本类中逐个查找。基本类是在类定义的时候,在元组之中指明的。一个术语的注释——如果在继承元组中列了一个以上的类,那么它就被称作 多重继承 。
继承语法 :
基类名写在括号里,基本类是在类定义的时候,在元组之中指明的。
class 派生类名( 基类1,[基类2, 基类3, ... 基类N] ):pass
示例:
class SubClassName (ParentClass1[, ParentClass2, ...]):'Optional class documentation string'class_suite
单继承
示例:
# coding=utf-8
# !/usr/bin/python
class Parent: # 定义父类parentAttr = 100def __init__(self):print("调用父类构造函数")def parentMethod(self):print('调用父类方法')def setAttr(self, attr):Parent.parentAttr = attrdef getAttr(self):print("父类属性 :", Parent.parentAttr)class Child(Parent): # 定义子类def __init__(self):print("调用子类构造方法")def childMethod(self):print('调用子类方法 child method')c = Child() # 实例化子类
c.childMethod() # 调用子类的方法
c.parentMethod() # 调用父类方法
c.setAttr(200) # 再次调用父类的方法
c.getAttr() # 再次调用父类的方法
执行结果如下:
调用子类构造方法
调用子类方法 child method
调用父类方法
父类属性 : 200
多继承
class A: # 定义类 A
.....
class B: # 定义类 B
.....
class C(A, B): # 继承类 A 和 B
.....
你可以使用issubclass()或者isinstance()方法来检测。
issubclass() - 布尔函数判断一个类是另一个类的子类或者子孙类,语法:issubclass(sub,sup)
isinstance(obj, Class) 布尔函数如果obj是Class类的实例对象或者是一个Class子类的实例对象则返回true。
在 Python 中继承中的一些特点:
1:在继承中基类的构造(__init__()方法)不会被自动调用,它需要在其派生类的构造中亲自专门调用。
2:在调用基类的方法时,需要加上基类的类名前缀,且需要带上self参数变量。区别于在类中调用普通函数时并不需要带上self参数
3:Python总是首先查找对应类型的方法,如果它不能在派生类中找到对应的方法,它才开始到基类中逐个查找。(先在本类中查找调用的方法,找不到才去基类中找)。 如果在继承元组中列了一个以上的类,那么它就被称作"多重继承" 。
super() 【继承顺序说明】
在 Python 类的方法(method)中,要调用父类的某个方法,在Python 2.2以前,通常的写法如下:
class A:def __init__(self):print "enter A"print "leave A"class B(A):def __init__(self):print "enter B"A.__init__(self)print "leave B">>> b = B()
enter B
enter A
leave A
leave B
使用非绑定的类方法(用类名来引用的方法),并在参数列表中,引入待绑定的对象(self),从而达到调用父类的目的。这样做的缺点是,当一个子类的父类发生变化时(如类B的父类由A变为C时),必须遍历整个类定义,把所有的通过非绑定的方法的类名全部替换过来,例如下面代码,
class B(C): # A --> Cdef __init__(self):print "enter B"C.__init__(self) # A --> Cprint "leave B"
如果代码简单,这样的改动或许还可以接受。但如果代码量庞大,这样的修改可能是灾难性的。因此,自Python 2.2开始,Python 添加了一个关键字 super,来解决这个问题。
class A(object): # A must be new-style classdef __init__(self):print "enter A"print "leave A"class B(C): # A --> Cdef __init__(self):print "enter B"super(B, self).__init__()print "leave B"
尝试执行上面同样的代码,结果一致,但修改的代码只有一处,把代码的维护量降到最低,是一个不错的用法。因此在开发过程中,super 关键字被大量使用,
在我们的印象中,对于 super(B, self).__init__() 是这样理解的:super(B, self) 首先找到B的父类(就是类A),然后把类B的对象 self 转换为类A的对象,然后 "被转换" 的类A对象调用自己的__init__函数。考虑到 super 中只有指明子类的机制,因此,在多继承的类定义中,通常我们保留使用类似代码段1的方法。
有一天某同事设计了一个相对复杂的类体系结构(我们先不要管这个类体系设计得是否合理,仅把这个例子作为一个题目来研究就好),代码如下:
class A(object):def __init__(self):print "enter A"print "leave A"class B(object):def __init__(self):print "enter B"print "leave B"class C(A):def __init__(self):print "enter C"super(C, self).__init__()print "leave C"class D(A):def __init__(self):print "enter D"super(D, self).__init__()print "leave D"class E(B, C):def __init__(self):print "enter E"B.__init__(self)C.__init__(self)print "leave E"class F(E, D):def __init__(self):print "enter F"E.__init__(self)D.__init__(self)print "leave F"f = F()结果:
enter F
enter E
enter B
leave B
enter C
enter D
enter A
leave A
leave D
leave C
leave E
enter D
enter A
leave A
leave D
leave F
明显地,类A和类D的初始化函数被重复调用了2次,这并不是我们所期望的结果!我们所期望的结果是最多只有类A的初始化函数被调用2次——其实这是多继承的类体系必须面对的问题。我们把代码段4的类体系画出来,如下图:
按我们对 super 的理解,从图中可以看出,在调用类C的初始化函数时,应该是调用类A的初始化函数,但事实上却调用了类D的初始化函数。好一个诡异的问题!
也就是说,mro中记录了一个类的所有基类的类类型序列。查看 mro 的记录,发觉包含7个元素,7个类名分别为:F E B C D A object
从而说明了为什么在 C.__init__ 中使用 super(C, self).__init__() 会调用类D的初始化函数了。
我们把代码段改写为:
class A(object):def __init__(self):print "enter A"super(A, self).__init__() # newprint "leave A"class B(object):def __init__(self):print "enter B"super(B, self).__init__() # newprint "leave B"class C(A):def __init__(self):print "enter C"super(C, self).__init__()print "leave C"class D(A):def __init__(self):print "enter D"super(D, self).__init__()print "leave D"
class E(B, C):def __init__(self):print "enter E"super(E, self).__init__() # changeprint "leave E"class F(E, D):def __init__(self):print "enter F"super(F, self).__init__() # changeprint "leave F"f = F()结果:
enter F
enter E
enter B
enter C
enter D
enter A
leave A
leave D
leave C
leave B
leave E
leave F
明显地,F 的初始化不仅完成了所有的父类的调用,而且保证了每一个父类的初始化函数只调用一次。再看类结构:
E-1,D-2 是 F 的父类,其中表示 E类在前,即 F(E,D)。所以初始化顺序可以从类结构图来看出 : F ---> E ---> B --> C --> D --> A
由于C,D 有同一个父类,因此会先初始化 D 再是 A。
延续的讨论
我们再重新看上面的类体系图,如果把每一个类看作图的一个节点,每一个从子类到父类的直接继承关系看作一条有向边,那么该体系图将变为一个有向图。不能发现mro的顺序正好是该有向图的一个拓扑排序序列。
从而,我们得到了另一个结果——Python是如何去处理多继承。支持多继承的传统的面向对象程序语言(如C++)是通过虚拟继承的方式去实现多继承中父类的构造函数被多次调用的问题,而Python则通过mro的方式去处理。
但这给我们一个难题:对于提供类体系的编写者来说,他不知道使用者会怎么使用他的类体系,也就是说,不正确的后续类,可能会导致原有类体系的错误,而且这样的错误非常隐蔽的,也难于发现。
小结
- 1. super 并不是一个函数,是一个类名,形如super(B, self)事实上调用了super类的初始化函数,产生了一个super对象;
- 2. super类的初始化函数并没有做什么特殊的操作,只是简单记录了类类型和具体实例;
- 3. super(B, self).func的调用并不是用于调用当前类的父类的func函数;
- 4. Python的多继承类是通过mro的方式来保证各个父类的函数被逐一调用,而且保证每个父类函数只调用一次(如果每个类都使用super);
- 5. 混用super类和非绑定的函数是一个危险行为,这可能导致应该调用的父类函数没有调用或者一个父类函数被调用多次。
示例:
class A(object):x = 1class B(A):passclass C(A):passclass D(B, C):passprint(A.x, B.x, C.x, D.x) # 1 1 1 1C.x = 2
print(A.x, B.x, C.x, D.x) # 1 1 2 2B.x = 3
print(A.x, B.x, C.x, D.x) # 1 3 2 3
方法重写
如果你的父类方法的功能不能满足你的需求,你可以在子类重写你父类的方法,示例:
#coding=utf-8
#!/usr/bin/python
class Parent: # 定义父类def myMethod(self):print '调用父类方法'class Child(Parent): # 定义子类def myMethod(self):print '调用子类方法'c = Child() # 子类实例
c.myMethod() # 子类调用重写方法
输出结果如下:
调用子类方法
基础重载方法
Python 运算符重载
示例:
#!/usr/bin/python
class Vector:def __init__(self, a, b):self.a = aself.b = bdef __str__(self):return 'Vector (%d, %d)' % (self.a, self.b)def __add__(self,other):return Vector(self.a + other.a, self.b + other.b)v1 = Vector(2,10)
v2 = Vector(5,-2)
print v1 + v2
以上代码执行结果如下所示:
Vector(7,8)
类 的 属性与方法
- 类 的 私有属性:__private_attrs:两个下划线开头,声明该属性为私有,不能在类地外部被使用或直接访问。在类内部的方法中使用时 self.__private_attrs。
- 类 方法 :以cls作为第一个参数,cls表示类本身,定义时使用@classmethod,那么通过cls引用的必定是类对象的属性和方法;
实例方法:一般都以self作为第一个参数,必须和具体的对象实例进行绑定才能访问,
静态方法:不需要默认的任何参数,跟一般的普通函数类似。定义的时候使用@staticmethod,静态方法中不需要额外定义参数,因此在静态方法中引用类属性的话,必须通过类对象来引用。
# coding=utf-8
# !/usr/bin/python
class JustCounter:__secretCount = 0 # 私有变量publicCount = 0 # 公开变量def count(self):self.__secretCount += 1self.publicCount += 1print(self.__secretCount)counter = JustCounter()
counter.count()
counter.count()
print(counter.publicCount)
print(counter.__secretCount) # 报错,实例不能访问私有变量
上面代码最后一行报错,因为 Python不允许 实例化的对象 访问私有数据,但你可以使用 object._className__attrName 访问属性。
将上面最后一行代码换成:print(counter._JustCounter__secretCount) 即可访问私有属性
python _、__和__xx__的区别
来源:http://www.cnblogs.com/coder2012/p/4423356.html
来源:https://segmentfault.com/a/1190000002611411
http://stackoverflow.com/questions/1301346/the-meaning-of-a-single-and-a-double-underscore-before-an-object-name-in-python
http://www.zhihu.com/question/19754941
"_"单下划线
Python 中不存在真正的私有方法。为了实现类似于 C++ 中私有方法,可以在类的方法或属性前加一个 "_" 单下划线,意味着该方法或属性不应该去调用,它并不属于API。
在使用 property 时,经常出现这个问题:
class BaseForm(StrAndUnicode):...def _get_errors(self):"Returns an ErrorDict for the data provided for the form"if self._errors is None:self.full_clean()return self._errorserrors = property(_get_errors)
上面的代码片段来自于 django 源码(django/forms/forms.py)。这里的 errors 是一个属性,属于 API 的一部分,但是 _get_errors 是私有的,是不应该访问的,但可以通过 errors 来访问该错误结果。
"__"双下划线
这个双下划线更会造成更多混乱,但它并不是用来标识一个方法或属性是私有的,真正作用是用来避免子类覆盖其内容。
让我们来看一个例子:
class A(object): def __method(self): print "I'm a method in A" def method(self): self.__method() a = A()
a.method()
输出是这样的:
$ python example.py
I'm a method in A
很好,出现了预计的结果。 现在我们给A添加一个子类,并重新实现一个__method:
class B(A): def __method(self): print "I'm a method in B" b = B()
b.method()
现在,结果是这样的:
$ python example.py
I'm a method in A
就像我们看到的一样,B.method()不能调用B.__method的方法。实际上,它是"__"两个下划线的功能的正常显示。
因此,在我们创建一个以"__"两个下划线开始的方法时,这意味着这个方法不能被重写,它只允许在该类的内部中使用。
在Python中如是做的?很简单,它只是把方法重命名了,如下:
a = A()
a._A__method() # never use this!! please!
$ python example.py
I'm a method in A
如果你试图调用a.__method,它还是无法运行的,就如上面所说,只可以在类的内部调用__method。
"__xx__"前后各双下划线 (特殊的属性和方法)
当你看到"__this__"的时,就知道不要调用它。为什么?因为它的意思是它是用于Python调用的,如下:
>>> name = "igor"
>>> name.__len__() 4
>>> len(name) 4
>>> number = 10
>>> number.__add__(20) 30
>>> number + 20 30
“__xx__”经常是操作符或本地函数调用的magic methods。在上面的例子中,提供了一种重写类的操作符的功能。
在特殊的情况下,它只是python调用的hook。例如,__init__()函数是当对象被创建初始化时调用的;__new__()是用来创建实例。
class CrazyNumber(object):def __init__(self, n): self.n = n def __add__(self, other): return self.n - other def __sub__(self, other): return self.n + other def __str__(self): return str(self.n) num = CrazyNumber(10)
print num # 10
print num + 5 # 5
print num - 20 # 30
另一个例子
class Room(object):def __init__(self): self.people = [] def add(self, person): self.people.append(person) def __len__(self): return len(self.people)room = Room()
room.add("Igor")
print len(room) # 1
结论
- 使用 _one_underline 来表示该方法或属性是私有的,不属于API;
- 当创建一个用于 python 调用或一些特殊情况时,使用 __two_underline__;
- 使用 __just_to_underlines,来避免子类的重写!
4、Python中面向对象编程
一. 如何定义一个类
在进行python面向对象编程之前,先来了解几个术语:类,类对象,实例对象,属性,函数和方法。
类是对现实世界中一些事物的封装,定义一个类可以采用下面的方式来定义:
class className: // blockpass
注意类名后面有个冒号,在 block 块里面就可以定义属性和方法了。当一个类定义完之后,就产生了一个类对象。类对象支持两种操作:引用和实例化。引用操作是通过类对象去调用类中的属性或者方法,而实例化是产生出一个类对象的实例,称作实例对象。比如定义了一个people类:
class People:name = 'jack' # 定义一个 类 的 属性 # 定义一个 实例 的 方法 def printName(self):print(self.name)
people类定义完成之后就产生了一个全局的类对象,可以通过类对象来访问类中的属性和方法了。当通过people.name(至于为什么可以直接这样访问属性后面再解释,这里只要理解类对象这个概念就行了)来访问时,people.name中的people称为类对象,这点和C++中的有所不同。当然还可以进行实例化操作,p=people( ),这样就产生了一个people的实例对象,此时也可以通过实例对象p来访问属性或者方法了(p.name).
理解了类、类对象和实例对象的区别之后,我们来了解一下Python中属性、方法和函数的区别。
在上面代码中注释的很清楚了,name是一个属性,printName( )是一个方法,与某个对象进行绑定的函数称作为方法。一般在类里面定义的函数与类对象或者实例对象绑定了,所以称作为方法;而在类外定义的函数一般没有同对象进行绑定,就称为函数。
二. 属性
class People:name = 'jack'age = 12p = People()
print(p.name, p.age)
定义了一个 People 类,里面定义了name和age属性,默认值分别为'jack'和12。在定义了类之后,就可以用来产生实例化对象了,这句p = People( )实例化了一个对象p,然后就可以通过p来读取属性了。这里的name和age都是公有的,可以直接在类外通过对象名访问,如果想定义成私有的,则需在前面加2个下划线 ' __'。
class People:__name = 'jack'__age = 12p = People()
print(p.__name, p.__age)
这段程序运行会报错:
Traceback (most recent call last): File "C:/PycharmProjects/FirstProject/oop.py", line 6, in <module> print p.__name,p.__age
AttributeError: people instance has no attribute '__name
提示找不到该属性,因为私有属性是不能够在类外通过对象名来进行访问的。在Python中没有像C++中public和private这些关键字来区别公有属性和私有属性,它是以属性命名方式来区分,如果在属性名前面加了2个下划线'__',则表明该属性是私有属性,否则为公有属性(方法也是一样,方法名前面加了2个下划线的话表示该方法是私有的,否则为公有的)。
三. 方法
class People:__name = 'jack'__age = 12def getName(self):return self.__namedef getAge(self):return self.__agep = People()
print(p.getName(), p.getAge())
如果对 self 不好理解的话,可以把它当做C++中类里面的this指针一样理解,就是对象自身的意思,在用某个对象调用该方法时,就将该对象作为第一个参数传递给self。
四. 类中内置的方法
在 Python 中有一些内置的方法,这些方法命名都有比较特殊的地方(其方法名以2个下划线开始然后以2个下划线结束)。类中最常用的就是构造方法和析构方法。
- 构造方法 __init__(self,....):在生成对象时调用,可以用来进行一些初始化操作,不需要显示去调用,系统会默认去执行。构造方法支持重载,如果没有构造方法,系统就自动执行默认的构造方法。
- 析构方法 __del__(self):在释放对象时调用,支持重载,可以在里面进行一些释放资源的操作,不需要显示调用。
还有其他的一些内置方法,比如 __cmp__( ), __len( )__等。下面是常用的内置方法:
内置方法 | 说明 |
__init__(self,...) | 初始化对象,在创建新对象时调用 |
__del__(self) | 释放对象,在对象被删除之前调用 |
__new__(cls,*args,**kwd) | 实例的生成操作 |
__str__(self) | 在使用print语句时被调用 |
__getitem__(self,key) | 获取序列的索引key对应的值,等价于seq[key] |
__len__(self) | 在调用内联函数len()时被调用 |
__cmp__(stc,dst) | 比较两个对象src和dst |
__getattr__(s,name) | 获取属性的值 |
__setattr__(s,name,value) | 设置属性的值 |
__delattr__(s,name) | 删除name属性 |
__getattribute__() | __getattribute__()功能与__getattr__()类似 |
__gt__(self,other) | 判断self对象是否大于other对象 |
__lt__(slef,other) | 判断self对象是否小于other对象 |
__ge__(slef,other) | 判断self对象是否大于或者等于other对象 |
__le__(slef,other) | 判断self对象是否小于或者等于other对象 |
__eq__(slef,other) | 判断self对象是否等于other对象 |
__call__(self,*args) | 把实例对象作为函数调用 |
__init__(): __init__方法在类的一个对象被建立时,马上运行。这个方法可以用来对你的对象做一些你希望的初始化。注意,这个名称的开始和结尾都是双下划线。代码例子:
# Filename: class_init.py
class Person:def __init__(self, name):self.name = namedef sayHi(self):print('Hello, my name is', self.name)p = Person('Swaroop')
p.sayHi()# 输出:
# Hello, my name is Swaroop
__new__(): __new__() 在 __init__() 之前被调用,用于生成实例对象。利用这个方法和类属性的特性可以实现设计模式中的单例模式。单例模式是指创建唯一对象吗,单例模式设计的类只能实例化一个对象。
# -*- coding: UTF-8 -*- class Singleton(object):__instance = None # 定义实例 def __init__(self):passdef __new__(cls, *args, **kwd): # 在__init__之前调用 if Singleton.__instance is None: # 生成唯一实例 Singleton.__instance = object.__new__(cls, *args, **kwd)return Singleton.__instance
__getattr__()、__setattr__() 和__getattribute__() :当读取对象的某个属性时,python会自动调用__getattr__()方法。例如,fruit.color将转换为fruit.__getattr__(color)。当使用赋值语句对属性进行设置时,python会自动调用__setattr__()方法。__getattribute__()的功能与__getattr__()类似,用于获取属性的值。但是__getattribute__()能提供更好的控制,代码更健壮。注意,python中并不存在__setattribute__()方法。代码例子:
# -*- coding: UTF-8 -*- class Fruit(object):def __init__(self, color="red", price=0):self.__color = colorself.__price = pricedef __getattribute__(self, item): # 获取属性的方法return object.__getattribute__(self, item)def __setattr__(self, key, value):self.__dict__[key] = valueif __name__ == "__main__":fruit = Fruit("blue", 10)print(fruit.__dict__.get("_Fruit__color")) # 获取color属性fruit.__dict__["_Fruit__price"] = 5print(fruit.__dict__.get("_Fruit__price")) # 获取price属性
Python 不允许实例化的类访问私有数据,但你可以使用 object._className__attrName 访问这些私有属性。
__getitem__(): 如果类把某个属性定义为序列,可以使用__getitem__()输出序列属性中的某个元素.假设水果店中销售多钟水果,可以通过__getitem__()方法获取水果店中的没种水果。代码例子:
# -*- coding: UTF-8 -*- class FruitShop:def __getitem__(self, i): # 获取水果店的水果return self.fruits[i]if __name__ == "__main__":shop = FruitShop()shop.fruits = ["apple", "banana"]print(shop[1])for item in shop: # 输出水果店的水果print(item)# 结果
# banana
# apple
# banana
__str__(): __str__()用于表示对象代表的含义,返回一个字符串.实现了__str__()方法后,可以直接使用print语句输出对象,也可以通过函数str()触发__str__()的执行。这样就把对象和字符串关联起来,便于某些程序的实现,可以用这个字符串来表示某个类。代码例子:
# -*- coding: UTF-8 -*- class Fruit:"""''Fruit类""" # 为Fruit类定义了文档字符串def __str__(self): # 定义对象的字符串表示return self.__doc__if __name__ == "__main__":fruit = Fruit()print(str(fruit)) # 调用内置函数str()触发__str__()方法,输出结果为:Fruit类print(fruit) # 直接输出对象fruit,返回__str__()方法的值,输出结果为:Fruit类
__call__(): 在类中实现__call__()方法,可以在对象创建时直接返回__call__()的内容。使用该方法可以模拟静态方法。代码例子:
# -*- coding: UTF-8 -*- class Fruit:class Growth: # 内部类def __call__(self):print("grow ...")grow = Growth() # 调用Growth(),此时将类Growth作为函数返回,即为外部类Fruit定义方法grow(),grow()将执行__call__()内的代码if __name__ == '__main__':fruit = Fruit()fruit.grow() # 输出结果:grow ...Fruit.grow() # 输出结果:grow ...
五. 类属性、实例属性、类方法、实例方法以及静态方法
在了解了类基本的东西之后,下面看一下python中这几个概念的区别。
先来谈一下类属性和实例属性
在前面的例子中我们接触到的就是类属性,顾名思义,类属性就是类对象所拥有的属性,它被所有类对象的实例对象所共有,在内存中只存在一个副本,这个和C++中类的静态成员变量有点类似。对于公有的类属性,在类外可以通过类对象和实例对象访问。
class people:name = 'jack' # 公有的类属性 __age = 12 # 私有的类属性 p = people()print(p.name) # 正确
print(people.name) # 正确
print(p.__age) # 错误,不能在类外通过实例对象访问私有的类属性
print(people.__age) # 错误,不能在类外通过类对象访问私有的类属性
实例属性是不需要在类中显示定义的,比如:
class People:name = 'jack'p = People()
p.age = 12
print(p.name) # 正确
print(p.age) # 正确print(People.name) # 正确
print(People.age) # 错误
对类对象People进行实例化之后,产生了一个实例对象p,然后p.age = 12这句给p添加了一个实例属性age,赋值为12。这个实例属性是实例对象p所特有的,注意,类对象 People 并不拥有它(所以不能通过类对象来访问这个age属性)。当然还可以在实例化对象的时候给age赋值。
class People:name = 'jack'# __init__()是内置的构造方法,在实例化对象时自动调用 def __init__(self, age):self.age = agep = People(12)
print(p.name) # 正确
print(p.age) # 正确 print(People.name) # 正确
print(People.age) # 错误
如果需要在类外修改类属性,必须通过类对象去引用然后进行修改。如果通过实例对象去引用,会产生一个同名的实例属性,这种方式修改的是实例属性,不会影响到类属性,并且之后如果通过实例对象去引用该名称的属性,实例属性会强制屏蔽掉类属性,即引用的是实例属性,除非删除了该实例属性。
class People:country = 'china'print(People.country)
p = People()
print(p.country)
p.country = 'japan'
print(p.country) # 实例属性会屏蔽掉同名的类属性
print(People.country)
del p.country # 删除实例属性
print(p.country)
下面来看一下类方法、实例方法 和 静态方法 的区别。
类方法:是类对象所拥有的方法,需要用修饰器"@classmethod"来标识其为类方法,对于类方法,第一个参数必须是类对象,一般以"cls"作为第一个参数(当然可以用其他名称的变量作为其第一个参数,但是大部分人都习惯以'cls'作为第一个参数的名字,就最好用'cls'了),能够通过实例对象和类对象去访问。
class People:country = 'china'# 类方法,用classmethod来进行修饰 @classmethoddef getCountry(cls):return cls.countryp = People()
print(p.getCountry()) # 可以用过实例对象引用
print(People.getCountry()) # 可以通过类对象引用
类方法还有一个用途就是可以对类属性进行修改:
class People:country = 'china'# 类方法,用classmethod来进行修饰 @classmethoddef getCountry(cls):return cls.country@classmethoddef setCountry(cls, country):cls.country = countryp = People()
print(p.getCountry()) # 可以用过实例对象引用
print(People.getCountry()) # 可以通过类对象引用 p.setCountry('japan')print(p.getCountry())
print(People.getCountry())
运行结果:
china
china
japan
japan
结果显示在用类方法对类属性修改之后,通过类对象和实例对象访问都发生了改变。
实例方法:在类中最常定义的成员方法,它至少有一个参数并且必须以实例对象作为其第一个参数,一般以名为'self'的变量作为第一个参数(当然可以以其他名称的变量作为第一个参数)。在类外实例方法只能通过实例对象去调用,不能通过其他方式去调用。
class People:country = 'china'# 实例方法 def getCountry(self):return self.countryp = People()
print(p.getCountry()) # 正确,可以用过实例对象引用
print(People.getCountry()) # 错误,不能通过类对象引用实例方法
静态方法: 需要通过修饰器"@staticmethod"来进行修饰,静态方法不需要多定义参数。
class People:country = 'china'@staticmethod# 静态方法 def getCountry():return People.countryprint(People.getCountry())
对于类属性和实例属性,如果在类方法中引用某个属性,该属性必定是类属性,而如果在实例方法中引用某个属性(不作更改),并且存在同名的类属性,此时若实例对象有该名称的实例属性,则实例属性会屏蔽类属性,即引用的是实例属性,若实例对象没有该名称的实例属性,则引用的是类属性;如果在实例方法更改某个属性,并且存在同名的类属性,此时若实例对象有该名称的实例属性,则修改的是实例属性,若实例对象没有该名称的实例属性,则会创建一个同名称的实例属性。想要修改类属性,如果在类外,可以通过类对象修改,如果在类里面,只有在类方法中进行修改。
从类方法和实例方法以及静态方法的定义形式就可以看出来,类方法的第一个参数是类对象cls,那么通过cls引用的必定是类对象的属性和方法;而实例方法的第一个参数是实例对象self,那么通过self引用的可能是类属性、也有可能是实例属性(这个需要具体分析),不过在存在相同名称的类属性和实例属性的情况下,实例属性优先级更高。静态方法中不需要额外定义参数,因此在静态方法中引用类属性的话,必须通过类对象来引用。
六. 继承、多重继承
上面谈到了类的基本定义和使用方法,这只体现了面向对象编程的三大特点之一:封装。下面就来了解一下另外两大特征:继承和多态。
在Python中,如果需要的话,可以让一个类去继承一个类,被继承的类称为父类或者超类、也可以称作基类,继承的类称为子类。并且Python支持多继承,能够让一个子类有多个父类。
Python中类的继承定义基本形式如下:
# 父类
class superClassName: block # 子类
class subClassName(superClassName): block
在定义一个类的时候,可以在类名后面紧跟一对括号,在括号中指定所继承的父类,如果有多个父类,多个父类名之间用逗号隔开。以大学里的学生和老师举例,可以定义一个父类UniversityMember,然后类Student和类Teacher分别继承类UniversityMember:
# -*- coding: UTF-8 -*- class UniversityMember:def __init__(self, name, age):self.name = nameself.age = agedef getName(self):return self.namedef getAge(self):return self.ageclass Student(UniversityMember):def __init__(self, name, age, sno, mark):UniversityMember.__init__(self, name, age) # 注意要显示调用父类构造方法,并传递参数self self.sno = snoself.mark = markdef getSno(self):return self.snodef getMark(self):return self.markclass Teacher(UniversityMember):def __init__(self, name, age, tno, salary):UniversityMember.__init__(self, name, age)self.tno = tnoself.salary = salarydef getTno(self):return self.tnodef getSalary(self):return self.salary
在大学中的每个成员都有姓名和年龄,而学生有学号和分数这2个属性,老师有教工号和工资这2个属性,从上面的代码中可以看到:
1)在Python中,如果父类和子类都重新定义了构造方法__init( )__,在进行子类实例化的时候,子类的构造方法不会自动调用父类的构造方法,必须在子类中显示调用。
2)如果需要在子类中调用父类的方法,需要以”父类名.方法“这种方式调用,以这种方式调用的时候,注意要传递self参数过去。
对于继承关系,子类继承了父类所有的公有属性和方法,可以在子类中通过父类名来调用,而对于私有的属性和方法,子类是不进行继承的,因此在子类中是无法通过父类名来访问的。
Python支持多重继承。对于多重继承,比如
class SubClass(SuperClass1,SuperClass2)
此时有一个问题就是如果SubClass没有重新定义构造方法,它会自动调用哪个父类的构造方法?这里记住一点:以第一个父类为中心。如果SubClass重新定义了构造方法,需要显示去调用父类的构造方法,此时调用哪个父类的构造方法由你自己决定;若SubClass没有重新定义构造方法,则只会执行第一个父类的构造方法。并且若SuperClass1和SuperClass2中有同名的方法,通过子类的实例化对象去调用该方法时调用的是第一个父类中的方法。
七. 多态
多态即多种形态,在运行时确定其状态,在编译阶段无法确定其类型,这就是多态。Python中的多态和Java以及C++中的多态有点不同,Python中的变量是弱类型的,在定义时不用指明其类型,它会根据需要在运行时确定变量的类型(个人觉得这也是多态的一种体现),并且Python本身是一种解释性语言,不进行预编译,因此它就只在运行时确定其状态,故也有人说Python是一种多态语言。在Python中很多地方都可以体现多态的特性,比如内置函数len(object),len函数不仅可以计算字符串的长度,还可以计算列表、元组等对象中的数据个数,这里在运行时通过参数类型确定其具体的计算过程,正是多态的一种体现。这有点类似于函数重载(一个编译单元中有多个同名函数,但参数不同),相当于为每种类型都定义了一个len函数。这是典型的多态表现。有些朋友提出Python不支持多态,我是完全不赞同的。
本质上,多态意味着可以对不同的对象使用同样的操作,但它们可能会以多种形态呈现出结果。len(object)函数就体现了这一点。在C++、Java、C#这种编译型语言中,由于有编译过程,因此就鲜明地分成了运行时多态和编译时多态。运行时多态是指允许父类指针或名称来引用子类对象,或对象方法,而实际调用的方法为对象的类类型方法,这就是所谓的动态绑定。编译时多态有模板或范型、方法重载(overload)、方法重写(override)等。而Python是动态语言,动态地确定类型信息恰恰体现了多态的特征。在Python中,任何不知道对象到底是什么类型,但又需要对象做点什么的时候,都会用到多态。
能够直接说明多态的两段示例代码如下:
1、方法多态
# -*- coding: UTF-8 -*- _metaclass_ = type # 确定使用新式类class Calculator:def count(self, args):return 1calc = Calculator() # 自定义类型from random import choiceobj = choice(['hello,world', [1, 2, 3], calc]) # obj是随机返回的 类型不确定
print(type(obj))
print(obj.count('a')) # 方法多态
对于一个临时对象 obj,它通过 Python 的随机函数取出来,不知道具体类型(是字符串、元组还是自定义类型),都可以调用count方法进行计算,至于count由谁(哪种类型)去做怎么去实现我们并不关心。
有一种称为 "鸭子类型( duck typing )" 的东西,讲的也是多态:
_metaclass_ = type # 确定使用新式类class Duck:def quack(self):print("Quaaaaaack!")def feathers(self):print("The duck has white and gray feathers.")class Person:def quack(self):print("The person imitates a duck.")def feathers(self):print("The person takes a feather from the ground and shows it.")def in_the_forest(duck):duck.quack()duck.feathers()def game():donald = Duck()john = Person()in_the_forest(donald)in_the_forest(john)game()
就 in_the_forest 函数而言,参数对象是一个鸭子类型,它实现了方法多态。但是实际上我们知道,从严格的抽象来讲,Person类型和 Duck 完全风马牛不相及。
2、运算符多态
def add(x, y):return x + yprint(add(1, 2)) # 输出3
print(add("hello,", "world")) # 输出hello,world
print(add(1, "abc")) # 抛出异常 TypeError: unsupported operand type(s) for +: 'int' and 'str'
上例中,显而易见,Python 的加法运算符是 "多态" 的,理论上,我们实现的add方法支持任意支持加法的对象,但是我们不用关心两个参数x和y具体是什么类型。
Python同样支持运算符重载,实例如下:
class Vector:def __init__(self, a, b):self.a = aself.b = bdef __str__(self):return 'Vector (%d, %d)' % (self.a, self.b)def __add__(self, other):return Vector(self.a + other.a, self.b + other.b)v1 = Vector(2, 10)
v2 = Vector(5, -2)
print(v1 + v2)
一两个示例代码当然不能从根本上说明多态。普遍认为面向对象最有价值最被低估的特征其实是多态。我们所理解的多态的实现和子类的虚函数地址绑定有关系,多态的效果其实和函数地址运行时动态绑定有关。在C++, Java, C#中实现多态的方式通常有重写和重载两种,从上面两段代码,我们其实可以分析得出Python中实现多态也可以变相理解为重写和重载。在Python中很多内置函数和运算符都是多态的。
参考文献:
http://www.cnblogs.com/dolphin0520/archive/2013/03/29/2986924.html
http://www.cnblogs.com/jeffwongishandsome/archive/2012/10/06/2713258.html