前言

Python从设计之初就已经是一门面向对象的语言,正因为如此,在Python中创建一个类和对象是很容易的。本章节我们将详细介绍Python的面向对象编程。

如果你以前没有接触过面向对象的编程语言,那你可能需要先了解一些面向对象语言的一些基本特征,在头脑里头形成一个基本的面向对象的概念,这样有助于你更容易的学习Python的面向对象编程。

接下来我们先来简单的了解下面向对象的一些基本特征。

面向对象技术简介

  • 类(Class): 用来描述具有相同的属性和方法的对象的集合。它定义了该集合中每个对象所共有的属性和方法。对象是类的实例。
  • 方法:类中定义的函数。
  • 类变量:类变量在整个实例化的对象中是公用的。类变量定义在类中且在函数体之外。类变量通常不作为实例变量使用。
  • 数据成员:类变量或者实例变量用于处理类及其实例对象的相关的数据。
  • 方法重写:如果从父类继承的方法不能满足子类的需求,可以对其进行改写,这个过程叫方法的覆盖(override),也称为方法的重写。
  • 局部变量:定义在方法中的变量,只作用于当前实例的类。
  • 实例变量:在类的声明中,属性是用变量来表示的,这种变量就称为实例变量,实例变量就是一个用 self 修饰的变量。
  • 继承:即一个派生类(derived class)继承基类(base class)的字段和方法。继承也允许把一个派生类的对象作为一个基类对象对待。例如,有这样一个设计:一个Dog类型的对象派生自Animal类,这是模拟"是一个(is-a)"关系(例图,Dog是一个Animal)。
  • 实例化:创建一个类的实例,类的具体对象。
  • 对象:通过类定义的数据结构实例。对象包括两个数据成员(类变量和实例变量)和方法。

和其它编程语言相比,Python 在尽可能不增加新的语法和语义的情况下加入了类机制。

Python中的类提供了面向对象编程的所有基本功能:类的继承机制允许多个基类,派生类可以覆盖基类中的任何方法,方法中可以调用基类中的同名方法。对象可以包含任意数量和类型的数据。

面向对象的重要术语:

  1. 多态(polymorphism):一个函数有多种表现形式,调用一个方法有多种形式,但是表现出的方法是不一样的。
  2. 继承(inheritance):子项继承父项的某些功能,在程序中表现某种联系
  3. 封装(encapsulation):把需要重用的函数或者功能封装,方便其他程序直接调用
  4. 类:对具有相同数据或者方法的一组对象的集合
  5. 对象:对象是一个类的具体事例
  6. 实例化:是一个对象事例话的实现
  7. 标识:每个对象的事例都需要一个可以唯一标识这个事例的标记
  8. 实例属性:一个对象就是一组属性的集合
  9. 事例方法:所有存取或者更新对象某个实例一条或者多条属性函数的集合。
  10. 类属性:属于一个类中所有对象的属性,
  11. 类方法:那些无须特定的对性实例就能够工作的从属于类的函数。

函数和面向对象编程的区别:

  • 相同点:都是把程序进行封装、方便重复利用,提高效率。
  • 不同点:函数重点是用于整体调用,一般用于一段不可更改的程序。仅仅是解决代码重用性的问题。
  • 而面向对象除了代码重用性。还包括继承、多态等。使用上更加灵活。

面向对象VS面向过程:

  • 编程范式

    • 编程是 程序 员 用特定的语法+数据结构+算法组成的代码来告诉计算机如何执行任务的过程
    • 实现一个任务的方式有很多种不同的方式, 对这些不同的编程方式的特点进行归纳总结得出来的编程方式类别,即为编程范式。
    • 两种最重要的编程范式分别是面向过程编程和面向对象编程。
  • 面向过程编程(Procedural Programming)
    • 面向过程编程是程序从上到下一步步执行,一步步从上到下,从头到尾的解决问题 。
    • 基本设计思路就是程序一开始是要着手解决一个大的问题,然后把一个大问题分解成很多个小问题或子过程
    • 这些子过程再执行的过程再继续分解直到小问题足够简单到可以在一个小步骤范围内解决。
    • 这样做的问题也是显而易见的,就是如果你要对程序进行修改,对你修改的那部分有依赖的各个部分你都也要跟着修改
    • 如果程序开头你设置了一个变量值 为1 , 但如果其它子过程依赖这个值 为1的变量才能正常运行,那么这个变量1变了,依赖程序全部都要改变

面向对象编程优点

  • 面向对象编程主要优点:易维护,易扩展,效率高

    • 其实OOP编程的主要作用和函数一样也是使你的代码修改和扩展变的更容易
    • 函数编程与OOP的主要区别就是OOP可以使程序更加容易扩展和易更改。
    • OOP编程是利用“类”和“对象”来创建各种模型来实现对真实世界的描述
    • 使用面向对象编程的原因一方面是因为它可以使程序维护和扩展变得更简单,并且可以大大提高程序开发效率
    • 另外,基于面向对象的程序可以使它人更加容易理解你的代码逻辑,从而使团队开发变得更从容。
  • Class 类(模板)
    • 一个类即是对一类拥有相同属性的对象的抽象、蓝图、原型。
    • 在类中定义了这些对象的都具备的属性(variables(data))、共同的方法
  • Object 对象(实例)
    • 一个对象即是一个类的实例化后实例
    • 一个类必须经过实例化后方可在程序中调用,一个类可以实例化多个对象,每个对象亦可以有不同的属性
    • 就像人类是指所有人,每个人是指具体的对象,人与人之前有共性,亦有不同
  • 类中的一些名词
