学习python(十一)——类和对象

目录

0.啥叫OOP

1.类的构成

(1)构造函数

(2)属性和方法

2.通过代码看概念(类的构成)

(1)代码演示

(2)代码解读

(3)额外扩展

3.property()函数

(1)描述符

(2)property()函数

4.封装机制


0.啥叫OOP

读者肯定听过 Python 中“一切皆对象”的说法,但可能并不了解它的具体含义,只是在学习的时候听说 Python 是面向对象的编程语言。面向对象编程是在面向过程编程的基础上发展来的,它比面向过程编程具有更强的灵活性和扩展性。面向对象编程是程序员发展的分水岭,很多初学者会因无法理解面向对象而放弃学习编程。类和对象是 Python 的重要特征,相比其它面向对象语言,Python 很容易就可以创建出一个类和对象。同时,Python 也支持面向对象的三大特征:封装、继承和多态。

面向对象编程(Object-oriented Programming,简称 OOP),是一种封装代码的方法。其实,在前面章节的学习中,我们已经接触了封装,比如说,将乱七八糟的数据扔进列表中,这就是一种简单的封装,是数据层面的封装;把常用的代码块打包成一个函数,这也是一种封装,是语句层面的封装。

代码封装,其实就是隐藏实现功能的具体代码,仅留给用户使用的接口,就好像使用计算机,用户只需要使用键盘、鼠标就可以实现一些功能,而根本不需要知道其内部是如何工作的。

面向对象中,常用术语包括:

  • 类:可以理解是一个模板,通过它可以创建出无数个具体实例。比如,前面编写的 tortoise 表示的只是乌龟这个物种,通过它可以创建出无数个实例来代表各种不同特征的乌龟(这一过程又称为类的实例化)。
  • 对象:类并不能直接使用,通过类创建出的实例(又称对象)才能使用。这有点像汽车图纸和汽车的关系,图纸本身(类)并不能为人们使用,通过图纸创建出的一辆辆车(对象)才能使用。
  • 属性:类中的所有变量称为属性。例如,tortoise 这个类中,bodyColor、footNum、weight、hasShell 都是这个类拥有的属性。
  • 方法:类中的所有函数通常称为方法。不过,和函数所有不同的是,类方法至少要包含一个 self 参数(后续会做详细介绍)。例如,tortoise 类中,crawl()、eat()、sleep()、protect() 都是这个类所拥有的方法,类方法无法单独使用,只能和类的对象一起使用。

1.类的构成

Python 中定义一个类使用 class 关键字实现,其基本语法格式如下:

class 类名:
    多个(≥0)类属性...
    多个(≥0)类方法...

注意,无论是类属性还是类方法,对于类来说,它们都不是必需的,可以有也可以没有。另外,Python 类中属性和方法所在的位置是任意的,即它们之间并没有固定的前后次序。给类起好名字之后,其后要跟有冒号(:),表示告诉 Python 解释器,下面要开始设计类的内部功能了,也就是编写类属性和类方法。其实,类属性指的就是包含在类中的变量;而类方法指的是包含类中的函数。换句话说,类属性和类方法其实分别是包含类中的变量和函数的别称。需要注意的一点是,同属一个类的所有类属性和类方法,要保持统一的缩进格式,通常统一缩进 4 个空格。Python 类是由类头(class 类名)和类体(统一缩进的变量和函数)构成。如果一个类没有任何类属性和类方法,那么可以直接用 pass 关键字作为类体即可。但在实际应用中,很少会创建空类,因为空类没有任何实际意义。

(1)构造函数

在创建类时,我们可以手动添加一个 __init__() 方法,该方法是一个特殊的类实例方法,称为构造方法(或构造函数)。构造方法用于创建对象时使用,每当创建一个类的实例对象时,Python 解释器都会自动调用它。Python 类中,手动添加构造方法的语法格式如下:

def __init__(self,...):
    代码块

注意,此方法的方法名中,开头和结尾各有 2 个下划线,且中间不能有空格。Python 中很多这种以双下划线开头、双下划线结尾的方法,都具有特殊的意义。另外,__init__() 方法可以包含多个参数,但必须包含一个名为 self 的参数,且必须作为第一个参数。也就是说,类的构造方法最少也要有一个 self 参数。

注意,即便不手动为类添加任何构造方法,Python 也会自动为类添加一个仅包含 self 参数的构造方法。仅包含 self 参数的 __init__() 构造方法,又称为类的默认构造方法。self 参数是特殊参数,不需要手动传值,Python 会自动传给它值。Python 只是规定,无论是构造方法还是实例方法,最少要包含一个参数,并没有规定该参数的具体名称。之所以将其命名为 self,只是程序员之间约定俗成的一种习惯,遵守这个约定,可以使我们编写的代码具有更好的可读性。Python 类方法中的 self 参数就相当于 C++ 中的 this 指针。 self 代表该方法的调用者,即谁调用该方法,那么 self 就代表谁。

(2)属性和方法

使用已创建好的类对象访问类中实例变量的语法格式如下:

