23 - 面向对象基础-封装-属性-方法-访问控制

1 面向对象介绍

        面向对象是一种程序设计思想,它把对象作为程序的基本单元,一个对象包含了数据和操作数据的函数。但并不是所有语言都支持面向对象编程的。简单的从语言本身来分的话,主要分为以下三种:(并不是说其他类型的语言不好,只是场景不适合而已,就好比操作系统,基本都是使用C语言编写的,语言没有优劣,只有适不适合)

  1. 面向机器(汇编语言等):抽象成机器指令,机器容易理解。
  2. 面向过程(C语言等):做一件事,排除个步骤,第一步干什么,第二部干什么,如果出现情况A,做什么处理,如果出现过程B,做什么处理。问题规模小,可以步骤话,按部就班的处理。
  3. 面向对象OOP(Java语言,C++语言,Python等):针对情况复杂的情况,比如表示一个大楼的作用,使用函数就比较麻烦了。

现在大部分语言都是面向对象的,它适合软件规模比较大的场景使用

2 面向对象

        那什么是面向对象?我们可以简单来说它就是一套方法论,是 一种认识世界、分析世界的方法论 。将万事万物都抽象为各种对象,在Python中一切皆对象。下面来说一下,主要的名词含义:

2.1 类class

抽象的概念,是一类事物的共同特征的集合。

  • 数据:有没有耳朵,有没有鼻子
  • 操作事物的能力:能不能吃,能不能说等

用计算机语言来描述,就是属性(数据)和方法(操作事物的能力)的集合。但凡说分类:都是抽象的概念

2.2 对象instance/object

        对象是类的具象、是一个实体。对于我们每个个体,都是抽象概念人类的不同实体(对象,实例),比如你吃鱼,你是对象,鱼也是对象,吃是动作.
PS:在python中我们说类的实例通常会用instance或者object来指代。

  • 属性:对象状态的抽象,用数据结构来描述
  • 操作:对象行为的抽象,用操作名和实现该操作的方法来描述(函数)

每个人都是人类的一个单独实例,都有自己的名字、身高、体重等信息,这些信息是个人的属性,但是这些信息不能保存在人类中,因为它是抽象的概念,不能保存具体的信息,而我们每个人,每个个体,都是具体的人,就可以存储这些具体的属性,而且不同的人具有不同的属性。

2.3 Python的哲学思想

在Python中一切皆对象,对象是数据和操作的封装,对象是独立的,但是对象之间可以相互作用(你推我,我打你,相互作用)。

目前OOP是最接近人类认知的编程范式

3 面向对象的要素

面向对象有三大特点:

  1. 封装(Encapsulation),将数据和操作组装到一起,对外只暴露一些借口,通过接口访问对象。(比如驾驶员驾驶汽车,不需要了解汽车的构造,只需要知道使用什么部件怎么驾驶皆可以了,踩了油门就能跑,可以不了解其中的机动原理。)
  2. 继承(Inheritance),多复用(继承来的就不用写了),遵循多继承少修改原则,OCP(Open-closed Principle),使用继承来改变,来体现个性(修改实例自己,不要去修改父类)
  3. 多态(Polymorphism),面向对象编程最灵活的地方,动态绑定,比如人吃和猫吃,都是吃,但是不同吃又不太一样。简单来说就是:同一种方法,在不同的子类上,表现方式不同。(父类、子类通过继承联系在一起,通过一套方法,就可以实现不同的表现,就是多态)

4 Python的类

对象是特征(变量)与技能(函数)的结合,类是一些列对象共有的特征与技能的结合体.

  • 在现实生活中:先有对象,再总结归纳出的类
  • 在程序中:一定先定义类,再实例化出对象

定义一个类:

class ClassName:
    语句块

规范:

  1. 必须使用class关键字
  2. 类名必须是用大驼峰命名
  3. 类定义完成后(被解释器一行一行解释完毕后),就产生一个类对象,绑定到了标识符ClassName上(一切皆对象思想,类对象也是某个对象(type)的实例)
class MyClass:              # class 类名称
    '''MyClass Doc'''       # 类介绍,通过类对象的__doc__属性获取
    x = 100                 # 类属性
    def showme(self):       # 类方法名(函数)
        print('My Class')   # 函数体
    
    showme = lambda self: print('My Class')     # 等同于上面定义的函数