__init__                     # 构造函数
Self.name = name # 实例变量、普通属性 或者叫 普通字段
public_object = "public" # 类变量、公有属性 或则叫 静态字段
self.__heart= "Normal" # 私有属性 在外面无法访问
def shot(self) # 类方法
def db_conn():
print("connecting db...") def db_backup(dbname):
print("导出数据库...", dbname)
print("将备份文件打包,移至相应目录...") def db_backup_test():
print("将备份文件导入测试库,看导入是否成功") def main():
db_conn()
db_backup('my_db')
db_backup_test() if __name__ == '__main__':
main() # 运行结果:
# connecting db...
# 导出数据库... my_db
# 将备份文件打包,移至相应目录...
# 将备份文件导入测试库,看导入是否成功

使用下面伪代码理解面向过程

class Role(object):                    #1、在定义类时继承object就是新式类,没有就是就是旧类式
public_object = "public" #2、在类例定义一个公有属性:所有实例都能访问的属性叫“公有属性”
def __init__(self,name,role,weapon,life_value=100,money=15000): #构造函数==初始化方法:模板
self.name = name #3、普通属性
self.__heart= "Normal" #4、私有属性在外面无法访问
def shot(self): #5、类的方法
print("%s is shooting..."%self.name)
class Role(object):    #在定义类时继承object就是新式类,没有就是就是旧类式
public_object = "public" #在类例定义一个公有属性:所有实例都能访问的属性叫“公有属性”
def __init__(self,name,role,weapon,life_value=100,money=15000): #构造函数==初始化方法:模板
self.name = name #普通属性
self.role = role
self.weapon = weapon
self.life_value = life_value
self.money = money
self.__heart= "Normal" #私有属性在外面无法访问
def shot(self): #类的方法
print("%s is shooting..."%self.name)
def got_shot(self):
print("ah...,I got shot...")
def buy_gun(self,gun_name):
print("%s just bought %s" %(self.name,gun_name))
self.weapon = gun_name #在购买后让实例值改变 #在下面实例化其实就是传入: Role('r1','Alex','police','AK47') 把r1传给了self
#r1就是实例化后产生的当前Role类的实例,所以self就是实例本身
#我理解r1其实就是__init__函数的内存地址
#所以在下面函数中调用self.name就相当于调用r1.name所以可以调用
r1 = Role('Alex','police','AK47') #生成一个角色 只要一实例化就会自动调用__init__
r2 = Role('Jack','terrorist','B22') #生成一个角色 r1.shot()
print(r2.weapon) #在调用r2.buy_gun('AK47')前是:B22
r2.buy_gun('AK47')
print(r2.weapon) #在调用r2.buy_gun('AK47')后是:AK47 #私有属性
# print(r2.__heart) #这里的.__heart是私有属性,所以在外部无法访问
print(r2._Role__heart) #强制访问私有属性的方法 #公有属性
print(r2.public_object) #打印出类的公有属性
Role.public_object = 'change_public' #从全局改变类的公有属性 r1,r2的公有属性都会变
print(r2.public_object) #这里打印可以看出公有属性变成类“change_public"
r2.public_object = "public_r2" #改变r2对象的公有属性,r1不会变
print(r2.public_object) #打印出改变后的r2公有属性

详细代码

公有属性,普通属性,私有属性 比较

  • 公有属性:在内存中仅存一份
  • 普通属性:每个实例对象在内存存一份
  • 私有属性:实例在外部无法调用

类中函数私有化

  • 默认情况下,程序可以从外部访问一个对象的特性    
  • 为了让方法和特性变成私有(从外部无法访问),只要在它的名字前加上双下划线即可
  • 先在__inaccessible从外界是无法访问的,而在内部还能使用(比如从accessible访问)
class Secretive:
def __accessible(self):
print("you can't see me,unless you're calling internally")
def accessible(self):
print("The secret message is:")
self.__accessible()
s = Secretive()
s.accessible() # 运行结果:
# The secret message is:
# you can't see me,unless you're calling internally

类中函数私有化

封装、继承、多态

Encapsulation 封装(隐藏实现细节)

  • 在类中对数据的赋值、内部调用对外部用户是透明的
  • 这使类变成了一个胶囊或容器,里面包含着类的数据和方法
  • 作用:
    • 防止数据被随意修改
    • 使外部程序不需要关注对象内部的构造,只需要通过对外提供的接口进行直接访问
class Foo:

    def __init__(self, name, age ,gender):
self.name = name
self.age = age
self.gender = gender def eat(self):
print "%s,%s岁,%s,吃奶" %(self.name, self.age, self.gender) def he(self):
print "%s,%s岁,%s,喝水" %(self.name, self.age, self.gender) def shui(self):
print "%s,%s岁,%s,睡觉" %(self.name, self.age, self.gender) a = Foo('jack', 10, '男')
a.eat()
a.he()
a.shui() b = Foo('rose', 11, '女')
b.eat()
b.he()
b.shui()

封装代码