类对象名.变量名

使用类对象调用类中方法的语法格式如下:

对象名.方法名(参数)

注意,对象名和变量名以及方法名之间用点 "." 连接。

无论是类属性还是类方法,都无法像普通变量或者函数那样,在类的外部直接使用它们。我们可以将类看做一个独立的空间,则类属性其实就是在类体中定义的变量,类方法是在类体中定义的函数。类变量指的是在类中,但在各个类方法外定义的变量。类变量的特点是,所有类的实例化对象都同时共享类变量,也就是说,类变量在所有实例化对象中是作为公用资源存在的。类方法的调用方式有 2 种,既可以使用类名直接调用,也可以使用类的实例化对象调用。实例变量指的是在任意类方法内部,以“self.变量名”的方式定义的变量,其特点是只作用于调用方法的对象。另外,实例变量只能通过对象名访问,无法通过类名访问。除了实例变量,类方法中还可以定义局部变量。和前者不同,局部变量直接以“变量名=值”的方式进行定义。通常情况下,定义局部变量是为了所在类方法功能的实现。需要注意的一点是,局部变量只能用于所在函数中,函数执行完成后,局部变量也会被销毁。

和类属性一样,类方法也可以进行更细致的划分,具体可分为类方法、实例方法和静态方法。通常情况下,在类中定义的方法默认都是实例方法。实例方法最大的特点就是,它最少也要包含一个 self 参数,用于绑定调用此方法的实例对象(Python 会自动完成绑定)。实例方法通常会用类对象直接调用。Python 类方法和实例方法相似,它最少也要包含一个参数,只不过类方法中通常将其命名为 cls,Python 会自动将类本身绑定给 cls 参数(注意,绑定的不是类对象)。也就是说,我们在调用类方法时,无需显式为 cls 参数传参。和 self 一样,cls 参数的命名也不是规定的(可以随意命名),只是 Python 程序员约定俗称的习惯而已。和实例方法最大的不同在于,类方法需要使用@classmethod修饰符进行修饰,静态方法,其实就是我们学过的函数,和函数唯一的区别是,静态方法定义在类这个空间(类命名空间)中,而函数则定义在程序所在的空间(全局命名空间)中。静态方法没有类似 self、cls 这样的特殊参数,因此 Python 解释器不会对它包含的参数做任何类或对象的绑定。也正因为如此,类的静态方法中无法调用任何类属性和类方法。静态方法需要使用@staticmethod修饰。

2.通过代码看概念(类的构成)

确实,我第一次看到这些的时候,我心里只有一个想法:这些东西TMD什么鬼……感觉不太好理解,为此,菜鸡专门写了一段代码进行学习……

(1)代码演示

下面用python来写一个复数类:

# coding=gbk
class complex:##定义一个复数类
    imag = 0j
    real = 0
    
    def __init__(complex,imag,real):##构造函数,实例方法
        complex.imag = imag
        complex.real = real
       
    @classmethod ##类方法
    def Math1(complex):
        complex.step = 1j##实例变量
        sacle = 2##局部变量
        return complex.imag+complex.step+complex.real*sacle
    
    def Math2(complex):##实例方法
        complex.step = 1j##实例变量
        sacle = 2##局部变量
        return complex.imag+complex.step+complex.real*sacle

    
    @staticmethod
    def Math3():
        complex.step = 1j##实例变量
        sacle = 2##局部变量
        return complex.imag+complex.step+complex.real*sacle

    @staticmethod
    def Math4(complex):
        complex.step = 1j##实例变量
        sacle = 2##局部变量
        return complex.imag+complex.step+complex.real*sacle

    def Printf(self):
        print("这是一个python自定义的复数类")

test = complex(3j,4)
test.Printf()
ans1 = test.Math1()
print(ans1)
ans2 = test.Math2()
print(ans2)
ans3 = test.Math3()
print(ans3)
ans4 = test.Math4(test)
print(ans4)

代码输出结果如下:

(2)代码解读

首先,通过构造函数实例化了一个叫test的complex类,实部为4,虚部为3,而math1作为类方法,其调用的imag和real是类属性,即0+0j,因此返回值为1j;而math2是实例方法,调用的imag和real是实例化时候构造函数的赋值,因此,返回值为8+4j。也就是说,math2无法对类属性imag和real进行取值。

而且。每调用一次类方法需要在该函数前加@classmethod,也就是关键字只是下一个函数的作用域。

而静态函数,调用者可以是类,也可以是对象,没有自动传参的效果。当内部无参数的时候,调用的类属性;当函数参数为实例化的自定义类时候,调用的是实例化参数。绑定到类的方法就是专门给类用的,但其实对象也可以调用,只不过自动传入的第一个参数仍然是类,也就是说这种调用是没有意义的, 并且容易引起混淆,

(3)额外扩展

菜鸡的学弟在菜鸡之前学过python,所以菜鸡去找我学弟讨论了这个问题。而我学弟的答复为我引出了一个新的概念“绑定方法”,为此,菜鸡专门去看了一下这个东西,下面给出菜鸡查阅的参考链接:

