第十五篇 Python 面向对象

242 阅读30分钟

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。其核心原则是下面两条,请谨记!

  • 子类在调用某个方法或变量的时候,首先在自己内部查找,如果没有找到,则开始根据继承机制在父类里查找。
  • 根据父类定义中的顺序,以深度优先的方式逐一查找父类!

例一:

设想有下面的继承关系:

image.png-7kB

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",左边具有深度优先权,当一条路走到黑也没找到的时候,才换另一条路。可见,在这种继承结构关系中,搜索顺序是这样的:

image.png-31.2kB

例二:

  那如果继承结构是这样的呢?类D和类G又同时继承了类H。当只有B和E有show方法的时候,无疑和上面的例子一样,找到B就不找了,直接打印"i am B"。但如果是只有H和E有show方法呢?

image.png-10.9kB

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的搜索路径是这样的:

image.png-32.2kB

  那可能有同学会问,别的继承情况呢?你这两种继承图太简单了,不能代表所有!实际上其它的继承模式,仔细一解剖,都能划分成上面两种情况,比如下面的例子(箭头代表继承关系),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

  我们用图形来分析它,就是下面的样子:

image.png-30kB

说明:

  • 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参数,且为第一个参数。执行实例方法时,会自动将调用该方法的实例赋值给selfself代表的是类的实例,而非类本身。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__ 方法

注意:

  1. __init__() ⽅法在创建对象时,会默认被调⽤,不需要⼿动的调⽤这个⽅法。
  2. __init__() ⽅法⾥的self参数,在创建对象时不需要传递参数,python解释器会把创建好的对象引⽤直接赋值给self。
  3. 在类的内部,可以使⽤self来使⽤属性和调⽤⽅法;在类的外部,需要使⽤对象名来使⽤属性和调⽤⽅法。
  4. 如果有多个对象,每个对象的属性是各⾃保存的,都有各⾃独⽴的地址。
  5. ⽅法是所有对象共享的,只占⽤⼀份内存空间,⽅法被调⽤时会通过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