Inheritance 继承(代码重用)

  • 一个类可以派生出子类,在这个父类里定义的属性、方法自动被子类继承
  • 比如CS中的警察和恐怖分子,可以将两个角色的相同点写到一个父类中,然后同时去继承它
  • 使用经典类: Person.__init__(self,name,age) 并重写写父类Person的构造方法,实现,先覆盖,再继承,再重构
class Animal:

    def eat(self):
print "%s 吃 " %self.name def drink(self):
print "%s 喝 " %self.name def shit(self):
print "%s 拉 " %self.name def pee(self):
print "%s 撒 " %self.name class Cat(Animal): def __init__(self, name):
self.name = name
self.breed = '猫' def cry(self):
print '喵喵叫' class Dog(Animal): def __init__(self, name):
self.name = name
self.breed = '狗' def cry(self):
print '汪汪叫' # ######### 执行 ######### c1 = Cat('猫one')
c1.eat() c2 = Cat('猫two')
c2.drink() d1 = Dog('狗one')
d1.eat()

继承代码

class Person(object):
def __init__(self,name,age): #执行Person.__init__(self,name,age)时就会将传入的参数执行一遍
self.name = name #所以在BlackPerson中不仅有name,age而且还有sex
self.age = age
self.sex = "normal"
def talk(self):
print("person is talking....") class WhitePerson(Person):
pass class BlackPerson(Person):
def __init__(self,name,age,strength): #先覆盖,再继承,再重构
#先覆盖父类的__init__方法,再继承父类__init__,再加自己的参数
Person.__init__(self,name,age) #先继承父类Person,这里self就是BlackPerson本身
#先将name,age传给子类BlackPerson,然后调用Person.__init__构造方法将参数出入父类()
self.strength = strength #然后再重构自己的方法,即写自己的参数
print(self.name,self.age,self.sex)
print(self.strength)
def talk(self):
print("black balabla")
def walk(self):
print("is walking....") b = BlackPerson("wei er smith",22,"Strong")
b.talk()
b.walk() # 运行结果:
# wei er smith 22 normal
# Strong
# black balabla
# is walking....
# person is talking....

黑人,白人都继承父类Person就可以都有父类的属性和方法了

注意: 关于多继承

  • 在Python中,如果父类和子类都重新定义了构造方法init( ),在进行子类实例化的时候,子类的构造方法不会自动调用父类的构造方法,必须在子类中显示调用。

    • Python的类可以继承多个类,Java和C#中则只能继承一个类
    • Python的类如果继承了多个类,那么其寻找方法的方式有两种,分别是:深度优先和广度优先
    • 当类是经典类时多继承情况下,会按照深度优先方式查找,当类是新式类时,多继承情况下,会按照广度优先方式查找
    • 经典类和新式类,从字面上可以看出一个老一个新,新的必然包含了跟多的功能,也是之后推荐的写法,从写法上区分的话,如果 当前类或者父类继承了object类,那么该类便是新式类,否则便是经典类。

Polymorphism 多态(接口重用)

  • 多态是面向对象的重要特性,简单点说:“一个接口,多种实现”
  • 指一个基类中派生出了不同的子类,且每个子类在继承同样的方法名的同时又对父类的方法做了不同的实现
  • 这就是同一种事物表现出的多种形态
  • 比如黄种人继承了人talk这个功能,但是他说的是中文,而美国人的talk是英文,但是他们是同样的talk
  • 作用:简单的讲就是允许父类调用子类的方法
class Animal:
def __init__(self, name): # Constructor of the class
self.name = name
def talk(self): # Abstract method, defined by convention only
raise NotImplementedError("Subclass must implement abstract method") class Cat(Animal):
def talk(self):
return 'Meow!' class Dog(Animal):
def talk(self):
return 'Woof! Woof!' animals = [Cat('Missy'),
Dog('Lassie')] for animal in animals:
print(animal.name + ': ' + animal.talk()) # 运行结果:
# Missy: Meow!
# Lassie: Woof! Woof!

多态代码

  • 没有谁规定test方法是接收的参数是什么类型的。test方法只规定,接收一个参数,调用这个参数的prt方法。在运行的时候如果这个参数有prt方法,python就执行,如果没有,python就报错,因为abcde都有prt方法,而f没有,所以得到了上边得结果,这就是python的运行方式。

静态方法、类方法、属性方法

静态方法(用这个装饰器来表示  @staticmethod)

  • 作用:静态方法可以更好的组织代码,防止代码变大后变得比较混乱。
  • 特性:  静态方法只是名义上归类管理,实际上在静态方法里访问不了类或则实例中的任何属性
  • 静态方法使用场景:
    • 我们要写一个只在类中运行而不在实例中运行的方法.
    • 经常有一些跟类有关系的功能但在运行时又不需要实例和类参与的情况下需要用到静态方法.
    • 比如更改环境变量或者修改其他类的属性等能用到静态方法.
    • 这种情况可以直接用函数解决, 但这样同样会扩散类内部的代码,造成维护困难.
  • 4. 调用方式:  既可以被类直接调用,也可以通过实例调用
错误示例:

class Person(object):
def __init__(self, name):
self.name = name @staticmethod # 把eat方法变为静态方法
def eat(self):
print("%s is eating" % self.name) d = Person("xiaoming")
d.eat() ############## 结果:
TypeError: eat() missing 1 required positional argument: 'self'

错误示例

因为用静态方法把eat这个方法与Person这个类截断了,eat方法就没有了类的属性了,所以获取不到self.name这个变量。