p = MyClass()
print(p.__doc__)

4.1 类对象及属性

  • 类对象:类的定义执行后会产生一个类对象
  • 类的属性:类定义中的变量和类中定义的方法都是类的属性
  • 类变量:x是类MyClass的变量

所以根据上面例子来说:

  1. MyClass中,x,showme都是类的属性,__doc__也是类的特殊属性。
  2. showme被叫做method(方法),本质上就是普通的函数对象function,它一般要求至少有一个参数。第一个形式参数可以是self(self只是一个惯用标识符,可以换名字,但是不建议,指代当前实例本身)。当使用某个实例调用方法时,这个实例对象会被注入给第一个参数self。所以,self指代的就是实例本身。

4.2 实例化

通过使用类名加括号来构建实例化一个对象。

a = Person() # 实例化
  1. 类名后面加括号,就调用类的实例化方法,完成实例化。
  2. 实例化就真正创建一个该类的对象(实例),每一次实例化都会返回不同的实例对象
daxin = Person()
xiaoming = Person()

注意:上面的两次实例化产生的都是不同的实例,即便是参数相同。Python类实例化后,会自动调用__init__方法。这个方法第一个形式参数必须留给self,其他参数随意。

4.2.1 __init__函数

        在Python中对象的实例化,其实分为两个部分,即实例化和初始化。主要对应__new__和__init__方法。

  1. 实例化(new):得到一个真正的具体的实例
  2. 初始化(init):对这个真正的实例进行定制化(不能返回,或return None)

__变量名__ 的方法叫做魔术方法

        __new__方法比较复杂,一般用在元类的定义和修改上,这里先记住:__new__方法实例化一个对象的过程中会调用__init__方法进行初始化,初始化完毕后再由__new__方法将产生的实例对象进行返回,当__new__方法和__init__方法没有被定义时,会隐式的向上(父类)查找并调用。

class Person():
    def __init__(self, name, age):  # 形参
        self.name = name   # 这里daxin 赋给 self.name
        self.age = age   # 这里20 赋给 self.age
    def sing(self):
        print('{} is sing'.format(self.name))
daxin = Person('daxin',20)    # 实参

注意:

  1. __init__: 的self是由__new__方法注入进来的,不用手动传递,这个self就是实例化后的对象。
  2. self.name和self.age:表示对实例化添加了两个属性name和age,并进行初始化赋值。
  3. __init__: 只是添加了一些定制化的属性,并不会返回对象。
  4. daxin 是由__new__方法返回的具体的实例化对象。

注意:__init__方法只有在实例化的时候才会被调用。

4.2.2 实例对象(instance)

        类实例化后一定会获得一个类的实例,就是实例对象。__init__方法的第一个变量self就是实例本身。通过实例化我们可以获得一个实例对象,比如上面的daxin,我们可以通过daxin.sing()来调用sing方法,但是我们并没有传递self参数啊,这是因为类实例化后,得到一个实例对象,实例对象会绑定方法上,在使用daxin.sing()进行调用时,会把方法的调用者daxin实例,作为第一个参数self传入。而self.name就是daxin实例的name属性,name保存在daxin实例中,而不是Person类中,所以这里的name被叫做实例变量。

类中定义的方法会存放在类中(仅存一份),而不是实例中,实例直接绑定到方法上。

class Person:
    def __init__(self,name):
        self.name = name

    def sing(self):
        print('{} is sing'.format(self.name))

daxin = Person('daxin')
print(daxin.sing)   # <bound method Person.sing of <__main__.Person object at 0x00000198EADD83C8>>  绑定方法
print(Person.sing)  # <function Person.sing at 0x00000198EADD99D8>  方法函数

        上例中,在调用daxin.sing时,daxin实例是被绑定到了sing方法上,当 a = Person('a') 时,a.sing其实也是把a绑定到了sing方法上,仔细看的话,不同实例的sing方法的内存地址是相同的.

4.2.3 实例变量和类变量

        实例变量是每一个实例自己的变量,是自己独有的。类变量是类的变量,是类的所有实例共享的属性和方法。下面我们从一个例子来看实例变量和类变量