https://www.cnblogs.com/liunaixu/p/12879302.html(要喷别喷我,喷他……)

也就是说,在python3中的无绑定方法之静态方法,即静态方法就是函数。

3.property()函数

(1)描述符

Python 中,通过使用描述符,可以让程序员在引用一个对象属性时自定义要完成的工作。本质上看,描述符就是一个类,只不过它定义了另一个类中属性的访问方式。换句话说,一个类可以将属性管理全权委托给描述符类。描述符是 Python 中复杂属性访问的基础,它在内部被用于实现 property、方法、类方法、静态方法和 super 类型。

描述符类基于以下 3 个特殊方法,换句话说,这 3 个方法组成了描述符协议:

  • __set__(self, obj, type=None):在设置属性时将调用这一方法
  • __get__(self, obj, value):在读取属性时将调用这一方法
  • __delete__(self, obj):对属性调用 del 时将调用这一方法。

其中,实现了 set和 get方法的描述符类被称为数据描述符;反之,如果只实现了 get 方法,则称为非数据描述符。

实际上,在每次查找属性时,描述符协议中的方法都由类对象的特殊方法 __getattribute__() 调用(注意不要和 __get__() 弄混)。也就是说,每次使用类对象.属性(或者 getattr(类对象,属性值))的调用方式时,都会隐式地调用 __getattribute__(),它会按照下列顺序查找该属性:

  1. 验证该属性是否为类实例对象的数据描述符;
  2. 如果不是,就查看该属性是否能在类实例对象的 __dict__ 中找到;

最后,查看该属性是否为类实例对象的非数据描述符。

(2)property()函数

我们一直在用“类对象.属性”的方式访问类中定义的属性,其实这种做法是欠妥的,因为它破坏了类的封装原则。正常情况下,类包含的属性应该是隐藏的,只允许通过类提供的方法来间接实现对类属性的访问和操作。因此,在不破坏类封装原则的基础上,为了能够有效操作类中的属性,类中应包含读(或写)类属性的多个 getter(或 setter)方法,这样就可以通过“类对象.方法(参数)”的方式操作属性。庆幸的是,Python 中提供了 property() 函数,可以实现在不破坏类封装原则的前提下,让开发者依旧使用“类对象.属性”的方式操作类中的属性。

property() 函数的基本使用格式如下:

属性名=property(fget=None, fset=None, fdel=None, doc=None)

其中,fget 参数用于指定获取该属性值的类方法,fset 参数用于指定设置该属性值的方法,fdel 参数用于指定删除该属性值的方法,最后的 doc 是一个文档字符串,用于说明此函数的作用。

PS:作为一个C++为主的程序员,我是用不惯这个东西的!

4.封装机制

简单的理解封装(Encapsulation),即在设计类时,刻意地将一些属性和方法隐藏在类的内部,这样在使用此类时,将无法直接以“类对象.属性名”(或者“类对象.方法名(参数)”)的形式调用这些属性(或方法),而只能用未隐藏的类方法间接操作这些隐藏的属性和方法。首先,封装机制保证了类内部数据结构的完整性,因为使用类的用户无法直接看到类中的数据结构,只能使用类允许公开的数据,很好地避免了外部对内部数据的影响,提高了程序的可维护性。除此之外,对一个类实现良好的封装,用户只能借助暴露出来的类方法来访问数据,我们只需要在这些暴露的方法中加入适当的控制逻辑,即可轻松实现用户对类中属性或方法的不合理操作。并且,对类进行良好的封装,还可以提高代码的复用性。

和其它面向对象的编程语言(如 C++、Java)不同,Python 类中的变量和函数,不是公有的(类似 public 属性),就是私有的(类似 private),这 2 种属性的区别如下:

  • public:公有属性的类变量和类函数,在类的外部、类内部以及子类(后续讲继承特性时会做详细介绍)中,都可以正常访问;
  • private:私有属性的类变量和类函数,只能在本类内部使用,类的外部以及子类都无法使用。

但是,Python 并没有提供 public、private 这些修饰符。为了实现类的封装,Python 采取了下面的方法:

  • 默认情况下,Python 类中的变量和方法都是公有(public)的,它们的名称前都没有下划线(_);
  • 如果类中的变量和函数,其名称以双下划线“__”开头,则该变量(函数)为私有变量(私有函数),其属性等同于 private。

除此之外,还可以定义以单下划线“_”开头的类属性或者类方法(例如 _name、_display(self)),这种类属性和类方法通常被视为私有属性和私有方法,虽然它们也能通过类对象正常访问,但这是一种约定俗称的用法,初学者一定要遵守。注意,Python 类中还有以双下划线开头和结尾的类方法(例如类的构造函数__init__(self)),这些都是 Python 内部定义的,用于 Python 内部调用。我们自己定义类属性或者类方法时,不要使用这种格式。

猜你喜欢

转载自blog.csdn.net/qq_35789421/article/details/113572395