正确示例: 

class Person(object):
def __init__(self, name):
self.name = name @staticmethod # 把eat方法变为静态方法
def eat(x):
print("%s is eating" % x) d = Person("xiaoming")
d.eat("jack") #就把eat方法当作一个独立的函数给他传参就行了

正确示例

类方法(用这个装饰器来表示 @classmethod)

  • 作用:无需实例化直接被类调用
  • 特性:  类方法只能访问类变量,不能访问实例变量
  • 类方法使用场景: 当我们还未创建实例,但是需要调用类中的方法
  • 调用方式:  既可以被类直接调用,也可以通过实例调用
错误示例: 

class Person(object):
def __init__(self, name):
self.name = name @classmethod # 把eat方法变为类方法
def eat(self):
print("%s is eating" % self.name) d = Person("xiaoming")
d.eat() ########### 结果:
AttributeError: type object 'Person' has no attribute 'name'

错误示例

因为self.name这个变量是实例化这个类传进去的,类方法是不能访问实例变量的,只能访问类里面定义的变量

class Person(object):
name="杰克"
def __init__(self, name):
self.name = name @classmethod # 把eat方法变为类方法
def eat(self):
print("%s is eating" % self.name) d = Person("xiaoming")
d.eat()

正确示例

属性方法(用这个装饰器表示 @property)

  • 作用:属性方法把一个方法变成一个属性,隐藏了实现细节,调用时不必加括号直接d.eat即可调用self.eat()方法
错误示例: 

class Person(object):

    def __init__(self, name):
self.name = name @property # 把eat方法变为属性方法
def eat(self):
print("%s is eating" % self.name) d = Person("xiaoming")
d.eat() ##########
结果:
TypeError: 'NoneType' object is not callable

错误示例

因为eat此时已经变成一个属性了, 不是方法了, 想调用已经不需要加()号了,直接d.eat就可以了

class Person(object):

    def __init__(self, name):
self.name = name @property # 把eat方法变为属性方法
def eat(self):
print("%s is eating" % self.name) d = Person("xiaoming")
d.eat

正确示例

class Flight(object):
def __init__(self,name):
self.flight_name = name def checking_status(self):
print("checking flight %s status " % self.flight_name)
return 1 @property
def flight_status(self):
status = self.checking_status()
if status == 0 :
print("flight got canceled...")
elif status == 1 :
print("flight is arrived...")
elif status == 2:
print("flight has departured already...")
else:
print("cannot confirm the flight status...,please check later") f = Flight("CA980")
f.flight_status

航空公司具体实例

成员修饰符

  • python的类中只有私有成员和公有成员两种,不像c++中的类有公有成员(public),私有成员(private)和保护成员(protected).并且python中没有关键字去修饰成员,默认python中所有的成员都是公有成员,但是私有成员是以两个下划线开头的名字标示私有成员,私有成员不允许直接访问,只能通过内部方法去访问,私有成员也不允许被继承。
class a: # 说明父类的私有成员无法在子类中继承
def __init__(self):
self.ge=123
self.__gene=456 class b(a):
def __init__(self,name):
self.name=name
self.__age=18
super(b,self).__init__() # 这一行会报错
def show(self):
print(self.name)
print(self.__age)
print(self.ge)
print(self.__gene) # 这一行也会报错
obj=b("xiaoming")
print(obj.name)
print(obj.ge)
# print(obj.__gene) # 这个也会报错
obj.show()

上面就是类里面的私有成员了。

类的特殊成员方法

  • __init__

    • __init__方法可以简单的理解为类的构造方法(实际并不是构造方法,只是在类生成对象之后就会被执行)
  • __del__

    • __del__方法是类中的析构方法,当对象消亡的时候(被解释器的垃圾回收的时候会执行这个方法)这个方法默认是不需要写的,不写的时候,默认是不做任何操作的。因为你不知道对象是在什么时候被垃圾回收掉,所以,除非你确实要在这里面做某些操作,不然不要自定义这个方法。

  • __call__对象后面加括号,触发执行。

    • __call__方法在类的对象被执行的时候(obj()或者 类()())会执行。
class Dog(object):
def __init__(self,name):
self.name = '实例变量'
self.name = name
def __call__(self, *args, **kwargs):
print("running call")
print(args,kwargs)
d = Dog("ChenRonghua")
d(name='tom') # 如果只实例化,而不d()调用,__call__函数不会执行 # 运行结果:
# running call
# () {'name': 'tom'}

__call__

  • __int__

    • __int__方法,在对象被int()包裹的时候会被执行,例如int(obj)如果obj对象没有、__int__方法,那么就会报错。在这个方法中返回的值被传递到int类型中进行转换。
  • __str__如果一个类中定义了__str__方法,在打印对象时,默认输出该方法的返回值

    • __str__方法和int方法一样,当对象被str(obj)包裹的时候,如果对象中没有这个方法将会报错,如果有这个方法,str()将接收这个方法返回的值在转换成字符串。
class Foo:
def __str__(self):
return 'alex li'
obj = Foo()
print(obj) # 输出:alex li

__str__

  • __add__

    • __add__方法在两个对象相加的时候,调用第一个对象的__add__方法,将第二个对象传递进来,至于怎么处理以及返回值,那是程序员自定义的,就如下面的例子:
