1、概述
- 面向过程:根据业务逻辑从上到下垒代码
- 函数式:将某功能代码封装到函数中,日后便无需重复编写,仅调用函数即可
- 面向对象:对函数进行分类和封装,让开发“更快更好更强...”
2、创建类和对象
面向对象编程是一种编程方式,此编程方式的落地需要使用 “类” 和 “对象” 来实现,所以,面向对象编程其实就是对 “类” 和 “对象” 的使用。
- 类就是一个模板,模板里可以包含多个函数,函数里实现一些功能。
- 对象则是根据模板创建的实例,通过实例对象可以执行类中的函数。
class Foo:
# 创建类中的函数
def Bar(self):
# do something
pass
# 根据Foo创建对象obj
obj = Foo()
- class是关键字,表示类
- 创建对象,类名称后加括号即可
- 类名一般需要遵守大驼峰命名法,每一个单词的首字母都大写
- 类中的函数第一个参数必须是self(详细见:类的三大特性之封装)
- 类中定义的函数叫做 “方法”
# 创建类
class Foo:
def Bar(self):
print('Bar')
def Hello(self, name):
print('i am %s' %name)
# 根据类Foo创建对象obj
obj = Foo()
obj.Bar() #执行Bar方法
obj.Hello('zhangsan') #执行Hello方法
说明:
-
obj = Foo()这段代码具体做了什么?
- 调用__new__方法,用来申请内存空间。
- 调用__init__方法传入参数,将self指向创建好的内存空间,填充数据。
- 变量obj 也指向创建好的内存空间。
-
类中可以设置__slots__ = ('name', 'age')属性,用来规定对象可以存在的属性。在日常开发中,不推荐在类的外部直接给对象添加属性这种方式。对象应该具有哪些属性,我们应该封装在类的内部。
3、面向对象三大特性
面向对象的三大特性是指:封装、继承和多态。
3.1、封装
封装,顾名思义就是将内容封装到某个地方,以后再去调用被封装在某处的内容。
所以,在使用面向对象的封装特性时,需要:
- 将内容封装到某处
- 从某处调用被封装的内容
3.1.1、第一步:将内容封装到某处
# 创建类
class Foo:
def __init__(self, name, age):
self.name = name
self.age = age
# 根据类Foo创建对象
# 自动执行Foo类的__init__方法
obj1 = Foo('boxiaoyuan', 18) # 将boxiaoyuan和18分别封装到(obj1和self)的name和age属性中。
obj2 = Foo('zhangsan', 22) # 将zhangsan和22分别封装到(obj2和self)的name和age属性中。
self 是一个形式参数:
- 当执行 obj1 = Foo('boxiaoyuan', 18 ) 时,self 等于 obj1
- 当执行 obj2 = Foo('zhangsan', 22) 时,self 等于 obj2
所以,内容其实被封装到了对象obj1和obj2中,每个对象中都有name和age属性。
3.1.2、第二步:从某处调用被封装的内容
调用被封装的内容时,有两种情况:
- 通过对象直接调用
- 通过self间接调用
1、通过对象直接调用被封装的内容
上图展示了对象 obj1 和 obj2 在内存中保存的方式,根据保存格式可以如此调用被封装的内容:对象.属性名
# 创建类
class Foo:
def __init__(self, name, age):
self.name = name
self.age = age
obj1 = Foo('boxiaoyuan', 18)
print(obj1.name) # 直接调用obj1对象的name属性
print(obj1.age) # 直接调用obj1对象的age属性
obj2 = Foo('zhangsan', 22)
print(obj2.name) # 直接调用obj2对象的name属性
print(obj2.age) # 直接调用obj2对象的age属性
2、通过self间接调用被封装的内容
执行类中的方法时,需要通过self间接调用被封装的内容。
class Foo:
def __init__(self, name, age):
self.name = name
self.age = age
def detail(self):
print self.name
print self.age
obj1 = Foo('wupeiqi', 18)
obj1.detail() # Python默认会将obj1传给self参数,即:obj1.detail(obj1),所以,此时方法内部的 self = obj1,即:self.name 是 wupeiqi ;self.age 是 18
obj2 = Foo('alex', 73)
obj2.detail() # Python默认会将obj2传给self参数,即:obj1.detail(obj2),所以,此时方法内部的 self = obj2,即:self.name 是 alex ; self.age 是 78
综上所述,对于面向对象的封装来说,其实就是使用构造方法将内容封装到对象中,然后通过对象直接或者self间接获取被封装的内容。
3.2、继承
继承,面向对象中的继承和现实生活中的继承相同,即:子可以继承父的内容。
例如,有这样一个设计:一个Dog类型的对象派生自Animal类,这是模拟"是一个(is-a)"关系(例如,Dog是一个Animal)。
例如:
- 猫可以:喵喵叫、吃、喝、拉、撒
- 狗可以:汪汪叫、吃、喝、拉、撒
如果我们要分别为猫和狗创建一个类,那么就需要为猫和狗实现他们所有的功能,不难看出,吃、喝、拉、撒是猫和狗都具有的功能,而我们却分别在猫和狗的类中编写了两次。如果使用继承的思想,如下实现:
动物:吃、喝、拉、撒
猫:喵喵叫(猫继承动物的功能)
狗:汪汪叫(狗继承动物的功能)
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('小白家的小黑猫')
c1.eat()
c2 = Cat('小黑的小白猫')
c2.drink()
d1 = Dog('胖子家的小瘦狗')
d1.eat()
所以,对于面向对象的继承来说,其实就是将多个类共有的方法提取到父类中,子类仅需继承父类而不必一一实现每个方法。
注:除了子类和父类的称谓,你可能看到过派生类和基类 ,他们与子类和父类只是叫法不同而已。
class 父类:
def 父类的方法(self):
# do something
pass
class 子类(父类): # 子类继承父类,即拥有了父类中所有方法
pass
zi = 子类() # 常见子类对象
zi.父类的方法() # 执行父类中继承下来的方法
3.2.1、Python3的继承机制
Python3的继承机制不同于Python2。其核心原则是下面两条,请谨记!
- 子类在调用某个方法或变量的时候,首先在自己内部查找,如果没有找到,则开始根据继承机制在父类里查找。
- 根据父类定义中的顺序,以深度优先的方式逐一查找父类!
例一:
设想有下面的继承关系:
class D:
pass
class C(D):
pass
class B(C):
def show(self):
print("i am B")
pass
class G:
pass
class F(G):
pass
class E(F):
def show(self):
print("i am E")
pass
class A(B, E):
pass
a = A()
a.show()
运行结果是"i am B"。在类A中,没有show()这个方法,于是它只能去它的父类里查找,它首先在B类中找,结果找到了,于是直接执行B类的show()方法。可见,在A的定义中,继承参数的书写有先后顺序,写在前面的被优先继承。
那如果B没有show方法,而是D有呢?
class D:
def show(self):
print("i am D")
pass
class C(D):
pass
class B(C):
pass
class G:
pass
class F(G):
pass
class E(F):
def show(self):
print("i am E")
pass
class A(B, E):
pass
a = A()
a.show()
执行结果是"i am D",左边具有深度优先权,当一条路走到黑也没找到的时候,才换另一条路。可见,在这种继承结构关系中,搜索顺序是这样的:
例二:
那如果继承结构是这样的呢?类D和类G又同时继承了类H。当只有B和E有show方法的时候,无疑和上面的例子一样,找到B就不找了,直接打印"i am B"。但如果是只有H和E有show方法呢?
class H:
def show(self):
print("i am H")
pass
class D(H):
pass
class C(D):
pass
class B(C):
pass
class G(H):
pass
class F(G):
pass
class E(F):
def show(self):
print("i am E")
pass
class A(B, E):
pass
a = A()
a.show()
我们想当然地以为会打印"i am H",因为深度优先嘛。但是,打印的却是"i am E"!为什么?因为在这种情况下,Python的搜索路径是这样的:
那可能有同学会问,别的继承情况呢?你这两种继承图太简单了,不能代表所有!实际上其它的继承模式,仔细一解剖,都能划分成上面两种情况,比如下面的例子(箭头代表继承关系),B同时继承了C和F:
class D():
pass
class G():
def show(self):
print("i am G")
pass
class F(G):
pass
class C(D):
pass
class B(C,F):
pass
class E(F):
def show(self):
print("i am E")
pass
class A(B, E):
pass
我们用图形来分析它,就是下面的样子:
说明:
- Python中针对类提供了⼀个内置属性 mro 可以⽤来查看⽅法的搜索顺序。
- MRO 是 method resolution order 的简称,主要⽤于在多继承时判断⽅法属性的调⽤顺序。
print(C.__mro__)
3.2.2、私有属性的继承特点
class Animal(object):
def __init__(self, name, age):
self.name = name
self.age = age
self.__money = 1000
def eat(self):
print(self.name + '正在吃东西')
def __test(self):
print('我是Animal类里的test方法')
class Person(Animal):
def __demo(self):
print('我是Person里的私有方法')
p = Person('张三', 18)
print(p.name) # 张三
p.eat() # 张三正在吃东西
# p.__test() # 出错
p._Person__demo() # 自己类里定义的私有方法 对象名._类名__私有方法()
p._Animal__test() # 我是Animal类里的test方法 可以通过 对象名._父类名__私有方法()调用
# 私有属性和方法,子类不会继承
# p._Person__test() # 出错 父类的私有方法,子类没有继承
# print(p._Person__money) # 出错
print(p._Animal__money) # 1000
3.2.3、新式类和旧式(经典)类
# 手动指定Student类继承自object
class Student(object):
pass
# 没有指定Dog的父类,python3里默认继承自object
class Dog:
pass
object 是Python中所有对象的基类,提供了⼀些内置的属性和⽅法,可以时候⽤ dir 函数查看。
- 新式类:以 object 为基类的类,推荐使⽤。
- 经典类:不以object为基类的类,不推荐使⽤。
- 在 Python3.x 以后定义类时,如果没有指定⽗类,这个类会默认继承⾃ object,所以,python3.x版本定义的类都是新式类。
- 在Python2.x中定义类时,如果没有指定⽗类,则不会继承⾃object,这个类是一个经典类。
为了保证代码在Python2.x和Python3.x中都能够运⾏,在定义类时,如果⼀个类没有⽗类,建议统⼀继承⾃object。
3.2.4、super()函数:
我们都知道,在子类中如果有与父类同名的成员,那就会覆盖掉父类里的成员。那如果你想强制调用父类的成员呢?使用super()函数!这是一个非常重要的函数,最常见的就是通过super调用父类的实例化方法__init__!
语法:super(子类名, self).方法名(),需要传入的是子类名和self,调用的是父类里的方法,按父类的方法需要传入参数。
class A:
def __init__(self, name):
self.name = name
print("父类的__init__方法被执行了!")
def show(self):
print("父类的show方法被执行了!")
class B(A):
def __init__(self, name, age):
super(B, self).__init__(name=name)
self.age = age
def show(self):
super(B, self).show()
obj = B("jack", 18)
obj.show()
3.3、多态
Pyhon不支持Java和C#这一类强类型语言中多态的写法,但支持原生多态,其Python崇尚“鸭子类型”。
鸭子类型
class F1:
pass
class S1(F1):
def show(self):
print 'S1.show'
class S2(F1):
def show(self):
print 'S2.show'
def Func(obj):
print obj.show()
s1_obj = S1()
Func(s1_obj)
s2_obj = S2()
Func(s2_obj)
实际上,由于Python的动态语言特性,传递给函数Func()的参数obj可以是任何的类型,只要它有一个show()的方法即可。动态语言调用实例方法时不检查类型,只要方法存在,参数正确,就可以调用。这就是动态语言的“鸭子类型”,它并不要求严格的继承体系,一个对象只要“看起来像鸭子,走起路来像鸭子”,那它就可以被看做是鸭子。
3.4、总结
面向对象是一种编程方式,此编程方式的实现是基于对类和对象的使用。类是一个模板,模板中包装了多个“函数”供使用。对象,根据模板创建的实例(即:对象),实例用于调用被包装在类中的函数。- 面向对象三大特性:封装、继承和多态。
- 类(Class): 用来描述具有相同属性和方法的对象的集合。它定义了该集合中每个对象所共有的属性和方法。其中的对象被称作类的实例。
- 实例:也称对象。通过类定义的初始化方法,赋予具体的值,称为一个"有血有肉的实体"。
- 实例化:创建类的实例的过程或操作。
- 实例变量:定义在实例中的变量,只作用于当前实例。
- 类变量:类变量是所有实例公有的变量。类变量定义在类中,但在方法体之外。
- 数据成员:类变量、实例变量、方法、类方法、静态方法和属性等的统称。
- 方法:类中定义的函数。
- 静态方法:不需要实例化就可以由类执行的方法。
- 类方法:类方法是将类本身作为对象进行操作的方法。
- 方法重写:如果从父类继承的方法不能满足子类的需求,可以对父类的方法进行改写,这个过程也称override。
- 封装:将内部实现包裹起来,对外透明,提供api接口进行调用的机制。
- 继承:即一个派生类(derived class)继承父类(base class)的变量和方法。
- 多态:根据对象类型的不同以不同的方式进行处理。
4、类的成员
类的成员可以分为三大类:字段、方法和属性:
-
字段:
- 普通字段
- 静态字段
-
方法:
- 普通方法
- 类方法
- 静态方法
-
属性:
- 普通属性
注:所有成员中,只有普通字段的内容保存对象中,即:根据此类创建了多少对象,在内存中就有多少个普通字段。而其他的成员,则都是保存在类中,即:无论对象的多少,在内存中只创建一份。
4.1、字段
字段包括:普通字段和静态字段,他们在定义和使用中有所区别,而最本质的区别是内存中保存的位置不同。
- 普通字段属于对象
- 静态字段属于类
class Province:
# 静态字段
country = '中国'
def __init__(self, name):
# 普通字段
self.name = name
# 直接访问普通字段
obj = Province('河北省')
print(obj.name)
# 直接访问静态字段
Province.country
由上述代码可以看出【普通字段需要通过对象来访问】【静态字段通过类访问】,在使用上可以看出普通字段和静态字段的归属是不同的。其在内容的存储方式类似如下图:
由上图可知:
- 静态字段在内存中只保存一份
- 普通字段在每个对象中都要保存一份
使⽤场景:
- 类的实例记录的某项数据始终保持⼀致时,则定义类属性。
- 实例属性要求每个对象为其单独开辟⼀份内存空间来记录数据,⽽类属性为全类所共有 ,仅占⽤⼀份内存,更加节省内存空间。
在使用实例变量和类变量的时候一定要注意,使用类似(实例.name)访问变量的时候,实例会先在自己的实例变量列表里查找是否有这个实例变量,如果没有,那么它就会去类变量列表里找,如果还没有,弹出异常。为了防止发生上面的混淆情况,对于类变量,请坚持使用类名.类变量的访问方式,不要用实例去访问类变量。
Python动态语言的特点,让我们可以随时给实例添加新的实例变量,给类添加新的类变量和方法。因此,在使用(实例.classroom = '102')的时候,要么是给已有的实例变量classroom重新赋值,要么就是新建一个实例专属的实例变量classroom并赋值为‘102’。
注意点:
- 尽量避免类属性和实例属性同名。如果有同名实例属性,实例对象会优先访问实例属性。
- 类属性只能通过类对象修改,不能通过实例对象修改。
- 类属性也可以设置为私有,前边添加两个下划线。
4.2、方法
方法包括:普通方法、静态方法和类方法,三种方法在内存中都归属于类,区别在于调用方式不同。
- 普通方法:类的实例方法由实例调用,至少包含一个self参数,且为第一个参数。执行实例方法时,会自动将调用该方法的实例赋值给
self。self代表的是类的实例,而非类本身。self不是关键字,而是Python约定成俗的命名,你完全可以取别的名字,但不建议这么做。 - 类方法:类方法由类调用,采用@classmethod装饰,至少传入一个cls(代指类本身,类似self)参数。执行类方法时,自动将调用该方法的类赋值给cls。建议只使用
类名.类方法的调用方式。(虽然也可以使用实例名.类方法的方式调用)。
class Dog:
__type = "狗"
# 类⽅法,⽤classmethod来进⾏修饰
@classmethod
def get_type(cls):
return cls.__type
print(Dog.get_type())
使⽤场景:当方法中需要使⽤类对象 (如访问私有类属性等)时,定义类⽅法,类⽅法⼀般和类属性配合使⽤。
静态方法:静态方法由类调用,无默认参数。将实例方法参数中的self去掉,然后在方法定义上方加上@staticmethod,就成为静态方法。它属于类,和实例无关。建议只使用类名.静态方法的调用方式。(虽然也可以使用实例名.静态方法的方式调用)
class Dog(object):
type = "狗"
def __init__(self):
name = None
# 静态⽅法
@staticmethod
def introduce(): # 静态⽅法不会⾃动传递实例对象和类对象
print("⽝科哺乳动物,属于⻝⾁⽬..")
dog1 = Dog()
Dog.introduce() # 可以⽤ 实例对象 来调⽤ 静态⽅法
dog1.introduce() # 可以⽤ 类对象 来调⽤ 静态⽅法
使⽤场景:当⽅法中既不需要使⽤实例对象(如实例对象,实例属性),也不需要使⽤类对象 (如类属性、类⽅法、创建实例等)时,定义静态⽅法,取消不需要的参数传递,有利于减少不必要的内存占⽤和性能消耗。
相同点 :对于所有的方法而言,均属于类(非对象)中,所以,在内存中也只保存一份。
不同点 :方法调用者不同、调用方法时自动传入的参数不同。
4.3、属性
如果你已经了解Python类中的方法,那么属性就非常简单了,因为Python中的属性其实是普通方法的变种。既要保护类的封装特性,又要让开发者可以使用“对象.属性”的方式操作操作类属性,Python提供了 @property 装饰器。通过@property装饰器,可以直接通过方法名来访问方法,不需要在方法名后添加一对“()”小括号。
@property的语法格式如下:
@property
def 方法名(self)
代码块
由属性的定义和调用要注意一下几点:
-
定义时,在普通方法的基础上添加 @property 装饰器;
-
定义时,属性仅有一个self参数
-
调用时,无需括号
- 方法:foo_obj.func()
- 属性:foo_obj.prop
注意:
- 属性存在意义是:访问属性时可以制造出和访问字段完全相同的假象。
- 属性由方法变种而来,如果Python中没有属性,方法完全可以代替其功能。
定义一个矩形类,并定义用 @property 修饰的方法操作类中的 area 私有属性,代码如下:
class Rect:
def __init__(self,area):
self.__area = area
@property
def area(self):
return self.__area
rect = Rect(30)
# 直接通过方法名来访问 area 方法
print("矩形的面积是:",rect.area)
运行结果为:
矩形的面积为: 30
上面程序中,使用 @property 修饰了 area() 方法,这样就使得该方法变成了 area 属性的 getter 方法。需要注意的是,如果类中只包含该方法,那么 area 属性将是一个只读属性。
也就是说,在使用 Rect 类时,无法对 area 属性重新赋值,即运行如下代码会报错:
rect.area = 90
print("修改后的面积:",rect.area)
运行结果为:
Traceback (most recent call last):
File "C:\Users\mengma\Desktop\1.py", line 10, in <module>
rect.area = 90
AttributeError: can't set attribute
而要想实现修改 area 属性的值,还需要为 area 属性添加 setter 方法,就需要用到 setter 装饰器,它的语法格式如下:
@方法名.setter
def 方法名(self, value):
代码块
例如,为 Rect 类中的 area 方法添加 setter 方法,代码如下:
@area.setter
def area(self, value):
self.__area = value
再次运行如下代码:
rect.area = 90
print("修改后的面积:",rect.area)
运行结果为:
修改后的面积: 90
这样,area 属性就有了 getter 和 setter 方法,该属性就变成了具有读写功能的属性。
除此之外,还可以使用 deleter 装饰器来删除指定属性,其语法格式为:
@方法名.deleter
def 方法名(self):
代码块
例如,在 Rect 类中,给 area() 方法添加 deleter 方法,实现代码如下:
@area.deleter
def area(self):
self.__area = 0
然后运行如下代码:
del rect.area
print("删除后的area值为:",rect.area)
运行结果为:
删除后的area值为: 0
4.4、类成员的修饰符
在类的内部,有各种变量和方法。这些数据成员,可以在类的外部通过实例或者类名进行调用,例如:
class People:
title = "人类"
def __init__(self, name, age):
self.name = name
self.age = age
def print_age(self):
print('%s: %s' % (self.name, self.age))
obj = People("jack", 12)
obj.age = 18
obj.print_age()
print(People.title)
上面的调用方式是我们大多数情况下都需要的,但是往往我们也不希望所有的变量和方法能被外部访问,需要针对性地保护某些成员,限制对这些成员的访问。这样的程序才是健壮、可靠的,也符合业务的逻辑。
在类似JAVA的语言中,有private关键字,可以将某些变量和方法设为私有,阻止外部访问。但是,Python没有这个机制,Python利用变量和方法名字的变化,实现这一功能。
在Python中,如果要让内部成员不被外部访问,可以在成员的名字前加上两个下划线__,这个成员就变成了一个私有成员(private)。私有成员只能在类的内部访问,外部无法访问。
class People:
title = "人类"
def __init__(self, name, age):
self.__name = name
self.__age = age
def print_age(self):
print('%s: %s' % (self.__name, self.__age))
obj = People("jack", 18)
obj.__name
------------------------------
Traceback (most recent call last):
File "F:/Python/pycharm/201705/1.py", line 68, in <module>
obj.__name
AttributeError: 'People' object has no attribute '__name'
那外部如果要对__name和 __age进行访问和修改呢?在类的内部创建外部可以访问的get和set方法!
class People:
title = "人类"
def __init__(self, name, age):
self.__name = name
self.__age = age
def print_age(self):
print('%s: %s' % (self.__name, self.__age))
def get_name(self):
return self.__name
def get_age(self):
return self.__age
def set_name(self, name):
self.__name = name
def set_age(self, age):
self.__age = age
obj = People("jack", 18)
obj.get_name()
obj.set_name("tom")
这样做,不但对数据进行了保护的同时也提供了外部访问的接口,而且在get_name,set_name这些方法中,可以额外添加对数据进行检测、处理、加工、包裹等等各种操作,作用巨大!
比如下面这个方法,会在设置年龄之前对参数进行检测,如果参数不是一个整数类型,则抛出异常。
def set_age(self, age):
if isinstance(age, int):
self.__age = age
else:
raise ValueError
那么,以双下划线开头的数据成员是不是一定就无法从外部访问呢?其实也不是!本质上,从内部机制原理讲,外部不能直接访问__age是因为Python解释器对外把__age变量改成了_People__age,也就是_类名__age(类名前是一个下划线)。因此,投机取巧的话,你可以通过_ People__age在类的外部访问__age变量:
obj = People("jack", 18)
print(obj._People__name)
也就是说:Python的私有成员和访问限制机制是“假”的,没有从语法层面彻底限制对私有成员的访问。这一点和常量的尴尬地位很相似。
拓展:由于Python内部会对双下划线开头的私有成员进行名字变更,所以会出现下面的情况:
class People:
title = "人类"
def __init__(self, name, age):
self.__name = name
self.__age = age
def print_age(self):
print('%s: %s' % (self.__name, self.__age))
def get_name(self):
return self.__name
def set_name(self, name):
self.__name = name
obj = People("jack", 18)
obj.__name = "tom" # 注意这一行
print("obj.__name: ", obj.__name)
print("obj.get_name(): ", obj.get_name())
-------------------
打印结果:
obj.__name: tom
obj.get_name(): jack
一定要注意,此时的obj.__name= 'tom',相当于给obj实例添加了一个新的实例变量__name,而不是对原有私有成员__name的重新赋值。
此外,有些时候,你会看到以一个下划线开头的成员名,比如_name,这样的数据成员在外部是可以访问的,但是,按照约定俗成的规定,当你看到这样的标识符时,意思就是,“虽然我可以被外部访问,但是,请把我视为私有成员,不要在外部访问我!”。
还有,在Python中,标识符类似__xxx__的,也就是以双下划线开头,并且以双下划线结尾的,是特殊成员,特殊成员不是私有成员,可以直接访问,要注意区别对待。同时请尽量不要给自定义的成员命名__name__或__iter__这样的标识,它们都是Python中具有特殊意义的魔法方法名。
4.5、类的成员与下划线总结:
_name、_name_、_name__:建议性的私有成员,不要在外部访问。__name、__name_:强制的私有成员,但是你依然可以蛮横地在外部危险访问。__name__:特殊成员,与私有性质无关,例如__doc__。name_、name__:没有任何特殊性,普通的标识符,但最好不要这么起名。
4.6、类的特殊成员
上文介绍了Python的类成员以及成员修饰符,从而了解到类中有字段、方法和属性三大类成员,并且成员名前如果有两个下划线,则表示该成员是私有成员,私有成员只能由类内部调用。无论人或事物往往都有不按套路出牌的情况,Python的类成员也是如此,存在着一些具有特殊含义的成员,叫做魔法方法,是类里的特殊的一些方法,不需要手动调用,会在何时的实际自动调用,详情如下:
4.6.1、运算相关的魔法函数
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
# 1.调用__new__方法申请内存空间
p1 = Person('zhangsan', 18)
# 1.调用__new__方法申请内存空间
p2 = Person('zhangsan', 18)
print(p1 == p2)
上述代码中,使⽤ == 运算符⽐较两个对象,结果是True还是False? == 到底⽐较的是什么?
怎样比较两个对象是否是同一个对象?比较的内存地址。
is 身份运算符,可以用来判断两个对象是否是同一个对象。
nums1 = [1, 2, 3]
nums2 = [1, 2, 3]
print(nums1 is nums2) # False
print(nums1 == nums2) # True
is 比较两个对象的内存地址。== 比较两个对象的值,会调用对象的__eq__方法,获取这个方法的比较结果。
4.6.1.1、比较运算符相关魔法方法
class Student:
def __init__(self, name, age):
self.name = name
self.age = age
def __eq__(self, other):
return self.name == other.name and self.age == other.age
def __ne__(self, other):
return self.name != other.name or self.age != other.age
def __lt__(self, other):
return self.age < other.age
def __gt__(self, other):
return self.age > other.age
def __le__(self, other):
return self.age <= other.age
def __ge__(self, other):
return self.age >= other.age
s1 = Student("zhangsan", 18)
s2 = Student("zhangsan", 18)
s3 = Student("lisi", 29)
print(s1 == s2) # True
print(s1 != s2) # False
print(s1 < s2) # False
print(s1 > s2) # False
print(s1 <= s3) # True
print(s2 >= s3) # False
4.6.1.2、算数运算符相关魔法方法
class Student:
def __init__(self, name, age):
self.name = name
self.age = age
def __add__(self, other):
return self.age + other
def __sub__(self, other):
return self.age - other
def __mul__(self, other):
return self.age * other
def __truediv__(self, other):
return self.age / other
def __mod__(self, other):
return self.age % other
def __pow__(self, power, modulo=None):
return self.age ** power
s = Student("zhangsan", 19)
print(s + 1) # 20
print(s - 1) # 18
print(s * 2) # 38
print(s / 5) # 3.8
print(s % 5) # 4
print(s ** 2) # 361
4.6.1.3、类型转换相关魔法方法
class Student:
def __init__(self, name, age):
self.name = name
self.age = age
def __int__(self):
return self.age
def __float__(self):
return self.age * 1.0
def __str__(self):
return self.name
def __bool__(self):
return self.age > 18
s = Student('zhangsan', 18)
print(int(s)) # 18
print(float(s)) # 18.0
print(str(s)) # zhangsan
print(bool(s)) # False
if s:
print('hello')
4.6.2、内置属性
使⽤内置函数 dir 可以查看⼀个对象⽀持的所有属性和⽅法,Python中存在着很多的内置属性。
4.6.2.1、__slots__
Python作为一种动态语言,可以在类定义完成和实例化后,给类或者对象继续添加随意个数或者任意类型的变量或方法,这是动态语言的特性。例如:
def print_doc(self):
print("haha")
class Foo:
pass
obj1 = Foo()
obj2 = Foo()
# 动态添加实例变量
obj1.name = "jack"
obj2.age = 18
# 动态的给类添加实例方法
Foo.show = print_doc
obj1.show()
obj2.show()
但是!如果我想限制实例可以添加的变量怎么办?可以使__slots__限制实例的变量,比如,只允许Foo的实例添加name和age属性。
def print_doc(self):
print("haha")
class Foo:
__slots__ = ("name", "age")
pass
obj1 = Foo()
obj2 = Foo()
# 动态添加实例变量
obj1.name = "jack"
obj2.age = 18
obj1.sex = "male" # 这一句会弹出错误
# 但是无法限制给类添加方法
Foo.show = print_doc
obj1.show()
obj2.show()
由于'sex'不在__slots__的列表中,所以不能绑定sex属性,试图绑定sex将得到AttributeError的错误。
Traceback (most recent call last):
File "F:/Python/pycharm/201705/1.py", line 14, in <module>
obj1.sex = "male"
AttributeError: 'Foo' object has no attribute 'sex'
需要提醒的是,__slots__定义的属性仅对当前类的实例起作用,对继承了它的子类是不起作用的。想想也是这个道理,如果你继承一个父类,却莫名其妙发现有些变量无法定义,那不是大问题么?如果非要子类也被限制,除非在子类中也定义__slots__,这样,子类实例允许定义的属性就是自身的__slots__。
4.6.2.2、 __doc__
表示类的描述信息
class Foo:
""" 描述类信息,这是用于看片的神奇 """
def func(self):
pass
print(Foo.__doc__)
# 输出:描述类信息,这是⽤于看⽚的神奇
4.6.2.3、__module__ 和 __class__
__module__表示当前操作的对象在哪个模块__class__表示当前操作的对象的类是什么
class C:
def __init__(self):
self.name = 'boxiaoyuan'
from lib.aa import C
obj = C()
print(obj.__module__) # 输出 lib.aa,即:输出模块
print(obj.__class__) # 输出 lib.aa.C,即:输出类
4.6.2.4、 _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'}
4.6.2.5、__getitem__、__setitem__和__delitem__⽅法
这三个⽅法,是将对象当做字典⼀样进⾏操作。
class Student:
def __init__(self, name, age):
self.name = name
self.age = age
# 获取方法
def __getitem__(self, item):
print('get_item')
return self.__dict__.get(item)
# 修改或新增方法
def __setitem__(self, key, value):
print('set_item')
self.__dict__[key] = value
# 删除方法
def __delitem__(self, key):
print('del_item')
self.__dict__.pop(key)
s1 = Student('ryan', 18)
s2 = Student('tom', 19)
s3 = Student('tracy', 20)
print(s1['name'])
s1['sex'] = 'man'
del s1['name']
print(s1['name'])
4.6.3、魔法方法
4.6.3.1、 __init__
构造方法,通过类创建对象时,自动触发执行。
class Foo:
def __init__(self, name):
self.name = name
self.age = 18
obj = Foo('zhangsan') # 自动执行类中的 __init__ 方法
注意:
__init__()⽅法在创建对象时,会默认被调⽤,不需要⼿动的调⽤这个⽅法。__init__()⽅法⾥的self参数,在创建对象时不需要传递参数,python解释器会把创建好的对象引⽤直接赋值给self。- 在类的内部,可以使⽤self来使⽤属性和调⽤⽅法;在类的外部,需要使⽤对象名来使⽤属性和调⽤⽅法。
- 如果有多个对象,每个对象的属性是各⾃保存的,都有各⾃独⽴的地址。
- ⽅法是所有对象共享的,只占⽤⼀份内存空间,⽅法被调⽤时会通过self来判断是哪个对象调⽤了实例⽅法。
4.6.3.2、 __del__
析构方法,当对象在内存中被释放时,自动触发执行。
注:此方法一般无须定义,因为Python是一门高级语言,程序员在使用时无需关心内存的分配和释放,因为此工作都是交给Python解释器来执行,所以,析构函数的调用是由解释器在进行垃圾回收时自动触发执行的。
class Foo:
def __del__(self):
pass
4.6.3.3、__call__
对象后面加括号,触发执行。
注:构造方法的执行是由创建对象触发的,即:对象 = 类名() ;而对于 call 方法的执行是由对象后加括号触发的,即:对象() 或者 类()()
class Foo:
def __init__(self):
pass
def __call__(self, *args, **kwargs):
print('__call__')
obj = Foo() # 执行 __init__
obj() # 执行 __call__
那么,怎么判断一个对象是否可以被执行呢?能被执行的对象就是一个Callable对象,可以用Python内建的callable()函数进行测试
print(callable(Foo))
4.6.3.4、 __str__
如果一个类中定义了__str__方法,那么在打印对象时,默认输出该方法的返回值,如果没有定义__str__,而定义了__repr__,则打印时调用__repr__,如果都定义,则打印时调用__str__。这也是一个非常重要的方法,需要用户自己定义。
class Foo:
def __str__(self):
return 'boxiaoyuan'
obj = Foo()
print(obj)
# 输出:boxiaoyuan
4.6.3.5、 __iter__
这是迭代器方法!列表、字典、元组之所以可以进行for循环,是因为其内部定义了 __iter__()这个方法。如果用户想让自定义的类的对象可以被迭代,那么就需要在类中定义这个方法,并且让该方法的返回值是一个可迭代的对象。当在代码中利用for循环遍历对象时,就会调用类的这个__iter__()方法。
普通的类:
class Foo:
pass
obj = Foo()
for i in obj:
print(i)
# 报错:TypeError: 'Foo' object is not iterable<br># 原因是Foo对象不可迭代
添加一个__iter__(),但什么都不返回:
class Foo:
def __iter__(self):
pass
obj = Foo()
for i in obj:
print(i)
# 报错:TypeError: iter() returned non-iterator of type 'NoneType'
#原因是 __iter__方法没有返回一个可迭代的对象
返回一个可迭代对象:
class Foo:
def __init__(self, sq):
self.sq = sq
def __iter__(self):
return iter(self.sq)
obj = Foo([11,22,33,44])
for i in obj:
print(i)
# 这下没问题了!
最好的方法是使用生成器:
class Foo:
def __init__(self):
pass
def __iter__(self):
yield 1
yield 2
yield 3
obj = Foo()
for i in obj:
print(i)
4.6.3.6、__len__
在Python中,如果你调用内置的len()函数试图获取一个对象的长度,在后台,其实是去调用该对象的__len__()方法,所以,下面的代码是等价的:
>>> len('ABC')
3
>>> 'ABC'.__len__()
3
Python的list、dict、str等内置数据类型都实现了该方法,但是你自定义的类要实现len方法需要好好设计。
4.6.4、对象相关的内置函数
Python中的身份运算符⽤来判断两个对象是否相等;isinstance⽤来判断对象和类之间的关系;issubclass⽤来判断类与类之间的关系。
4.6.4.1、身份运算符
身份运算符⽤来⽐较两个对象的内存地址,看这两个对象是否是同⼀个对象。
class Person(object):
def __init__(self, name, age):
self.name = name
self.age = age
p1 = Person('张三', 18)
p2 = Person('张三', 18)
p3 = p1
print(p1 is p2) # False
print(p1 is p3) # True
4.6.4.2、isinstance
instance内置函数,⽤来判断⼀个实例对象是否是由某⼀个类(或者它的⼦类)实例化创建出来的。
class Person(object):
def __init__(self, name, age):
self.name = name
self.age = age
class Student(Person):
def __init__(self, name, age, score):
super(Student, self).__init__(name, age)
self.score = score
class Dog(object):
def __init__(self, name, color):
self.name = name
self.color = color
p = Person('tony', 18)
s = Student('jack', 20, 90)
d = Dog('旺财', '⽩⾊')
print(isinstance(p, Person)) # True.对象p是由Person类创建出来的
print(isinstance(s, Person)) # True.对象s是有Person类的⼦类创建出来的
print(isinstance(d, Person)) # False.对象d和Person类没有关系
4.6.4.3、issubclass
issubclass ⽤来判断两个类之间的继承关系。
class Person(object):
def __init__(self, name, age):
self.name = name
self.age = age
class Student(Person):
def __init__(self, name, age, score):
super(Student, self).__init__(name, age)
self.score = score
class Dog(object):
def __init__(self, name, color):
self.name = name
self.color = color
print(issubclass(Student, Person)) # True
print(issubclass(Dog, Person)) # False