In [2]: class Person: 
   ...:     country = 'China'         # 类变量
   ...:     def __init__(self, name, gender): 
   ...:         self.name = name      # 实例变量
   ...:         self.gender = gender 
   ...:                                                                                                 

In [3]: daxin = Person('daxin','male')                                                                  

In [4]: daxin.country                                                                          
Out[4]: 'China'

In [5]: daxin.name                                                                                      
Out[5]: 'daxin'

In [6]: Person.country                                                                                  
Out[6]: 'China'

In [7]: Person.name                                                                                     
---------------------------------------------------------------------------
AttributeError       

观察例子,我们可以发现:

  1. 类变量我们可以通过类访问,也可以通过实例访问
  2. 实例变量只能通过实例访问(因为类本身是不会知道谁是他的实力)

类变量可以通过实例调用,但实例变量是实例私有的,无法通过类调用。

PS: 获取类属性的不同的方式

daxin.age
daxin.__class__.age
Person.age
type(daxin).age 

有几个特殊的属性先来看一下,便于后续理解

特殊属性 含义
__name__ 获取类对象的对象名(返回字符串)
__class__ 获取实例对象的类型(相当于type),type和__class__返回的是同样的东西
__dict__ 对象的属性的字典
__qualname__ 类的限定名
In [8]: daxin.__class__                                                                                 
Out[8]: __main__.Person

In [9]: Person.__class__                                                                                
Out[9]: type

In [10]: type(daxin)                                                                                    
Out[10]: __main__.Person

In [11]: type(daxin) is Person                                                                          
Out[11]: True

In [13]: Person.__name__                                                                                 
Out[13]: 'Person'   # 返回字符串

疑问:当类变量和实例变量重名时,到底访问的是哪个?

class Person:
    age = 3
    height = 170

    def __init__(self, name, age=18):
        self.name = name
        self.age = age


tom = Person('Tom')
jerry = Person('jetty', 20)

Person.age = 30
print(1, Person.age, tom.age, jerry.age) 
print(2, Person.height, tom.height, jerry.height)
jerry.height = 175
print(3, Person.height, tom.height, jerry.height)
tom.height += 10
print(4, Person.height, tom.height, jerry.height)
Person.height += 15
print(5, Person.height, tom.height, jerry.height)
Person.weight = 70
print(6, Person.weight, tom.weight, jerry.weight)

分析:

  1. Person返回的是自己的类属性,所以Person.age是30,tom优先访问自己的实例变量,由于指定了默认值,所以tom.age是18,jerry指定了age,所以jerry.age是20.
  2. Person返回的是自己的类属性,所以Person.height是170,tom和jerry没有height实例变量,所以访问的都是类变量,值得都是170
  3. Person.height是170,jerry.height=175 赋值即定义了一个实例变量height,所以tom.height = 170, jerry.height = 175
  4. Person.height是170, tom.height += 10 赋值即定义了一个实例变量height, 所以tom.height = tom.height + 10 = 180, jerry.height = 175
  5. Person.height += 15,Person.height = 185,tom和jerry有自己的实例变量不会收Person影响,所以tom.height = 170, jerry.height=175
  6. Person.weight = 70,为Person赋值即定义了一个新的weight属性,所以Person.weight = 70,tom和jerry没有weight属性,访问Person的所以值都是70

通过观察发现,当类变量和实例变量重名时,似乎有一定的查找规则,那么就要说说__dict__了。

4.2.4 __dict__和变量查找顺序

        __dict__是一个非常重要的特殊属性,它是一个存放着对象的属性信息的字典(对实例来说是字典,对类来说是映射)。

In [14]: Person.__dict__                                                                                
Out[14]: 
mappingproxy({'__module__': '__main__',
              'country': 'China',
              '__init__': <function __main__.Person.__init__(self, name, gender)>,
              '__dict__': <attribute '__dict__' of 'Person' objects>,
              '__weakref__': <attribute '__weakref__' of 'Person' objects>,
              '__doc__': None})

In [15]: daxin.__dict__                                                                                 
Out[15]: {'name': 'daxin', 'gender': 'male'}

In [19]: daxin.__dict__['name']         # 因为__dict__是一个字典,我们可以直接通过key来访问                                                              
Out[19]: 'daxin'

通过观察发现:

  1. Person.dict:包含所有类内定义的属性及方法。
  2. daxin.dict : 只记录实例自己的属性信息。