class abc:
def __init__(self,age):
self.age=age
def __add__(self,obj):
return self.age+obj.age
a1=abc(18)
a2=abc(20)
print(a1+a2)
#执行结果:38

__add__

  • __dict__

    • __dict__方法在类里面有,在对象里面也有,这个方法是以字典的形式列出类或对象中的所有成员。就像下面的例子:
class Province:
country = 'China'
def __init__(self, name, count):
self.name = name
self.count = count
def func(self, *args, **kwargs):
print('func')
# 获取类的成员,即:静态字段、方法、
print(Province.__dict__)
# 输出:{'country': 'China', '__module__': '__main__', 'func': <function func at 0x10be30f50>, '__init__': <function __init__ at 0x10be30ed8>, '__doc__': None} obj1 = Province('HeBei',10000)
print(obj1.__dict__)
# 获取 对象obj1 的成员
# 输出:{'count': 10000, 'name': 'HeBei'} obj2 = Province('HeNan', 3888)
print(obj2.__dict__)
# 获取 对象obj1 的成员
# 输出:{'count': 3888, 'name': 'HeNan'}

__dict__

  • __doc__表示类的描述信息
class Foo:
""" 输出类的描述类信息 """
def func(self):
pass
print(Foo.__doc__) #运行结果:描输出类的描述类信息

__doc__

  • __getitem__ __setitem__ __delitem__ 用于索引操作,如字典。以上分别表示获取、设置、删除数据
class Foo(object):
def __getitem__(self, key):
print('__getitem__', key) def __setitem__(self, key, value):
print('__setitem__', key, value) def __delitem__(self, key):
print('__delitem__', key) obj = Foo()
result = obj['k1'] # __getitem__ k1
obj['k2'] = 'wupeiqi' # __setitem__ k2 wupeiqi
del obj['k1'] # __delitem__ k1

如何触发__setitem__ __getitem__方法

  • __getslice__ __setslice__ __delslice__

    • 这三种方式在python2.7中还存在,用来对对象进行切片的,但是在python3之后,将这些特殊方法给去掉了,统一使用上面的方式对对象进行切片,因此在使用__getitem__ __setitem__ 这两个方法之前要先判断传递进参数的类型是不是slice对象。例子如下:
class Foo:
def __init__(self,name,age):
self.name=name
self.age=age
self.li=[1,2,3,4,5,6,7]
def __getitem__(self, item): # 匹配:对象[item]这种形式
if isinstance(item,slice): # 如果是slice对象,返回切片后的结果
return self.li[item] # 返回切片结果
elif isinstance(item,int): # 如果是整形,说明是索引
return item+10
def __setitem__(self, key, value): # 匹配:对象[key]=value这种形式
print(key,value)
def __delitem__(self, key): # 匹配:del 对象[key]这种形式
print(key)
def __getslice__(self,index1,index2):
print(index1,index2) li=Foo("alex",18)
print(li[3:5])
#执行结果:
[4, 5]

__getslice__ __setslice__ __delslice__

  • __iter__

    • 类的对象如果想要变成一个可迭代对象,那么对象中必须要有__iter__方法,并且这个方法返回的是一个迭代器。
    • for 循环的对象如果是一个可迭代的对象,那么会先执行对象中的__iter__方法,获取到迭代器,然后再执行迭代器中的__next__方法获取数据。如果for循环的是一个迭代器,那么直接执行迭代器中的__next__方法。
class Foo:
def __init__(self,name,age):
self.name=name
self.age=age
def __iter__(self):
return iter([1,2,3,4,5]) # 返回的是一个迭代器
li=Foo("alex",18) # 1.如果类中有__iter__方法,他的对象就是可迭代对象
# 2.对象.__iter()的返回值是一个迭代器
# 3.for循环的如果是迭代器,直接执行.next方法
# 4.for循环的如果是可迭代对象,先执行对象.__iter(),获取迭代器再执行next for i in li:
print(i)
#执行结果:
1
2
3
4
5

__iter__

  • __new__ \ __metaclass__  __metaclass__定义这个类以怎样的形式被创建
class User(object):
def __init__(self,name,age):
print('__init__') def __new__(cls, *args, **kwargs):
print("__new__",args,kwargs)
return object.__new__(cls) # 调用一下object的__new__方法否则不往下走
d = User('tom',100) # 运行结果:
# __new__ ('tom', 100) {}
# __init__

__new__

__new__和__init__的区别

  • __new__是一个静态方法,而__init__是一个实例方法.
  • __new__方法会返回一个创建的实例,而__init__什么都不返回.
  • 只有在__new__返回一个cls的实例时后面的__init__才能被调用.
  • 当创建一个新实例时调用__new__,初始化一个实例时用__init__.

isinstance和issubclass

  • 之前讲过isinstance可以判断一个变量是否是某一种数据类型,其实,isinstance不只可以判断数据类型,也可以判断对象是否是这个类的对象或者是这个类的子类的对象,代码如下:
class Foo:
def __init__(self,name,age):
self.name=name
self.age=age
class Son(Foo):
pass
obj=Son("xiaoming",18)
print(isinstance(obj,Foo))
执行结果:True

isinstance代码

issubclass用来判断一个类是否是某个类的子类,返回的是一个bool类型数据,代码如下:

class Foo:
def __init__(self,name,age):
self.name=name
self.age=age
class Son(Foo):
pass
obj=Son("xiaoming",18)
print(issubclass(Son,Foo))
执行结果:True

