【深入理解】Python面向对象,魔法方法与反射详解


本专栏的内容参考《python cookbook》

面向对象基础
应用场景

应用:一般情况下,如果我们只是写个脚本,实现一些简单的功能,就使用函数式编程,但是如果我们的代码比较复杂,后期要进行重用,维护等等就使用面向对象编程

  • 面向对象编程:基本设计思路就是程序一开始是要着手解决一个大的问题,然后把一个大问题分解成很多个小问题或子过程,这些子过程再执行的过程再继续分解直到小问题足够简单到可以在一个小步骤范围内解决。
  • 面向过程编程:OOP编程是利用“类”和“对象”来创建各种模型来实现对真实世界的描述,使用面向对象编程的原因一方面是因为它可以使程序的维护和扩展变得更简单,并且可以大大提高程序开发效率 ,另外,基于面向对象的程序可以使它人更加容易理解你的代码逻辑,从而使团队开发变得更从容。
面向对象的特性:
继承(新式与旧式类)
  • 继承:面向对象编程 (OOP) 语言的一个主要功能就是“继承”。继承是指这样一种能力:它可以使用现有类的所有功能,并在无需重新编写原来的类的情况下对这些功能进行扩展。

继承例子实操:

 #!usr/local/bin/python3
  2 class People:
  3         def __init__(self,name,age):
  4                 self.name = name
  5                 self.age = age
  6         def eat(self):
  7                 print("{} am eating".format(self.name))
  8 
  9 class Man(People):
 10         def working(self):
 11                 print("%s am working"%(self.name))
 12         def eat(self):
 13         # 重写父亲的方法,且子类里面可以调用父类的方法
 14                 People.eat(self)
 15                 print("{} am eating.i am man".format(self.name))
 16 
 17 class Woman(People):
 18         def play(self):
 19                 print("%s is playing"%(self.name))
 20 if __name__ == '__main__':
 21         xiaoming = Man('xiaohong','28')
 22         xiaoming.eat()
 23         xiaoming.working()
 24         woman = Woman('xiaohong','29')
 25         woman.play()

继承实操2:

# class People: 经典类
class People(object): #新式类
    def __init__(self,name,age):
        self.name = name
        self.age = age
        self.friends = []
        print("--doens't run ")
    def eat(self):
        print("%s is eating..." % self.name)
    def talk(self):
        print("%s is talking..." % self.name)
    def sleep(self):
        print("%s is sleeping..." % self.name)

class Relation(object):
    def __init__(self,n1,n2):
        print("init in relation")
    def make_friends(self,obj): #w1
        print("%s is making friends with %s" % (self.name,obj.name))
        self.friends.append(obj.name)
class Man(Relation,People):
    # def __init__(self,name,age,money):
    #     #People.__init__(self,name,age)
    #     super(Man,self).__init__(name,age) #新式类写法
    #     self.money  = money
    #     print("%s 一出生就有%s money" %(self.name,self.money))
    def pia(self):
        print("%s is piaing ..... 20s....done." % self.name)
    def sleep(self):
        People.sleep(self)
        print("man is sleeping ")
class Woman(People,Relation):
    def get_birth(self):
        print("%s is born a baby...." % self.name)

m1 = Man("NiuHanYang",22)
# w1 = Woman("ChenRonghua",26)
#
# m1.make_friends(w1)
# w1.name = "陈"
# print(m1.friends[0])

实验得出

  • 当使用多继承的时候,其实是有先后顺序的,比如class Man(Relation,People):那么Python先去找第一个类的构造器,如果找到了,它就不往下找了,找不到再往下找
  • 在下面这段代码里面super(Man,self).__init__(name,age)People.__init__(name,age)的作用是一样的
class Man(Relation,People):
    def __init__(self,name,age,money):
         People.__init__(self,name,age)
         super(Man,self).__init__(name,age) #新式类写法
继承的广度优先和深度优先(新式类与旧式类)

class Man(object)这是新式类,class Man这是旧式类,object就是基类,其实就相当于是祖师爷,我们要记得写上新式类,比较好的习惯

以下内容面试的时候我们可以自己引出来装逼,因为一般不会用到,面试官会觉得你很知道底层

