面向对象
简介:
面向对象编程(Object Oriented Programming-OOP) 是一种解决软件复用的设计和编程方法。 这种方法把软件系统中相近相似的操作逻辑和操作 应用数据、状态,以类的型式描述出来,以对象实例的形式在软件系统中复用,以达到提高软件开发效率的作用。
其实面向对象也很简单,却也很难,熟能生巧。你需要了解类和对象,要学会定义类,创建对象。
特点:
- 类(Class): 用来描述具有相同的属性和方法的对象的集合。它定义了该集合中每个对象所共有的属性和方法。对象是类的实例。
- 方法:类中定义的函数。
- 类变量:类变量在整个实例化的对象中是公用的。类变量定义在类中且在函数体之外。类变量通常不作为实例变量使用。
- 数据成员:类变量或者实例变量用于处理类及其实例对象的相关的数据。
- 方法重写:如果从父类继承的方法不能满足子类的需求,可以对其进行改写,这个过程叫方法的覆盖(override),也称为方法的重写。
- 局部变量:定义在方法中的变量,只作用于当前实例的类。
- 实例变量:在类的声明中,属性是用变量来表示的,这种变量就称为实例变量,实例变量就是一个用 self 修饰的变量。
- 继承:即一个派生类(derived class)继承基类(base
class)的字段和方法。继承也允许把一个派生类的对象作为一个基类对象对待。 - 实例化:创建一个类的实例,类的具体对象。
- 对象:通过类定义的数据结构实例。对象包括两个数据成员(类变量和实例变量)和方法。
1. 方法
1.1 静态方法:
- 定义:使用装饰器@staticmethod。参数随意,没有“self”和“cls”参数,但是方法体中不能使用类或实例的任何属性和方法;
- 调用:实例对象和类对象都可以调用。
- 特性: 静态方法只是名义上归类管理,实际上在静态方法里访问不了类或则实例中的任何属性
- 作用:静态方法可以更好的组织代码,防止代码变大后变得比较混乱。
- 静态方法使用场景:
- 我们要写一个只在类中运行而不在实例中运行的方法.
- 经常有一些跟类有关系的功能但在运行时又不需要实例和类参与的情况下需要用到静态方法.
- 比如更改环境变量或者修改其他类的属性等能用到静态方法.
- 这种情况可以直接用函数解决, 但这样同样会扩散类内部的代码,造成维护困难.
class Dog(object):
def __init__(self,name):
self.name = name
@staticmethod
def eat():
print("I am a static method")
d = Dog("ChenRonghua")
d.eat() #方法1:使用实例调用
Dog.eat() #方法2:使用类直接调用
1.2 类方法:
- 定义:使用装饰器@classmethod。第一个参数必须是当前类对象,该参数名一般约定为“cls”,通过它来传递类的属性和方法(不能传实例的属性和方法);
- 调用:实例对象和类对象都可以调用。
- 作用:无需实例化直接被类调用
- 类方法使用场景: 当我们还未创建实例,但是需要调用类中的方法
class Dog(object):
name = '类变量' #在这里如果不定义类变量仅定义实例变量依然报错
def __init__(self,name):
self.name = '实例变量'
self.name = name
@classmethod
def eat(self,food):
print("%s is eating %s"%(self.name,food))
Dog.eat('baozi') #方法1:使用类直接调用
d = Dog("ChenRonghua")
d.eat("包子") #方法2:使用实例d调用
1.3 实例方法:
- 定义:第一个参数必须是实例对象,该参数名一般约定为“self”,通过它来传递实例的属性和方法(也可以传类的属性和方法);
- 调用:只能由实例对象调用。
class Dog(object):
def __init__(self, name):
self.name = name
@property
def eat(self):
print(" %s is eating" % self.name)
d = Dog("ChenRonghua")
d.eat()
# 调用会出以下错误, 说NoneType is not callable, 因为eat此时已经变成一个静态属性了,
# 不是方法了, 想调用已经不需要加()号了,直接d.eat就可以了
1.4 魔法方法:
我们在调用python类中的某个方法时,通常会看到某些特殊的方法,它们总被双下划线所包围,像这种格式:“方法名”,这些方法很强大,充满魔力,可以让你实现很多功能。,如果你的对象实现(重载)了这些方法中的某一个,那么这个方法就会在特殊的情况下被 Python 所调用,你可以定义自己想要的行为,而这一切都是自动发生的。因此了解这类方法的作用及用户很有必要,以下对基本魔法方法做出总结,请看:魔法方法表格
type生成类调用顺序:
new : 先于__init__方法,每生成一个实例执行一次,new 类方法创建实例对象
init : __init__方法每生成一个实例就会执行一次,初始化实例对象
call : 后与__init__方法,C()() 使用类再加一个括号调用, C为类名称
del : 析构方法,删除无用的内存对象(当程序结束会自动自行析构方法)
类实例化时魔法方法调用顺序
class Student(object):
def __new__(cls, *args, **kwargs):
print('__new__')
return object.__new__(cls) # 必须返回父类的__new__方法,否则不不执行__init__方法,无法创建实例
def __init__(self,name):
print('__init__')
self.name = name
def __str__(self): # 作用:打印实例时显示指定字符串,而不是内存地址
print('__str__')
return self.name
def __call__(self, *args, **kwargs): # 当执行C()(*args) 或者 s1(*args) 就会执行__call__
print('__call__',*args)
def __del__(self): # 作用:清除无用的实例对内存的暂用
print('__del__')
#1、实例化时机会执行__new__、__init__
s1 = Student('tom')
#2、执行 实例() 就会执行__call__ 方法,并将参数传递给__call__函数
s1('call01')
#3、当打印实例时就会执行 __str__ 方法下返回的字符串(默认返回的实例地址)
print(s1)
#4、析构方法:当删除实例时就会调用 __del__ 方法
del s1
# 析构方法作用:在程序结束后会自动执行析构方法删除所有实例
# 但是在程序运行时有很多实例是无用的,但是python内存回收机制却不会自动删除他们,这样就浪费内存
# 我们可以执行 del s1 ,那么在程序运行时,python内存回收机制会检测到这些实例时无用的,才会删除
# 其实我们执行del s1,并没有回收内存,只不过是摘除门牌号,python内存回收机制发现没有门牌号后会自动回收内存
new & __init__详解:
- new 至少要有一个参数cls,代表要实例化的类,此参数在实例化时由Python解释器自动 提供
- new 必须要有返回值,返回实例化出来的实例,这点在自己实现 new 时要特别注 意,可以return父类 new 出来的实例,或者直接是object的 new 出来的实例
- init 有一个参数self,就是这个 new 返回的实例, init 在 \new 的基础上 可以完成一些其它初始化的动作, init 不需要返回值 我们可以将类比作制造商, new 方法就是前期的原材料购买环节,
init 方法就是在 有原材料的基础上,加工,初始化商品环节。
1.5 单例模式:
__new__方法书写:
class A(object):
def __init__(self):
print(self)
print("这是 init 方法")
def __new__(cls):
print(id(cls))
print("这是 __new__ 方法")
ret = object.__new__(cls)
print(res)
return ret
print(id(A))
-->: 12345678987654321
a = A()
-->: 12345678987654321
这是 new 方法
-->: <__main__.A object at 0x105b96ac8>
-->: <__main__.A object at 0x105b96ac8>
线程安全的单例:
import threading
"""
线程安全的单利模式
紧跟with后面的语句被求值后,返回对象的 __enter__() 方法被调用,这个方法的返回值将被赋值给as后面的变量。
当with后面的代码块全部被执行完之后,将调用前面返回对象的 __exit__()方法
"""
def synchronized(func):
func.__lock__ = threading.Lock()
def lock_func(*args, **kwargs):
with func.__lock__:
return func(*args, **kwargs)
return lock_func
class Singleton(object):
instance = None
@synchronized
def __new__(cls):
# 关键在于这,每一次实例化的时候,我们都只会返回这同一个instance对象
if not cls.instance:
cls.instance = super(Singleton, cls).__new__(cls)
return cls.instance
先看类,可以看出这里我们先定义了一个类属性instance,接着我们重写了父类的__new__方法,这个方法就是我们在实例化一个对象时最先调用的一个方法。和其他静态语言不一样,其他静态语言,直接调用了构造方法,一般情况下初始化的程序也写在构造方法之中。而python实例化一个对象和初始化是分开的。__new__是类方法,__init__是实例方法,也就是说,__init__是在对象已经创建完成之后,才执行。
在python3中,调用父类的方法是用super()来调用。所以我们这里的思路就是,还是用父类的方法去创造,但是我们要加一个判断,就是说,当这个对象也就是类属性并不为空的时候,我们就不在实例化,而是返回一个已经实例化的类属性。
线程不安全的单例
class Singleton(object):
__instance = None
def __new__(cls, name, age):
# 如果类属性__instance的值为None,那么就创建一个对象
if not cls.__instance:
cls.__instance = object.__new__(cls)
# 如果已经有实例存在,直接返回
return cls.__instance
a = Singleton("Zhangsan", 18)
b = Singleton("lisi", 20)
print(id(a))
print(id(b))
a.age = 30 # 给a指向的对象添加一个属性
print(b.age) # 获取b指向的对象的age属性
del
Python 通过调用 init() 方法构造当前类的实例化对象,而 del() 方法,功能正好和 init() 相反,其用来销毁实例化对象。
事实上在编写程序时,如果之前创建的类实例化对象后续不再使用,最好在适当位置手动将其销毁,释放其占用的内存空间(整个过程称为垃圾回收(简称GC))。
大多数情况下,Python 开发者不需要手动进行垃圾回收,因为 Python 有自动的垃圾回收机制,能自动将不需要使用的实例对象进行销毁。
无论是手动销毁,还是 Python 自动帮我们销毁,都会调用 del() 方法。
2. 特性
面向对象三大特性: 封装,继承,多态
2.1 封装:
- 在类中对数据的赋值、内部调用对外部用户是透明的
- 这使类变成了一个胶囊或容器,里面包含着类的数据和方法 作用:
- 防止数据被随意修改
- 使外部程序不需要关注对象内部的构造,只需要通过对外提供的接口进行直接访问
继承的种类:
- 单继承:一个类继承单个基类
- 多继承:一个类继承多个基类
- 多级继承:一个类继承自单个基类,后者继承自另一个基类
- 分层继承:多个类继承自单个基类
- 混合继承:两种或多种类型继承的混合
封装的好处:
- 将变化隔离
- 便于使用
- 提高复用性
- 提高安全性
封装:将数据进行封装到对象中,以供其他函数进行调用
2.2 Inheritance 继承(代码重用:
- 一个类可以派生出子类,在这个父类里定义的属性、方法自动被子类继承
- 比如CS中的警察和恐怖分子,可以将两个角色的相同点写到一个父类中,然后同时去继承它
- 使用经典类: Person.init(self, name, age)
并重写写父类Person的构造方法,实现,先覆盖,再继承,再重构
继承的优点:
- 节省代码,减少代码的重复性
- 增强耦合性(也就是增强代码可读性)
- 使代码更加规范化
- 子类可以调用父类的所有属性
class D:
def talk(self):
print('D')
class B(D):
pass
# def talk(self):
# print('B')
class C(D):
pass
def talk(self):
print('C')
class A(B,C):
pass
# def talk(self):
# print('A')
a = A()
a.talk()
# 黑人,白人都继承父类Person就可以都有父类的属性和方法了
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....
新式类经典类区别:
Python 2.x中默认都是经典类,只有显式继承了object才是新式类
Python 3.x中默认都是新式类,不必显式的继承object
当类是经典类时,多继承情况下,会按照深度优先方式查找
当类是新式类时,多继承情况下,会按照广度优先方式查找
2.3 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!
Python中多态的特点:
- 只关心对象的实例方法是否同名,不关心对象所属的类型;
- 对象所属的类之间,继承关系可有可无;
- 多态的好处可以增加代码的外部调用灵活度,让代码更加通用,兼容性比较强;
- 多态是调用方法的技巧,不会影响到类的内部设计。
3. 属性
-
类的公有属性
public_attrs:能在类的外部被使用或直接访问。在类内部的方法中使用时 public_attrs_attrs,在类的外部class_name.public_attrs。 -
类的私有属性
__private_attrs:两个下划线开头,声明该属性为私有,不能在类的外部被使用或直接访问。在类内部的方法中使用时 self.__private_attrs。 -
类的(公有)方法
在类的内部,使用 def 关键字来定义一个方法,与一般函数定义不同,类方法必须包含参数 self,且为第一个参数,self 代表的是类的实例。
self 的名字并不是规定死的,也可以使用 this,但是最好还是按照约定是用 self。 -
类的私有方法
__private_method:两个下划线开头,声明该方法为私有方法,只能在类的内部调用 ,不能在类的外部调用。self.__private_methods。
4. 反射: hasattr、getattr、setattr 和 delattr
在做程序开发中,我们常常会遇到这样的需求:需要执行对象里的某个方法,或需要调用对象中的某个变量,但是由于种种原因我们无法确定这个方法或变量是否存在,这是我们需要用一个特殊的方法或机制要访问和操作这个未知的方法或变量,这中机制就称之为反射。
反射就是通过字符串的形式,导入模块;通过字符串的形式,去模块寻找指定函数,并执行。利用字符串的形式去对象(模块)中操作(查找/获取/删除/添加)成员,一种基于字符串的事件驱动!
四大属性:
- 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
- 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!!!
- 使用stattr给类实例对象动态添加一个新的方法
def abc(self):
print("%s正在交谈"%self.name)
class Person(object):
def __init__(self,name):
self.name = name
p = Person("汇森")
setattr(p,"talk",abc) # 将abc函数添加到对象中p中,并命名为talk
p.talk(p) # 调用talk方法,因为这是额外添加的方法,需手动传入对象
# 打印结果 汇森正在交谈
setattr(p,"age",30) # 添加一个变量age,复制为30
print(p.age) # 打印结果:30
- delattr删除对象中的变量。注意:不能用于删除方法
class Person(object):
def __init__(self,name):
self.name = name
def talk(self):
print("%s正在交谈"%self.name)
p = Person("汇森")
delattr(p,"name") # 删除name变量
print(p.name) # 此时将报错