issubclass代码

 

新式类和经典类区别

  • 首先,写法不一样:
class A:                       #经典类写法
  pass class B(object): #新式类写法
  pass
  • 多继承中,新式类采用广度优先搜索,而旧式类是采用深度优先搜索,python3中全是广度查询
  • 在继承中新式类和经典类写法区别
SchoolMember.__init__(self,name,age,sex)                      #经典类写法
super(Teacher,self).__init__(name,age,sex) #新式类写法

  • 重写特殊的构造方法      

    • 注:如果一个类的构造方法被重写,那么就需要调用超类的构造方法,否则对象可能不能给被正确的初始化
    • 2. 其实调用超类构造方法很容易,SongBird类中只添加一行代码
      • 经典类写法:    Bird.__init__(self)
      • 新式类写法:    super(SongBird,self).__init__()
    • 如果将下面代码没有2中的那句调用父类Bird中的self.hungry方法会报错

      • if self.hungry == True:
      • AttributeError: SongBird instance has no attribute 'hungry'
class Bird:
def __init__(self):
self.hungry = True
def eat(self):
if self.hungry == True:
print("Aaaah...")
self.hungry = False
else:
print("no, thanks")
a = Bird()
a.eat()
a.eat() class SongBird(Bird):
def __init__(self):
# Bird.__init__(self) # 经典类写法
# super(SongBird,self).__init__() # 新式类写法
self.sound = 'Squawk'
def sing(self):
print(self.sound)
b = SongBird()
b.sing()
b.eat()

例1: 新式类,经典类,无超类构造方法报错

class SchoolMember(object):
'''学校成员基类'''
member = 0
def __init__(self,name,age,sex):
self.name = name
self.age = age
self.sex = sex
self.enroll() #只要一实例化就自动注册
def enroll(self):
'''注册'''
print("just enrolled a new school member [%s]"%self.name)
SchoolMember.member += 1
def tell(self):
print("------info:%s-------"%self.name)
for k,v in self.__dict__.items():
print("\t",k,':',v)
def __del__(self): #'''析构方法'''
print("开除了[%s]...."%self.name)
SchoolMember.member -= 1
class Teacher(SchoolMember):
def __init__(self,name,age,sex,salary,course):
SchoolMember.__init__(self,name,age,sex) #经典类写法
#super(Teacher,self).__init__(name,age,sex) #新式类写法
self.salary = salary
self.cource = course
# self.enroll()
def teaching(self):
print("Teacher [%s] is teachin g [%s]"%(self.name,self.cource))
class Student(SchoolMember):
def __init__(self,name,age,sex,course,tuition):
SchoolMember.__init__(self,name,age,sex)
self.cource = course
self.tuition = tuition
self.amount = 0
# self.enroll()
def pay_tuition(self,amount):
print("student [%s] has just paied [%s]"%(self.name,amount))
self.amount += amount
t1 = Teacher("Wusir",28,"F*M",3000,"python")
s1 = Student("HaiTao",38,"M","PYS15",300000)
s2 = Student("Lichuang",12,"M","PYS15",11000)
print(SchoolMember.member)
del s2
print(SchoolMember.member)
t1.tell()

例2: 模拟学校角色,讲师,学生

组合-->类似继承

  • 组合的作用

    • 和继承很相似,可以让在一个类中使用另一个类的方法,但是并不是父类和子类的继承关系
    • 他是先实例化一个类的实例,然后将这个实例当做参数传入列一个类中,所以就可以在这个类中调用了
  • 组合举例说明
    • 如何实现Teacher类不是SchoolMember类的子类的情况下而在Teacher类中调用SchoolMember的方法
    • 方法很简单:首先实例化一个SchoolMember的实例,然后将这个实例当做参数传入到Teacher类中
    • 那么在Teacher类中通过 ***.fangfa  就可以调用了
    • 这里 T1.user_SM() 就是调用了SchoolMember中的tell方法
class SchoolMember(object):
def __init__(self,name,age):
self.name = name
self.age = age
def tell(self):
print("I am SchoolMember,my name is %s"%self.name)
SM1 = SchoolMember("tom",100) class Teacher(object):
def __init__(self,sex,t1):
self.SchoolMember = t1
def user_SM(self):
self.SchoolMember.tell()
T1 = Teacher('F',SM1)
T1.user_SM()

组合实例:Teacher类中调用SchoolMember类的方法

  • 创建一个最简单的Dog类,实例化并调用他的sayhi()方法,这个过程的原理
class Dog(object):
def __init__(self,name,dog_type):
self.name = name
self.type = dog_type
def sayhi(self):
print("i am a dog,my name is %s"%self.name)
d = Dog('LiChuang','京巴') #等价于 Dog(d,"LiChuang","京巴")
d.sayhi() #运行结果:i am a dog,my name is LiChuang
  • 为什么可以在sayhi()类方法中可以调用self.name

    • 在实例化时:d = Dog('LiChuang','京巴')等价于d = Dog(d,'LiChuang','京巴')将实例d自己传递给类中self参数,所以self.name = name  等价于 d.name = name
    • 在调用类方法d.sayhi()的时候相当于调用d.sayhi(d),上一步实例化时已经将d.name = name(赋值给了name)
    • 所以在类方法中调用self.name就是调用d.name, self传递给了sayhi()所以可以调用self.name
  • 实例化d = Dog('LiChuang','京巴')在内存中是怎样存储的?
    • 当一个类被定义后,就会开辟一块内存空间,存放着类自己(可以理解为一个模板)
    • 2执行d = Dog('LiChuang','京巴')时是实质上需要执行以下几步:
      • 第一步:先申请一块内存空间,命名为d
      • 第二步:将第一步中d的地址空间,d实例和‘LiChuang’ ‘京巴’两个参数都传递给类自己(也就是模板)
      • 第三步:刚刚将实例d已经传进去了,所以可以执行d.name = LiChuang, d.type = 京巴,然后将这两个值返回到刚刚实例d申请的内存中