在这段代码里面,三个类的关系以及查找模块的顺序如下
在这里插入图片描述

__author__ = "Alex Li"

class A:
   def __init__(self):
       print("A")
class B(A):
   pass
   # def __init__(self):
   #     print("B")
class C(A):
   pass
   # def __init__(self):
   #     print("C")
class D(B,C):
   pass
   # def __init__(self):
   #     print("D")
obj = D()

一开始我们先都不做注释,慢慢的试一下,会发现它是按照广度优先遍历的,在Python2里边运行是深度优先遍历。
总结一下:

  • 在Python2里面,经典类是深度优先遍历,新式类是广度优先遍历
  • 在Python3里面,因为广度优先遍历效率更高一点,所以都改成了使用广度优先
封装
  • 封装:把一些功能的实现细节不对外暴露,就是封装。
  • 比如我们的一个Student学生类里面假如有成绩和名字这两个数据,那么当我们访问数据的时候,就没有必要从外面的函数去访问他,可以在类里面定义一个访问数据的方法,这样就相当于把数据封装了起来。
  • 比如我们的空调,我们只需要知道怎么用,不需要知道里面的结构是怎么样的也不影响我们的使用,这就是封装。
  • 为啥要封装,封装可以实现对类里面的数据的保护,因为可以吧数据的访问和修改分离开来
多态
  • Python没有直接的语法支持多态。
  • 当我们定义了一个class的时候,就相当于定义了一种数据类型,和Python自带的数据类型list等等没啥两样
  • 多态性(polymorphisn)是指一个接口多种实现,多态目的在于接口的重用。
  • 实例变量和类变量:实例变量是存在于每个实例的内存上面的,而类变量是存在类的内存里面,所有实例共享这个内存。

Pyhon 很多方法都是支持多态的,比如 len(),sorted(), 你给len传字符串就返回字符串的长度,传列表就返回列表长度。
多态示例:

#_*_coding:utf-8_*_
 
 
class Animal(object):
    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):
        print('%s: 喵喵喵!' %self.name)
 
 
class Dog(Animal):
    def talk(self):
        print('%s: 汪!汪!汪!' %self.name)
 
 
 
def func(obj): #一个接口,多种形态
    obj.talk()
 
c1 = Cat('小晴')
d1 = Dog('李磊')
 
func(c1)
func(d1)
静态方法

@staticmethod装饰器可以把一个方法变成静态方法,什么是静态方法,普通的方法可以实例化后直接调用,可以通过self.调用实例变量或者类变量,但是静态方法不可以,静态方法和类已经没多大关系了,它和类有的关系就是,它需要类名来调用这个方法。

类方法

@classmethod可以把一个普通方法变成类方法,类方法只能访问类变量,不能访问实例变量

属性方法

属性方法的作用就是通过@property把一个方法变成一个静态属性,他的作用和应用场景是当我们想要对用户来讲,隐藏实现细节的时候可以用他。详情看Alex的面向对象进阶

私有属性和私有方法,构造器,析构器

在变量或者方法前面加上两个下划线,就是私有属性和私有方法,他们不能通过类名.属性直接调用,只能通过类里面设置方法来调用

  • 私有属性:__val
  • 私有方法:__method,我们要注意使用了双下划线开头的属性会出现名称重整的现象,具体表现在继承里面,这样的属性不会通过继承而被覆盖,比如我们A这个类里面有一个__x=1,那么我B这个类的构造方法初始化self.__x=1,那么当C去继承B,并赋值self.__x=2的时候,不会覆盖B里面的__x=1
  • 构造器:__init__(self,args),用来初始化一个类
  • 析构器:__del__(self,args),用来定义类的实例呗销毁后要干啥,默认就有一个析构函数,你写了就是重构它,否则什么也不干
如何建立类模型

面向对象建模的三字经方法:找名词、加属性、连关系。

self到底是干啥的
  • 为什么有self,他是干啥的,看下图
    在这里插入图片描述
    明白了类的基本定义,接下来我们一起分解一下上面的代码分别 是什么意思