方法是记录在类的__dict__中的

实例属性查找顺序: 指的是实例使用.点号来访问属性,会先找自己的__dict__,如果没有,然后通过属性__class__找到自己的类,然后再在类的__dict__中查找.直接使用实例.__dict__[变量名],不会在类中查找。

4.2.5 总结

  1. 类变量是属于类的变量,这个类的所以实例可以共享这个变量
  2. 对象(实例或者类),可以动态的给自己增加一个属性(赋值即定义)
  3. 实例.dict[变量名] 和 实例.变量名 都可以访问到实例自己的属性
  4. 实例的同名变量会隐藏掉类变量,或者说是覆盖了这个类变量。但是注意类变量还在那里,并没有真正被覆盖。
  5. 实例属性查找顺序:指的是实例使用.点号来访问属性,会先找自己的__dict__,如果没有,然后通过属性__class__找到自己的类,然后再在类的__dict__中查找。直接使用实例.dict[变量名],不会在类中查找。
  6. 一般来说类变量可以全部用大写来表示(当常量用)

4.3 装饰一个类

为一个类通过装饰,增加一些类属性。例如能否给一个类增加一个NAME类属性并提供属性值

def dec(name):
    def warpper(cls):
        cls.NAME = name
        return cls
    return warpper


@dec('tom')  # Person = dec('tom')(Person)
class Person:
    pass

a = Person()
print(a.NAME)

类也可以作为一个装饰器,后面会说

可以给类加装饰器,那么可以给类中的方法加装饰器吗?答案当然是可以的。

4.4 类方法和静态方法

        在类中中定义的方法大多都需要传入一个self参数,用于指定一个实例化对象,用于通过对象来调用这个方法,但是也有例外的情况

4.4.1 普通函数(不用)

        定义在类内部的普通函数,只能通过类来进行调用

In [23]: class Person: 
    ...:     def normal_function(): 
    ...:         print('normal_function') 
    ...:                                                                                                

In [24]: Person.normal_function()                                                                       
normal_function

In [25]: test = Person()                                                                                

In [27]: test.normal_function()                                                                         
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-27-9011f04ffcaa> in <module>
----> 1 test.normal_function()

TypeError: normal_function() takes 0 positional arguments but 1 was given

In [28]:   

注意:

  1. normal_function 定义在类里,只是受Person类管辖的一个普通函数而已,在Person中,认为normal_function仅仅是Person的一个属性而已.
  2. 当通过实例访问时,由于实例会默认把当前实例当作self传入函数,而函数并没有形参接受所以会报错。

虽然可以通过类调用,但是没人这样用,也就是说是禁止这么写的

4.4.2 类方法

        类方法是给类用的,在使用时会将类本身当做参数传给类方法的第一个参数,使用@classmethod装饰器来把类中的某个函数定义成类方法。

In [31]: class Person: 
    ...:     @classmethod 
    ...:     def class_method(cls): 
    ...:         print('class={0.__name__}({0})'.format(cls)) 
    ...:         cls.HEIGHT=170 
    ...:                                                                                                

In [32]: Person.class_method()                                                                          
class=Person(<class '__main__.Person'>)

In [35]: daxin = Person()                                                                               

In [36]: daxin.class_method()                                                                           
class=Person(<class '__main__.Person'>)

In [37]:

注意:

  1. 在类定义中,使用@classmethod装饰器的方法
  2. 必须至少有一个参数,且第一个参数留给了cls,cls指代调用者即类对象自身
  3. cls这个标识符可以是任意合法名称,但是为了易读,不建议修改
  4. 通过cls可以直接操作类的属性
  5. 通过类的实例依旧可以调用类方法,那是因为当通过实例调用时,@classmethod发现调用者是实例时,会把当前实例的__class__对象,传入给类方法的cls。

类方法类似于C++、Java中的静态方法。

4.4.3 静态方法(用的很少)

        静态方法是一种普通函数,位于类定义的命名空间中,不会对任何实例类型进行操作,它有如下特点

  1. 在类定义中,使用@classmethod装饰器修饰的方法,在调用时不会隐式的传入参数
  2. 静态方法,只表明这个方法属于这个名称空间,函数归在一起,方便组织管理。
  3. 可以理解为一个函数封装,可以通过类调用、也可以通过实例调用,只是强制的放在类中而已。