反射

  • hasattr(ogj,name_str) 判断一个对象里是否有对应的字符串方法
class Dog(object):
def eat(self,food):
print("eat method!!!")
d = Dog() #hasattr判断对象d是否有eat方法,有返回True,没有返回False
print(hasattr(d,'eat')) #True
print(hasattr(d,'cat')) #False

hasattr判断对象是否有某方法,返回True或False

  • getattr(obj,name_str) 根据字符串去获取obj对象里的对应的方法对应的内存地址
class Dog(object):
def eat(self):
print("eat method!!!")
d = Dog() if hasattr(d,'eat'): # hasattr判断实例是否有eat方法
func = getattr(d, 'eat') # getattr获取实例d的eat方法内存地址
func() # 执行实例d的eat方法
#运行结果: eat method!!!

getattr获取对象方法内存地址

  • 使用stattr给类实例对象动态添加一个新的方法
class Dog(object):
def eat(self,food):
print("eat method!!!")
d = Dog() def bulk(self): #给Dog类添加一个方法必须先写这个方法
print('bulk method add to Dog obj') d = Dog()
setattr(d,"bulk",bulk) #将bulk方法添加到实例d中,命名为talk方法
d.bulk(d) #实例d调用刚刚添加的talk方法时必须将实例d自身当变量传入,因为他不会自己传入self #1. 注:setattr(x,’y’,z)用法: x就是实例对象 y就是在实例中调用时用的名字,z就是改变属性的值/或则要添加的函数的名字(正真的函数)
#2. setattr( 具体实例名称 , ’在类中调用时使用的名称’ , 要添加的真实函数的名称)
#3. 作用: setattr(d,"bulk",bulk) 将bulk方法添加到实例d中并且在实例中以bulk名称调用

使用stattr给类实例对象动态添加一个新的方法

class Dog(object):
def __init__(self,name):
self.name = name
def eat(self,food):
print("eat method!!!")
d = Dog('Fly') #1 实例d中没有sex这个属性,就会动态添加一个属性 sex = Male
setattr(d,"sex",'Male') #给实例d添加一个属性:sex=Male
print("将实例d添加一个新属性sex=Male:\t",d.sex) #2 如果实例d中本身存在这个属性那么 新的值就会覆盖这个属性
setattr(d,'name','name change to jack')
print("原本名字是Fly改变后的名字是:\t",d.name) # 运行结果:
# 将实例d添加一个新属性sex=Male: Male
# 原本名字是Fly改变后的名字是: name change to jack

使用stattr给类实例对象动态添加一个新的属性

  • delattr删除实例属性
class Dog(object):
def __init__(self,name):
self.name = name
def eat(self,food):
print("%s is eating....."%self.name)
d = Dog("NiuHanYang")
choice = input(">>:").strip()
if hasattr(d,choice):
delattr(d,choice) #使用delattr(d,choice)删除实例的属性那么所以下面打印就会报错
print(d.name) # 运行结果:
# >>:name #输入的值是name
# 下面是报错信息
# Traceback (most recent call last):
# File "C:/Users/admin/PycharmProjects/s14/Day7/test1.py", line 10, in <module>

delattr

单例模式

单例模式原理及作用

  • 单例模式:永远用一个对象得实例,避免新建太多实例浪费资源
  • 实质:使用__new__方法新建类对象时先判断是否已经建立过,如果建过就使用已有的对象
  • 使用场景:如果每个对象内部封装的值都相同就可以用单例模式

创建单例模式举例

class Foo(object):
instance = None
def __init__(self):
self.name = 'alex' def __new__(cls, *args, **kwargs):
if Foo.instance:
return Foo.instance
else:
Foo.instance = object.__new__(cls,*args,**kwargs)
return Foo.instance obj1 = Foo() # obj1和obj2获取的就是__new__方法返回的内容
obj2 = Foo()
print(obj1,obj2) # 运行结果: <__main__.Foo object at 0x00D3B450> <__main__.Foo object at 0x00D3B450> # 运行结果说明:
# 这可以看到我们新建的两个Foo()对象内存地址相同,说明使用的•同一个类,没有重复建立类

创建单例模式举例

这里介绍一个设计模式,设计模式在程序员写了两三年代码的时候,到一定境界了,才会考虑到设计模式对于程序带来的好处,从而使用各种设计模式,这里只是简单的介绍一个简单的设计模式:单例模式。在面向对象中的单例模式就是一个类只有一个对象,所有的操作都通过这个对象来完成,这就是面向对象中的单例模式,下面是实现代码:

