网站开发报价表 excel,网站自然排名怎么优化,南京门户网站,如何建立自己的面向对象编程#xff1a;https://www.liaoxuefeng.com/wiki/897692888725344/923030496738368面向对象高级编程#xff1a;https://www.liaoxuefeng.com/wiki/897692888725344/9230305380126401、类、对象 类 和 对象 是面向对象编程的两个主要方面。 类 是创建一个 新类型https://www.liaoxuefeng.com/wiki/897692888725344/923030496738368面向对象高级编程https://www.liaoxuefeng.com/wiki/897692888725344/9230305380126401、类、对象 类 和 对象 是面向对象编程的两个主要方面。 类 是创建一个 新类型而对象是这个类 的 实例 。类(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(fname:{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 TestClasspassobj 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 persons 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, thats 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 中所有的 类成员包括数据成员都是 公共的 所有的方法都是 有效的 。只有一个例外如果你使用的数据成员名称以 双下划线前缀 比如 __privatevarPython 的名称管理体系会有效地把它作为私有变量。这样就有一个惯例如果某个变量 只想在类或对象中使用就应该以 单下划线前缀。而其他的名称都将作为公共的可以被其他类/对象使用。记住这只是一个惯例并不是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 stringclass_suite 单继承 示例 # codingutf-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参数
3Python总是首先查找对应类型的方法如果它不能在派生类中找到对应的方法它才开始到基类中逐个查找。先在本类中查找调用的方法找不到才去基类中找。 如果在继承元组中列了一个以上的类那么它就被称作多重继承 。 super() 【继承顺序说明】 在 Python 类的方法method中要调用父类的某个方法在Python 2.2以前通常的写法如下 class A:def __init__(self):print enter Aprint leave Aclass B(A):def __init__(self):print enter BA.__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 BC.__init__(self) # A -- Cprint leave B 如果代码简单这样的改动或许还可以接受。但如果代码量庞大这样的修改可能是灾难性的。因此自Python 2.2开始Python 添加了一个关键字 super来解决这个问题。 class A(object): # A must be new-style classdef __init__(self):print enter Aprint leave Aclass B(C): # A -- Cdef __init__(self):print enter Bsuper(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 Aprint leave Aclass B(object):def __init__(self):print enter Bprint leave Bclass C(A):def __init__(self):print enter Csuper(C, self).__init__()print leave Cclass D(A):def __init__(self):print enter Dsuper(D, self).__init__()print leave Dclass E(B, C):def __init__(self):print enter EB.__init__(self)C.__init__(self)print leave Eclass F(E, D):def __init__(self):print enter FE.__init__(self)D.__init__(self)print leave Ff 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 Asuper(A, self).__init__() # newprint leave Aclass B(object):def __init__(self):print enter Bsuper(B, self).__init__() # newprint leave Bclass C(A):def __init__(self):print enter Csuper(C, self).__init__()print leave Cclass D(A):def __init__(self):print enter Dsuper(D, self).__init__()print leave D
class E(B, C):def __init__(self):print enter Esuper(E, self).__init__() # changeprint leave Eclass F(E, D):def __init__(self):print enter Fsuper(F, self).__init__() # changeprint leave Ff 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-1D-2 是 F 的父类其中表示 E类在前即 FED。所以初始化顺序可以从类结构图来看出 F --- E --- B -- C -- D -- A 由于CD 有同一个父类因此会先初始化 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的方式来保证各个父类的函数被逐一调用而且保证每个父类函数只调用一次如果每个类都使用super5. 混用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 方法重写 如果你的父类方法的功能不能满足你的需求你可以在子类重写你父类的方法示例 #codingutf-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静态方法中不需要额外定义参数因此在静态方法中引用类属性的话必须通过类对象来引用。 # codingutf-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-pythonhttp://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 formif 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 Im a method in A def method(self): self.__method() a A()
a.method() 输出是这样的 $ python example.py
Im a method in A 很好出现了预计的结果。 现在我们给A添加一个子类并重新实现一个__method class B(A): def __method(self): print Im a method in B b B()
b.method() 现在结果是这样的 $ python example.py
Im a method in A 就像我们看到的一样B.method()不能调用B.__method的方法。实际上它是__两个下划线的功能的正常显示。 因此在我们创建一个以__两个下划线开始的方法时这意味着这个方法不能被重写它只允许在该类的内部中使用。 在Python中如是做的很简单它只是把方法重命名了如下 a A()
a._A__method() # never use this!! please!
$ python example.py
Im 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中的有所不同。当然还可以进行实例化操作ppeople( )这样就产生了一个people的实例对象此时也可以通过实例对象p来访问属性或者方法了(p.name). 理解了类、类对象和实例对象的区别之后我们来了解一下Python中属性、方法和函数的区别。 在上面代码中注释的很清楚了name是一个属性printName( )是一个方法与某个对象进行绑定的函数称作为方法。一般在类里面定义的函数与类对象或者实例对象绑定了所以称作为方法而在类外定义的函数一般没有同对象进行绑定就称为函数。 二. 属性 在类中我们可以定义一些属性比如 class People:name jackage 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个下划线的话表示该方法是私有的否则为公有的。 三. 方法 在类中可以根据需要定义一些方法定义方法采用def关键字在类中定义的方法至少会有一个参数一般以名为self的变量作为该参数用其他名称也可以而且需要作为第一个参数。下面看个例子 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, colorred, price0):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 jackp 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 chinaprint(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.countryclassmethoddef 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 chinastaticmethod# 静态方法 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