class Person:
    @staticmethod
    def static_method():
        print('static_method')


daxin = Person()
Person.static_method()
daxin.static_method()

和普通函数的区别是,静态方法可以使用实例调用,普通方法不可以。

4.4.4 方法的调用

        类几乎可以调用所有方法,普通函数的调用一般不可能出现,因为不允许这么定义。实例也几乎可以调用所有内部定义的方法,但是调用普通方法时会报错,原因是第一参数必须是类的实例self。

class Test:
    age = 10
    def normal_funtion():
        print('normal_function')

    @classmethod
    def class_method(cls):
        print(cls.__name__)

    @staticmethod
    def static_method():
        # Test.age
        print('static_method')

daxin = Test()
daxin.normal_funtion()   # 无法调用,因为通过实例调用时,实例会被当作第一个参数传给normal_funcion。
daxin.class_method()     # 可以调用,当@classmethod检测到是通过实例调用时,会把当前实例的__class__,当作cls传递,可以访问类属性,但无法访问实例属性
daxin.static_method()    # 可以调用,当@staticmethod检测到是通过实例调用时,会在当前实例的类中调用静态方法,无法访问类或实例的属性,但是可以访问全局变量(比如全局变量Test类)

Test.normal_funtion()    # 可以调用,类调用时不传递参数,刚好normal_function也不需要参数,无法访问类或实例的属性
Test.class_method()      # 可以调用,类方法,通过类调用时,类会被当作cls传递给函数,可以访问类属性,无法访问实例属性
Test.static_method()     # 可以调用,静态方法,等于定义在类内的函数,无法访问类或实例的属性,但是可以访问全局变量(比如全局变量Test类)

注意:

  1. 实例除了普通方法都可以调用,普通方法需要对象的实例作为第一参数。
  2. 类可以调用所有类中定义的方法(包括类方法、静态方法), 普通方法不用传递参数,静态方法,和类方法需要查找到实例的类。

体会:

class Test:
    age = 10
    def normal_funtion(abc):
        print('{}'.format(abc))

Test.normal_funtion(Test)
Test.normal_funtion(123)
a = Test()
a.normal_funtion()

        normal_function依旧是一个普通方法而已,加了参数发现类和实例都可以调用了,那是因为之前实例无法调用是因为实例调用时默认传递给函数作为第一个参数,那么我给普通函数加一个形参接受就可以了。没有场景这样使用,这里只做学习了解。

5 访问控制

        封装成类还可以控制什么属性可以让别人访问,什么方法不能让别人访问,这就叫做访问控制。

5.1 私有属性

使用 双下划线开头 的属性名,就是私有属性,现有如下例子:

class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age 

daxin = Person('daxin', 18)
print(daxin.age)

        我们可以在外面直接使用daxin.age来访问daxin的age属性,但是如果这个age是银行卡密码,不能让别人知道该怎么办呢,Python提供了一种私有属性来解决

class Person:
    def __init__(self, name, age):
        self.name = name
        self.__age = age 
        
daxin = Person('daxin', 20)
print(daxin.__age)

        使用__开头的属性被称为私有属性,这种属性在类的外部是无法直接进行访问的,前面所说__dict__中会存放类的属性信息,那么我们来看一下daxin实例的__dict__是怎么样的。

print(daxin.__dict__)

{'name': 'daxin', '_Person__age': 20}

根据上面结果可以得知:私有属性的本质上其实是属性改名,设置self.__age属性时,__age 变为了 _Person__age(_类名__属性名)

class Person:
    def __init__(self,name, age):
        self.name = name
        self.__age = age

daxin = Person('daxin',18)
daxin.__age = 100
print(daxin.__dict__) # {'name': 'daxin', '_Person__age': 18, '__age': 100}

        只有在类中定义的私有变量才会被改名,上面我们虽然指定了实例的变量__age,但由于是在类外定义的,所以它并不会变形,就真的产生了一个__age属性,而在类内定义的,由于变型了,所以不会覆盖。观察__dict__就可以看出结果。

        我们知道私有属性在定义时会被改名,并且知道改名后的属性名称,那么我们是否就可以修改了呢?