class Foo: # 单例模式
__v=None
@classmethod
def ge_instance(cls):
if cls.__v:
return cls.__v
else:
cls.__v=Foo()
return cls.__v
obj1=Foo.ge_instance()
print(obj1)
obj2=Foo.ge_instance()
print(obj2)
obj3=Foo.ge_instance()
print(obj3)
执行结果:
<__main__.Foo object at 0x000001D2ABA01860>
<__main__.Foo object at 0x000001D2ABA01860>
<__main__.Foo object at 0x000001D2ABA01860>

可以看到,三个对象的内存地址都是一样的,其实,这三个变量中存储的都是同一个对象的内存地址,这样有什么好处呢?能够节省资源,就比如在数据库连接池的时候就可以使用单例模式,只创建一个类的对象供其他程序调用,还有在web服务中接收请求也可以使用单例模式来实现,节省资源。

Python 【面向对象】的更多相关文章

  1. python 面向对象初级篇

    Python 面向对象(初级篇) 概述 面向过程:根据业务逻辑从上到下写垒代码 函数式:将某功能代码封装到函数中,日后便无需重复编写,仅调用函数即可 面向对象:对函数进行分类和封装,让开发" ...

  2. Python 面向对象 基础

    编程范式概述:面向过程 和 面向对象 以及函数式编程 面向过程:(Procedure Oriented)是一种以事件为中心的编程思想. 就是分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现 ...

  3. python面向对象进阶(八)

    上一篇<Python 面向对象初级(七)>文章介绍了面向对象基本知识: 面向对象是一种编程方式,此编程方式的实现是基于对 类 和 对象 的使用 类 是一个模板,模板中包装了多个“函数”供使 ...

  4. python 面向对象(进阶篇)

    上一篇<Python 面向对象(初级篇)>文章介绍了面向对象基本知识: 面向对象是一种编程方式,此编程方式的实现是基于对 类 和 对象 的使用 类 是一个模板,模板中包装了多个“函数”供使 ...

  5. python 面向对象编程学习

    1. 问题:将所有代码放入一个py文件:无法维护 方案:如果将代码才分放到多个py文件,好处: 1. 同一个名字的变量互相不影响 2.易于维护 3.引用模块: import module 2.包:解决 ...

  6. Python面向对象详解

    Python面向对象的"怜人之处" Python的待客之道--谁能进来 Python的封装--只给你想要的 Python的继承--到处认干爹 Python的多态--说是就是

  7. python 面向对象和类成员和异常处理

    python 面向对象 你把自己想象成一个上帝,你要创造一个星球,首先你要把它揉成一个个球,两个直径就能创造一个球 class star: '''名字(name),赤道直径(equatorial di ...

  8. python 面向对象学习

    ------Python面向对象初 下面写一个类的简单实用,以便方便理解类 #python 3.5环境,解释器在linux需要改变 #阅读手册查询readme文件 #作者:S12-陈金彭 class ...

  9. 初识python面向对象

    一.初识python面向对象: class Person: #使用class关键字定义一个类 age=0 #类变量(静态变量) def eat(self,food): #定义一个方法 self.age ...

  10. python 面向对象、特殊方法与多范式、对象的属性及与其他语言的差异

    1.python 面向对象 文章内容摘自:http://www.cnblogs.com/vamei/archive/2012/06/02/2532018.html   1.__init__() 创建对 ...

随机推荐

  1. vue2.0学习之动画

    下载animate.css <transition name="v"> <div class="content">需要做动画的内容< ...

  2. Socket设置超时时间

    主要有以下两种方式,我们来看一下方式1: Socket s=new Socket(); s.connect(new InetSocketAddress(host,port),10000); 方式2: ...

  3. Java 线程池(二)

    简介 在上篇 Java 线程池(一) 我们介绍了线程池中一些的重要参数和具体含义,这篇我们看一看在 Java 中是如何去实现线程池的,要想用好线程池,只知其然是远远不够的,我们需要深入实现源码去了解线 ...

  4. OpenCV、EmguCV函数注解

  5. 在CentOS安装CMake (CentOS7 64位适用)

    在CentOS安装CMake 转自:http://www.cnblogs.com/mophee/archive/2013/03/19/2969456.html 一.环境描述 1.系统:CentOS 6 ...

  6. docker E: Unable to locate package nginx

    在使用docker容器时,有时候里边没有安装vim,敲vim命令时提示说:vim: command not found,这个时候就需要安装vim,可是当你敲apt-get install vim命令时 ...

  7. DOCKER中centos7的中文支持

    直接编写看下能否改变成识别中文字体 写到你的~/.bashrc里吧,然后重启终端(我写的是英文的啊,改成你要的) export LC_ALL=en_US.UTF-8 export LANGUAGE=e ...

  8. montagy

    因为只是想分享ghcjs和webgl的使用经验,所以很多地方说的很粗,因为涉及的知识确实很多, 推荐两本书,一本haskell基础的 learn you a haskell for great goo ...

  9. Ionic3学习笔记(十三)HttpClient 实现 HTTP 请求以及踩过的一些坑

    本文为原创文章,转载请标明出处 目录 猫眼API HttpClient 实现 HTTP 请求 安装 HttpClientModule 模块 创建 provider 创建 page 一些坑 坑1: 未在 ...

  10. 通用 mapper的简单使用

    通用 MAPPER的简单使用 官方  https://mapperhelper.github.io/docs/2.use/ 依赖 <dependency> <groupId>t ...