class Role(object): 
#定义一个类, class是定义类的语法,
Role是类名,(object)是新式类的写法,
必须这样写,以后再讲为什么
    def __init__(self,name,role,weapon,life_value=100,money=15000): #初始化函数,在生成一个角色时要初始化的一些属性就填写在这里
        self.name = name #__init__中的第一个参数self,和这里的self都 是什么意思? 看下面解释
        self.role = role
        self.weapon = weapon
        self.life_value = life_value
        self.money = money

上面的这个__init__()叫做初始化方法(或构造方法), 在类被调用时,这个方法(虽然它是函数形式,但在类中就不叫函数了,叫方法)会自动执行,进行一些初始化的动作,所以我们这里写的__init__(self,name,role,weapon,life_value=100,money=15000)就是要在创建一个角色时给它设置这些属性,那么这第一个参数self是干毛用的呢?

初始化一个角色,就需要调用这个类一次:

r1 = Role('Alex','police','AK47’) #生成一个角色 , 会自动把参数传给Role下面的__init__(...)方法
r2 = Role('Jack','terrorist','B22’)  #生成一个角色

我们看到,上面的创建角色时,我们并没有给__init__传值,程序也没未报错,是因为,类在调用它自己的__init__(…)时自己帮你给self参数赋值了,

r1 = Role('Alex','police','AK47’) #此时self 相当于 r1 ,  Role(r1,'Alex','police','AK47’)
r2 = Role('Jack','terrorist','B22’)#此时self 相当于 r2, Role(r2,'Jack','terrorist','B22’)

为什么这样子?
执行r1 = Role(‘Alex’,‘police’,'AK47’)时,python的解释器其实干了两件事:

  • 在内存中开辟一块空间指向r1这个变量名
  • 调用Role这个类并执行其中的__init__(…)方法,相当于Role.init(r1,‘Alex’,‘police’,’AK47’),这么做是为什么呢? 是为了把’Alex’,‘police’,’AK47’这3个值跟刚开辟的r1关联起来,是为了把’Alex’,‘police’,’AK47’这3个值跟刚开辟的r1关联起来,是为了把’Alex’,‘police’,’AK47’这3个值跟刚开辟的r1关联起来,重要的事情说3次,

因为关联起来后,你就可以直接r1.name, r1.weapon 这样来调用啦。所以,为实现这种关联,在调用__init__方法时,就必须把r1这个变量也传进去,否则__init__不知道要把那3个参数跟谁关联呀。所以这个__init__(…)方法里的,self.name = name , self.role = role 等等的意思就是要把这几个值存到r1的内存空间里。

深入类的成员方法

参考廖雪峰的Python定制类

__doc__
  1. __doc__表示类的描述信息,#输出:类的描述信息
class Foo:
    """ 描述类信息,这是用于看片的神奇 """
 
    def func(self):
        pass
 
print Foo.__doc_
__module____class__
  1. __module__表示当前操作的对象是哪个模块
    __class__表示当前操作的对象的类是什么,__class__比较少用
class C:

    def __init__(self):
        self.name = 'wupeiqi'
from lib.aa import C

obj = C()
print obj.__module__  # 输出 lib.aa,即:输出模块
print obj.__class__      # 输出 lib.aa.C,即:输出类
__call__

3.使用了__call__的对象后面加括号,就会触发__call__执行,看一个例子就明白了

#!/usr/local/bin/python3
class Dog(object):
        def __init__(self,name,age):
                self.name = name
                self.age = age
        def __call__(self,*args,**kwargs):
                print("i am a dog,from",args,kwargs)
d = Dog('wang',2)
d(1,2,3,name=333)

输出

[root@jfzijain 类与对象]# python3 call.py
i am a dog,from (1, 2, 3) {'name': 333}
__import__

__import__是使用字符串模式导入模块,而且只能导入最顶层的模块,如```import(’

__dict__

__dict__用于查看类或对象中的所有成员
实操:加上以下代码

d = Dog('wang',2)
print(d.__dict__)
print(Dog.__dict__)

输出:

{'name': 'wang', 'age': 2}  # 实例d的所有属性
{'__module__': '__main__', '__init__': <function Dog.__init__ at 0x7fe13e44f158>, '__call__': <function Dog.__call__ at 0x7fe13e44f730>, '__dict__': <attribute '__dict__' of 'Dog' objects>, '__weakref__': <attribute '__weakref__' of 'Dog' objects>, '__doc__': None}
# 类Dog的所以属性
__file__

print(__file__)会在终端输出当前文件的文件名,但是在pycharm里面会输出当前文件的绝对路径,这是pycharm给我们做的额外操作

__len__

要想len()函数正常工作,类就必须提供一个特殊方法__len__,我们自己定义的类里面如果没有这个魔法方法__len__那么是不能计算长度的。

class Students(object):
    def __init__(self, *args):
        self.names = args
    def __len__(self):
        return len(self.names)
>>> ss = Students('Bob', 'Alice', 'Tim')
>>> print len(ss)
3
__format__

我们在类里面写这个方法可以实现自定义format,也就是可以去自定义我们的格式

__str__

__str__在实际的Django开发模型层里面经常用到,如果我们在类里面定义一个__str__方法,让他return一个结果,那么这个结果就会在打印对象时默认打印出来,一句话就是: 如果一个类中定义了__str__方法,那么在打印 对象 时,默认输出该方法的返回值。

__slot__
  • __slot__是一个类变量,变量值可以是列表,元祖,或者可迭代对象,也可以是一个字符串属性,定义了__slot__,那么我们就只能声明__slot__里面定义的属性,它其实是用来省内存的,
  • 引子:使用点来访问属性本质就是在访问类或者对象的__dict__属性字典(类的字典是共享的,而每个实例的是独立的),我们每产生一个实例就要给这个实例生成一个字典,如果有一个属性很少的类,但是这个类需要产生很多的实例,那么开辟太多的字典就会很耗费内存,这时候__slot__就出场了,定义了__slot__就没有__dict__了,就不会生成字典了,就省了内存
__repr__

前面我们讲过,__str__是在print对象的时候,就会调用,而__repr__是直接输入对象的时候就会调用的,这两个方法都是为了方便我们开发过程中的调试的,我们一般设置为一样的内容._repr__=__str__,两者的区别是__str__()返回用户看到的字符串,而__repr__()返回程序开发者看到的字符串

上下文管理协议

Python里面:我们在类里面写一个__enter__和__exit__方法就可以使类支持上下文管理协议

class Open(object):
    def __init__(self, name):
        self.name = name

    def __enter__(self):
        print('i am __enter__')
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        print('i am __exit__')


with Open('txt') as f:
    print(f.name)

运行结果:
在这里插入图片描述
__enter__是在with这一行执行的时候触发的,with运行的过程触发__enter__,__enter__返回的结果赋值给as限定的变量中
在这里插入图片描述

__import__

__import__方法用于动态加载模块,可以实现通过字符串的映射找到类,但是实操发现它只能找到最顶层的文件参考
这个方法也是经常使用的,要掌握。而importlib.import_module也是通过字符串导入模块,但是它可以一直通过字符串找到最底层的文件来导入
我在test文件夹下新增一个test1的Python文件

def hello():
    print('i am test1')

然后尝试用__import去导入它

import importlib
m = __import__('test.test1')
print(m.hello)

报错发现只能解析到test,而importlib可以

import importlib
# m = __import__('test.test1')
m = importlib.import_module('test.test1')
print(m.hello)
__cmp__

Python3里面已经不支持__cmp__,在Python2里面重写__cmp__可以在对类的实例进行排序的时候,实现自定义排序

__new____metaclass__

__new__ \ __metaclass__装逼利器,面试的时候要吹一吹,因为10个Python程序员,8个不知道,__new__方法是用来实例化类的,注意实例化类是__new__触发__init__进行实例化,不是通过__init__进行实例化,如果我们把__new__完全改写了,那么类就无法被实例化了,我们可以为__new__增加一些功能,实现自定义类.,同时我们要知道我们的最开始创建的那个类对象他是由type创建的。

print type(f) # 输出:<class '__main__.Foo'>     表示,obj 对象由Foo类创建
print type(Foo) # 输出:<type 'type'>              表示,Foo类对象由 type 类创建

类是由 type 类实例化产生

那么问题来了,类默认是由 type 类实例化产生,type类中如何实现的创建类?类又是如何创建对象?

答:类中有一个属性 metaclass,其用来表示该类由 谁 来实例化创建,所以,我们可以为 metaclass 设置一个type类的派生类,从而查看 类 创建的过程。
在这里插入图片描述

创建类的装逼方法

普通方法

class Myclass(object):
	def __init__(self,name)
		self.name = name

高级方法
使用type方法创建
参考:
面向对象基础
面向对象进阶

面向对象中动态语言的特性
  • 当我们定义了一个类,并且实例化了之后,我们就可以为这个实例绑定任何的属性和方法,这是动态语言的特性
  • 动态绑定允许我们在程序运行的过程中动态给class加上功能,这在静态语言中很难实现
class Dog(object):
	pass
Dog.name = 'wang'

但是如果我们想做一些限制,就可以使用__slots____slots__可以限制我们的类实例化后只能绑定什么属性

class Student(object):
    __slots__ = ('name', 'age') # 用tuple定义允许绑定的属性名称

这样我们的类实例化后就只能绑定name与age这两个动态属性了

a = Student()
a.score=100

以上代码就会报错

详解Python反射

什么是反射:反射指的是Python里面通过字符串去反射,去映射到对应的类里面的属性,它实现了一个动态的内存装配,就是我们写一个交互式的程序的时候,我们可以使用字符串进行反射,而不是直接输入内存去交互。

hasattr

hasattr(d,choice),d是一个实例化后的类,choice是字符串,这个函数用于判断有没有一个choice属性在d里边,有就返回True

getattr

getattr(obj,name_str)第一个参数是一个类,第二个参数是一个字符串,用来根据字符串去获取obj对象里的对应的属性的内存地址,当获取的是一个变量,后面不用加括号,如果获取的是一个方法,那么后面必须还要多加一个括号才能执行,即getattr(d,choice)

setattr(obj,name_str,value)

先看源码:
在这里插入图片描述
setattr(obj,name_str,value)
然后好好用心看这段代码

def bulk(self):
    print("%s is yelling...." %self.name)

class Dog(object):
    def __init__(self,name):
        self.name = name

    def eat(self,food):
        print("%s is eating..."%self.name,food)

d = Dog("NiuHanYang")
choice = input(">>:").strip()

if hasattr(d,choice):
    getattr(d,choice)
else:
	# 慢慢琢磨
    setattr(d,choice,bulk) 
    #当我输入talk的时候相当于d.talk = bulk
    func = getattr(d, choice)
    func(d)

当设置属性是变量不是方法时

#!/usr/local/bin/python3
class Dog(object):
        def __init__(self,name):
                self.name = name
        def eat(self):
                print("%s is  eating"%(self.name))
choice = input(">>:").strip()
d = Dog('wang')
if hasattr(d,choice):
        getattr(d,choice)()
else:
        setattr(d,choice,22)
        print(getattr(d,choice))

输出:

>>:'a'
22
Python断言

使用语法:asset type(a) is int
应用场景:一般用在程序开发过程中,如果后面的内容很重要的时候,就需要在前面先使用断言。

assert type(obj.name) is int
print(obj.name /2)
面向对象面试
  1. 聊一聊面向对象的特性吧?

    答:面向对象有三大特性,继承,封装以及多态,继承是为了代码的重用,在Django开发或者说web开发里面,经常会有许多类有一些相同的功能或者说方法,我们把这些方法写到基类里边,然后让其他需要的类去继承,而且还可以重写方法,在Python里边还支持多继承,多继承的时候,Python会去找父类里边的构造方法,第一个父类没有找到,它就会继续往下找,找到了就不再往下找了,这个找父类的过程,我研究过一下,而请问需要说出Python2和Python3里面新式类与旧式类继承时候的区别吗?然后封装,主要体现在两个方面,一个是把同一类方法封装到类里边,另外一个是将数据封装在对象里边。多态是一种接口多种实现,目的在于接口的重用,Python里边许多方法就是多态的例子,额,比如len()

  2. 怎么使用字符串导入一个模块

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值