class Person:
    def __init__(self,name, age):
        self.name = name
        self.__age = age
    def get_age(self):
        return self.__age
daxin = Person('daxin',18)
daxin._Person__age = 100
print(daxin.get_age())   # 100

通过结果我们可以知道,只要知道了私有变量的名称,就可以直接从外部访问,并且修改它。但并不建议这么做!

Java等其他语言,比较严格,私有在外面是绝对访问不到的。

5.2 保护变量

        保护变量(protected),其实Python并不支持保护变量,是开发者自己的不成文的约定。那什么是保护变量呢?在变量前使用 一个下划线 的变量称为保护变量。

class Person:
    def __init__(self,name):
        self._name = name

daxin = Person('daxin')
print(daxin.__dict__)  # {'_name': 'daxin'}
print(daxin._name)   # daxin

注释:

  1. 保护变量不会被改变名称
  2. 外部依旧可以看到并且调用

如果看见这种变量,就如同私有变量,尽量不要直接使用

5.3 私有方法

        前面说了私有属性,那么私有方法和私有属性是相似的,使用单/双下划线开头的方法称为私有方法(不能已双下划线结尾)

class Person:
    def __init__(self,name):
        self._name = name

    def __age(self):
        return self._name

print(Person.__dict__)  # {'__module__': '__main__', '__init__': <function Person.__init__ at 0x00000228BCB22158>, '_Person__age': <function Person.__age at 0x00000228BCB221E0>, '__dict__': <attribute '__dict__' of 'Person' objects>, '__weakref__': <attribute '__weakref__' of 'Person' objects>, '__doc__': None}

通过上面__dict__的内容,我们发现私有方法和私有属性是相同的,都会被改名,所以知道了私有方法真正的名字,我们依旧可以在外部进行调用,但是不建议。

单下划线的方法,也和变量相同,解释器不会做任何变型,只是告诉你,它是一个私有方法,不建议直接使用。

5.4 补丁:(黑科技)

        可以通过修改或替换类的成员。使用者调用的方式没有改变,但是,类提供的功能可能已经改变了。(比如前面的类装饰器),通常称作猴子补丁(Monkey Patch)

        在运行时,对属性、方法、函数等进行动态替换。其目的往往是为了通过替换、修改来增强、扩展原有代码的能力。

class Person:

    def __init__(self, name):
        self.name = name

    def sing(self):
        print('{} is sing'.format(self.name))


def monkeypatch():    # 猴子补丁,用于动态修改Person中某个方法
    Person.sing = lambda self: print('hello world')    # 可以来自于不同包中的某个函数,这里只是使用lambda举例

monkeypatch()    # 执行后,Person.sing函数就被替换掉了

daxin = Person('daxin')
daxin.sing()   # 'hello world'

        一般情况下是不建议使用的,但是在某些场景下,比如我替换的方法原来是连接数据库获取数据的,反复连接数据库测试不是很方便,所以在这种情况下,我们使用补丁的方式,返回部分目标数据就好了,不用每次都去数据库取数据。

5.5 属性装饰器

        被属性装饰器装饰的方法,就变成了属性了。是不是很拗口?简单来说就是:属性装饰器把一个方法变成属性,进行调用。这么做的目的可以把某些属性保护起来,不让外部访问(通过前面私有属性的了解,我们知道这是不可能的,嘿嘿嘿)。它主要由下面三种组成,可以组合使用也可以单独使用。

  1. @property:标识下面的方法为属性方法,同时激活setter,deleter
  2. @方法.setter:设置属性时调用方法
  3. @方法.deleter:删除属性时调用方法

不使用属性装饰器时,我们为了隐藏某个属性可以使用如下方法:

class Person:

    def __init__(self, name, age=18):
        self.name = name
        self.__age = age

    def age(self):    # 一般称为getter方法
        return self.__age

    def setage(self, value):  # 一般称为setter方法
        self.__age = value

daxin = Person('daxin')
daxin.setage(30)
print(daxin.age())

        使用起来很别扭,为什么获取age属性要加括号执行方法呢?如何能让用户在使用age或者setage时不要认为他们是在调用函数,而是一个属性呢,下面使用属性装饰器property来完成这个需求

class Person:

    def __init__(self, name, age=18):
        self.name = name
        self.__age = age

    @property
    def age(self):    # 一般称为getter方法
        return self.__age
        
daxin = Person('daxin')
print(daxin.age)

        添加了@property装饰器以后,被他装饰的函数,就可以像普通的属性来访问了(daxin.age,就是daxin.age()的返回值),那是否可以使用daxin.age=100来设置age属性的值呢?

daxin.age = 100 
>> AttributeError: can't set attribute

        无法设置的,是因为property装饰的函数是只读的,如果要使用daxin.age = 100 来赋值时,还需要一个setter装饰器来装饰一个设置属性的函数,并且这个函数必须和property装饰的函数的名称相同。那如果要删除呢,自然就触发了deleter装饰器了,我们在方法中,删除属性即可。

class Person:

    def __init__(self, name, age=18):
        self.name = name
        self.__age = age
        
    @property
    def age(self):    # 一般称为getter方法
        return self.__age
    
    @age.setter    # 被装饰的函数.setter(设置一个属性的值时触发)
    def age(self,value):
        self.__age = value 
    
    @age.deleter   # 删除一个属性时触发(很不常用)
    def age(self):
        del self.__age

daxin = Person()
print(daxin.age) # 触发@property装饰过的age
daxin.age = 200  # 触发age.setter 
del daxin.age    # 触发age.deleter      

这样使用起来就比较方便了,当然property还提供了另一种写法property(getter,settr,deleter,'description'),因为property实际上是一个class类而已。

class Person:
    def __init__(self, name, age=18):
        self.name = name 
        self.__age = age 
    
    def getage(self):
        return self.__age 
        
    def setage(self, value):
        self.__age = value 
    def delage(self):
        del self.__age 
        
    age = property(getage,setage,delage,'age property')

例:快速实现一个只读属性

class Person:
    def __init__(self, name, age=18):
        self.name = name
        self.__age = age 
        
    age = property(lambda self:self.__age)  # 快捷包装一个只读属性。

因为lambda不支持等号,而setter中有赋值等号,所以lambda就无法实现这个需求了。

5.6 对象的销毁

        __init__ 用于在对象被实例化时为对象初始化一些属性信息,按照其他语言的逻辑,也可以称之为构造器,既然有构造器,那么就会有析构器,即在对象被销毁时执行。在Python的类中使用__del__方法定义的函数称为析构函数,在对象被销毁时触发执行。需要注意的是,这个方法不能引起对象本身的销毁,只是对象销毁的时候会自动调用它。换言之,就是当对象引用计数为0时,触发销毁操作(标记为可回收,等待GC),而销毁操作又会触发对象的__del__方法。

class Person:
    def __init__(self, name):
        self.name = name

    def __del__(self):
        print('{} is die'.format(self.name))

daxin = Person('daxin')
daxin.__del__()    # 仅仅是执行__del__方法,不会真正销毁
daxin.__del__()    # 仅仅是执行__del__方法,不会真正销毁
del daxin   # 在本例中引用计数为0,即真正的销毁


# 代码执行完毕后也会触发销毁操作。

一般在析构函数中清理当前实例中申请的内存空间或者某些对象,做一些资源释放的工作。

5.7 方法重载(overload)

        在其他面向对象的高级语言中,会有重载的改变。所谓重载,就是同一个方法名,但是参数数量、类型不一样,就是同一个方法的重载。

下面是一段模拟其他语言方法重载的伪代码

func test(x int, y int) {
    pass
}
    
func test(x array,y array) {
    pass 
}
    
test(4,5)
test([1],[2])

        在其他语言中,当test(4,5)时会调用上面的函数,当test([1],[2])会调用下面的函数,这就叫做类型重载,传递不同的参数类型,就会执行对应的方法,而在Python中,后面的会直接覆盖前面的同名函数,所以Python没有重载,当然Python也不需要重载,为什么呢?因为Python的动态语言的特性,其实悄悄的就实现了其他语言的类型重载

def test(x,y):
    return x + y 
    
test(4,5)
test([1],[2])
test('a','b')

        在Python中上面是可以执行的,但是在其他语言中,可能就需要对应三个函数处理不同类型的数据,然后通过类型重载来调用。由于Python语言的特性,天生就能实现类型重载的功能。

posted @ 2019-03-07 09:29  SpeicalLife  阅读(656)  评论(0编辑  收藏  举报