当前位置:   article > 正文

【python基础知识学习(6)】面向对象编程_python人狗大csdn

python人狗大csdn

一、面向对象编程 VS 面向过程编程

(1)面向过程:根据业务逻辑从上到下写代码
(2)面向对象:将数据与函数绑定到一起,分类进行封装,每个程序员只要负责分配给自己的分类,这样能够更快速的开发程序,减少了重复代码。用面向对象的思维解决问题的重点是当遇到一个需求的时候,不用自己去实现。pytorch就是一种深度学习中面向对象编程的思想。我们甚至不需要知道深度学习中常见的诸如卷积算法等操作如何用代码编写,因为pytorch深度学习框架已经把这些基础功能封装成了函数,想使用的时候直接调用函数名字即可。面向对象编程的主要思想在于分发任务:一个好老板不需要知道如何混合水泥,如何冶炼钢铁照样可以盖起一片楼盘。

二、类与对象

(1)前言

一个好的开发商老板可以通过分发任务的方式盖起一篇楼盘,可是分发下去的任务怎么实现?换言之,水泥厂,钢筋厂这些实现任务的功能组从哪里来?
实现方法有两个:一个是借鉴,一个是自己造。具体来说,借鉴是指导入一些编程大神之前写好的项目,即如果你想实现的功能已经有人实现过了,那么你可以通过导入他的代码来直接实现这个功能。类比到现实就是一些已经存在的‘水泥厂’,‘钢筋厂’,我们只需要他们的地址,就可以去运输水泥和钢筋了。至于自己造,指的是从0到1搭建一个‘工厂’,这其中有两个非常重要的概念就是类和对象,它们各自的作用是设计框架和封装功能。
注意:在面向对象编程中有三个至关重要的思想,分别是封装,继承和多态。封装中的主要概念就是类和对象了。 类(class)和对象(object)是两种以计算机为载体的计算机语言的合称。以下解释常见的专业名称含义:
(1)类:类是一个抽象的概念,在现实世界中不存在。类本质上是在现实世界中具有共同特征的事物,通过提取这些共同特征形成的概念叫做“类”,比如人类、动物类、汽车类、鸟类等。 类其实就是一个模板,类中描述的是所有对象的“共同特征”。

(2)对象:对象是一个具体的概念,在现实世界中真实存在的。比如李白、爱迪生、贝克汉姆都是对象,他们都属于人类。对象就是通过类创建出的真实的个体

(3)抽象:将具有相同特征的对象抽取共同特征的过程叫做“抽象”。

(4)实例化:对象还有一个别名叫做“实例”,通过类创建对象的过程叫做“实例化”

(2)类的创建

我们都知道类是具有相同的特征的对象的抽象,而特征包含静态的和动态的,分别对应着类的属性和方法。例如,鸟类的静态特征是长着一双翅膀,动态特征是会飞。狗类的静态特征是嗅觉灵敏,动态特征是会“汪汪”叫等等。因此,再加上类的名字,这三个成分就组成的类的概念。
举例:

1)人类设计,只关心3样东西:
事物名称(类名):人(Person)
属性:身高(height)、年龄(age)等等
方法:(行为/功能):跑(run)、打架(fight)等等

2)狗类的设计
类名:狗(Dog)
属性:品种 、毛色、性别、名字、 腿儿的数量等等
方法:(行为/功能):叫 、跑、咬人、吃、摇尾巴等等

(3)代码的方式定义类和对象

一般,使用 class 语句来创建一个新类,class之后为类的名称(通常首字母大写)并以冒号结尾,例如:

class Car:
    # 方法
    def getCarInfo(self):
        print('车轮子个数:%d, 颜色%s'%(self.wheelNum, self.color))

    def move(self):
        print("车正在移动...")
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

在类中,可以定义所使用的方法,类中的方法普通的函数只有一个特别的区别——它们必须有一个额外的第一个参数名称, 按照惯例它的名称是 self。 它表示类创建的实例本身,指向当前创建对象的内存地址。某个对象调用其方法时,python解释器会把这个对象作为第一个参数传递给self,所以开发者只需要传递后面的参数即可。

假设我们由‘商品房’这个类实例化出对象a 和 对象b, 可以理解成 客户a 和 客户b,。这时候客户a和客户b都买了同一个设计图(同一个类)盖出的房子,但是他们想要自己的装修风格,这些装修风格就是对象a、对象b的特征。当装修队开始装修房子时,要根据不同风格进行装修。那么,如何选择装修风格呢?很简单,根据客户选择!所以,self其中代指的就是客户,也就是实例本身。

所以,具体的实例化过程是这样的,当使用‘商品房’这个类实例化出对象a 时,默认参数self会把对象a创建在某一个内存地址中;同理,在实例化出对象b 时,默认参数self会把对象b创建在另一个与a不同的内存地址中。就如同客户a和客户b买商品房不可能买的是同一套房子一样,self的制作就如同售楼处,可以让客户买到不同地址的房子,而且还会管理这些房号,让客户在装修时知道去几零几号装修。简而言之,可以把self理解成类在实例化对象时的管理工具。

刚才的代码中,我们定义了一个Car类; 就好比有车一个张图纸,那么接下来就应该把图纸交给生产工人们去生产了。python中,可以根据已经定义的类去创建出一个个对象,创建对象的格式为: 对象名 = 类名()

# 定义类
class Car:
    # 定义移动方法
    def move(self):
        print('车在行驶...')

    # 定义鸣笛方法
    def toot(self):
        print("车在嘟嘟..")
 
# 创建一个对象,并用变量Wuling来保存它的引用
Wuling = Car()
Wuling.color = 'white' #使用‘.’符号的方法添加类属性:车的颜色
Wuling.wheelNum = 4 #使用‘.’符号的方法添加类属性:轮子数量
Wuling.move() #使用‘.函数名()’的语法调用类中函数:车的行驶

print(Wuling.color) #打印实例五菱的颜色属性
print(Wuling.wheelNum) #打印实例五菱的车轮数量属性
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

在上个实例中,我们给五菱神车添加了两个对象属性:wheelNum和color,试想一下如果再次创建一个对象的话,创建之后需要重新进行属性添加,这样做是比较麻烦的。是否可以在创建对象时,就可以顺便把属性给与呢?

这就是__init__()函数的作用。使用方法如下实例所示:

# 定义汽车类
class Car:
    # 初始化函数,对象属性有默认值:4和white;也可以通过传参的方法对对象属性进行重新赋值
    def __init__(self, wheelNum=4, color='white'):
        self.wheelNum = wheelNum
        self.color = color
    # 定义类方法
    def move(self):
        print('车在行驶')

# 创建对象五菱神车,不传参时,属性使用默认值
Wuling = Car() 
print('五菱车的颜色为:',  Wuling.color)
print('五菱车轮胎数量为:', Wuling.wheelNum)

# 创建对象自行车,传参时,新的传参值代替默认值
Bicycle = Car(2, 'black')
print('自行车的颜色为:',  Bicycle.color)
print('自行车轮胎数量为:', Bicycle.wheelNum)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

注意
(1)当创建Wuling对象后,在没有调用方法的前提下,Wuling就默认拥有了2个属性wheelNum和color,原因是方法是在创建对象后,就立刻被默认调用了__init__() 函数,不需要手动调动。
(2)init(self)中,默认有1个参数名字为self,如果在创建对象时传递了2个实参,那么除了self作为第一个形参外还需要2个形参,例如 init(self, x, y)
(3)init(self)中的self参数,不需要开发者传递,python解释器会自动把当前的对象引用传递进去。

三、魔法方法

魔法方法是在Python的类中被双下划线前后包围的方法,如常见的 :__init__、__new__、__del__等。
这些方法在类或对象进行特定的操作时会自动被调用,我们可以使用或重写这些魔法方法,给自定义的类添加各种特殊的功能来满足自己的需求。
上一小节已经介绍过初始化魔法方法__init__,也是在定义类时最常见的魔法方法。除此之外,还有一些常见的魔法方法如下。

构造类的魔法方法
我们都知道一个最基本的魔术方法, __init__ 。
通过此方法我们可以定义一个对象的初始操作。
但你知道吗,当实例化我们定义的类,如x = SomeClass() 的时候, __init__ 并不是第一个被调用的方法。
实际上,还有一个叫做 __new__ 的方法,来实例化这个对象。然后给在开始创建时候的初始化函数来传递参数。
在对象生命周期的另一端,也有一个__del__ 方法。接下来看一看这三个方法:

(1)__new__(cls, […]) 是在一个对象实例化的时候所调用的第一个方法,所以它才是真正意义上的构造方法。
它的第一个参数是这个类,其他的参数是用来直接传递给__init__ 方法。

__new__ 决定是否要使用该 __init__ 方法,
因为 __new__ 可以调用其他类的构造方法或者直接返回别的实例对象来作为本类的实例,
如果__new__ 没有返回实例对象,则__init__ 不会被调用。
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

举例:

class Person(object):

   def __new__(cls, *args, **kwargs):
       print("__new__()方法被调用了")
       print('这个是*agrs', *args)
       print('这个是kwagrs', **kwargs)
       
       # cls表示这个类,剩余所有的参数传给__init__()方法,
       # 若不返回,则__init__()不会被调用
       return object.__new__(cls)

   def __init__(self, name, age):
       print("__init__()方法被调用了")
       self.name = name
       self.age = age
       print(self.name, self.age)

p = Person("张三", 20)

# Output:
# __new__()方法被调用了
# 这个是*agrs 张三 20
# 这个是kwagrs
# __init__()方法被调用了
# 张三 20
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
那__new__()在什么场景使用呢? 当我们需要继承内置类时,
例如,想要继承 int、str、tuple,就无法使用__init__ 来初始化了,只能通过__new__ 来初始化数据。
实际上,在日常的编写中,__new__()和__del__()这两个魔法函数一般都不常见,保持默认就好,经常编写的是__init__()函数。
下面对__del__()函数做举例说明:
  • 1
  • 2
  • 3
  • 4
class Washer:
    # 析构器,当一个实例被销毁时自动调用的方法。
   def __del__(self):
       """
       当删除对象时,解释器会自动调用del方法
       """
       print('对象已删除!')

haier = Washer() 
# output:
# 对象已删除!
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
表示类的魔法方法(重要)
关于类的表示相关的魔法方法,主要包括__str__()和__repr__()两种。
这两个方法都是用来描述类或对象信息的,比如你直接实例化了一个对象,打印出来的是这个对象的地址。
而要是重新在类中定义了这两个方法,那打印对象的结果就是方法返回的信息。
  • 1
  • 2
  • 3
  • 4

举例:当直接实例化了一个对象,打印出来的是这个对象的地址。

class Washer:
   def __int__(self):
       pass

haier = Washer() 
print(haier)

# output
# <__main__.Washer object at 0x00000266AFF21550>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
如果重新在类中定义了__str__()和__repr__()这两个方法,
那打印对象的结果就是方法返回的信息。
  • 1
  • 2
class Washer:
   def __int__(self):
       pass

   def __repr__(self):
       return '我是__repr__()魔法方法!'

   def __str__(self):
       """
       这个str的作用就是:类的说明或对象状态的说明
       :return:
       """
       return '我是__str__魔法方法!'

haier = Washer()
# 不定义str方法,直接打印,结果是对象的内存地址,定义了str方法,
# 显示的就是str方法返回的内容
print(haier)  

# output
# 我是__str__魔法方法
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
可以发现,要是同时写了这两个方法,只会调用__str__方法。
都是用来描述类或对象的信息,那为啥要定义两个呢? 
设计的目的是不一样的: 
 1. __repr__的目标是准确性,或者说,__repr__的结果是让解释器用的。
 2. __str__的目标是可读性,或者说,__str__的结果是让人看的。
 3. 一般的,也可以只定义__str__方法而不定义__repr__方法。
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

访问控制类魔法方法

关于访问控制的魔法方法,主要包括以下几种:

setattr:定义当一个属性被设置时的行为

getattr:定义当用户试图获取一个不存在的属性时的行为

delattr:删除某个属性时调用

getattribute:访问任意属性或方法时调用

class Person(object):

    def __setattr__(self, key, value):
        """属性赋值"""
        if key not in ('name', 'age'):
            return
        if key == 'age' and value < 0:
            raise ValueError()
        super(Person, self).__setattr__(key, value)

    def __getattr__(self, key):
        """访问某个不存在的属性"""
        return 'unknown'

    def __delattr__(self, key):
        """删除某个属性"""
        if key == 'name':
            raise AttributeError()
        super().__delattr__(key)

    def __getattribute__(self, key):
        """所有属性/方法调用都经过这里"""
        if key == 'money':
            return 100
        elif key == 'hello':
            return self.say
        return super().__getattribute__(key)

p1 = Person()
p1.name = '张三'  # 调用__setattr__
p1.age = 20  # 调用__setattr__
print(p1.name, p1.age)  # 张三 20

setattr(p1, 'name', '李四')	# 调用__setattr__
setattr(p1, 'age', 30)  # 调用__setattr__
print(p1.name, p1.age)  # 李四 30

print(p1.sex)  # 调用__getattr__

# 上面只要是访问属性的地方,都会调用__getattribute__方法
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40

比较操作类魔法方法

比较操作的魔法方法主要包括以下几种:

__eq__() 可以判断两个对象是否相等。

__ne__() 判断两个对象是否不相等,这个和__eq__()方法基本一样,只不过这个是反面。

__lt__() 比较对象的大小的,__lt__()为小于。

__gt__() 比较对象的大小的,__gt__()为大于。
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
class Person(object):
    def __init__(self, uid):
        self.uid = uid

    def __eq__(self, other):
        return self.uid == other.uid
    
    def __ne__(self, other):
        """对象 != 判断"""
        return self.uid != other.uid

    def __lt__(self, other):
        """对象 < 判断 根据self.uid"""
        return self.uid < other

    def __gt__(self, other):
        """对象 > 判断 根据self.uid"""
        return self.uid > other
    
p1 = Person(1)
p2 = Person(1)
p3 = Person(2)
 
print(p1 == p2) # True
print(p2 == p3) # False

 
print(p1 != p2) # False
print(p2 != p3) # True

 
print(p1 < p2) # False
print(p2 < p3) # True

print(p1 > p2) # False
print(p2 > p3) # False
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36

容器类的魔法方法(重要)

容器类的魔法方法,主要包括:

__setitem__(self, key, value):定义设置容器中指定元素的操作 ,相当于 self[key] = value;

__getitem__(self, key): 定义获取容器中指定元素的操作 ,相当于 self[key];

__delitem__(self, key):定义删除容器中指定元素的操作 ,相当于 del self[key];

__len__(self):定义当被 len() 调用时的操作(返回容器中元素的个数);

__iter__(self):定义迭代容器中的元素的操作;

__contains__(self, item):定义当使用成员测试运算符(in 或 not in)时的操作;

__reversed__(self):定义当被 reversed() 调用时的操作。
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

在介绍容器的魔法方法之前,首先要知道,Python 中的容器类型都有哪些,Python 中常见的容器类型有:

字典
元组
列表
字符串
因为它们都是「可迭代」的。可迭代是因为,它们都实现了容器协议,也就是下面要介绍到的魔法方法。下面通过自己定义类实现列表,来说明这些方法的用法:

class MyList(object):
    """自己实现一个list"""

    def __init__(self, values=None):
        # 初始化自定义list
        self.values = values or []
        self._index = 0

    def __setitem__(self, idx, value):
        # 添加元素
        self.values[idx] = value

    def __getitem__(self, idx):
        # 获取元素
        return self.values[idx]

    def __delitem__(self, idx):
        # 删除元素
        del self.values[idx]

    def __len__(self):
        # 自定义list的元素个数
        return len(self.values)

    def __iter__(self):
        # 可迭代
        return self

    def __next__(self):
        # 迭代的具体细节
        # 如果__iter__返回self 则必须实现此方法
        if self._index >= len(self.values):
            raise StopIteration()
        value = self.values[self._index]
        self._index += 1
        return value

    def __contains__(self, idx):
        # 元素是否在自定义list中
        return idx in self.values

    def __reversed__(self):
        # 反转
        return list(reversed(self.values))

# 初始化自定义list
my_list = MyList([1, 2, 3, 4, 5])

print(my_list[0])	     # __getitem__
my_list[1] = 20		     # __setitem__

print(1 in my_list)	     # __contains__
print(len(my_list))     # __len__

print([i for i in my_list])  # __iter__
del my_list[0]	             # __del__

reversed_list = reversed(my_list) # __reversed__
print([i for i in reversed_list])  # __iter__


# output
# 1
# True
# 5
# [1, 20, 3, 4, 5]
# [5, 4, 3, 20]
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
这个例子实现了一个 MyList 类,在这个类中,定义了很多容器类的魔法方法。
这样一来,这个 MyList 类就可以像操作普通 list 一样,通过切片的方式添加、获取、删除、迭代元素了。具体魔法函数在实例中的解释如下:
__setitem__():当执行 my_list[1] = 20 时,就会调用__setitem__ 方法,这个方法主要用于向容器内添加元素。

__getitem__():当执行 my_list[0] 时,就会调用__getitem__ 方法,这个方法主要用于从容器中读取元素。

__delitem__():当执行 del my_list[0] 时,就会调用__delitem__ 方法,这个方法主要用于从容器中删除元素。

__len__():当执行 len(my_list) 时,就会调用__len__ 方法,这个方法主要用于读取容器内元素的数量。

__iter__这个方法需要重点关注,为什么我们可以执行 [i for i in my_list]?就是因为定义了__iter__。 这个方法的返回值可以有两种:

(1)返回 iter(obj):代表使用 obj 对象的迭代协议,一般 obj 是内置的容器对象;
(2)返回 self:代表迭代的逻辑由本类来实现,此时需要重写 next 方法,实现自定义的迭代逻辑
在这个例子中,__iter__ 返回的是 self,所以需要定义__next__ 方法,实现自己的迭代细节。 
__next__ 方法使用一个索引变量,用于记录当前迭代的位置,这个方法每次被调用时,都会返回一个元素,当所有元素都迭代完成后,此时 for 会停止迭代,若迭代时下标超出边界,这个方法会返回 StopIteration 异常。
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

可调用对象类魔法方法(重要)

在Python中,方法也是一种高等的对象。这意味着他们也可以像其他对象一样被传递到方法中,这是一个非常惊人的特性。 Python中有一个特殊的魔法方法可以让类的实例的行为表现的像函数一样,你可以调用他们,将一个函数当做一个参数传到另外一个函数中等等。
这个魔法方法就是__call__(self, [args…])。如下实例

class Circle(object):

    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __call__(self, x, y):
        self.x = x
        self.y = y

a = Circle(10, 20)	 # __init__
print(a.x, a.y)	# 10 20

a(100, 200)	# 此时a这个对象可以当做一个方法来执行,这是__call__魔法方法的功劳
print(a.x, a.y)	 # 100 200
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
这个例子首先初始化一个 Circle 实例 a,此时会调用__init__ 方法,这个很好理解。
但是,我们对于实例 a 又做了调用 a(100, 200),
注意,此时的 a 是一个实例对象,当我们这样执行时,其实它调用的就是__call__。这样一来,我们就可以把实例当做一个方法来执行。

也就是说,Python 中的实例,也是可以被调用的,通过定义__call__ 方法,
就可以传入自定义参数实现自己的逻辑。
这个魔法方法通常会用在类实现一个装饰器、元类等场景中,当遇到这个魔法方法时,能理解其中的原理就可以了。
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

四、类属性和类方法

(1)类属性,实例属性

在了解了类基本的东西之后,下面看一下类属性实例属性这两个概念。

类属性顾名思义就是类所拥有的属性,分为共有属性私有属性私有属性通过__属性名称的方法进行定义。对于公有的类属性,可以在类外进行访问,私有属性则不可以,实例如下:

class People(object):
    name = 'Arwin'  #公有的类属性
    __age = 24     #私有的类属性
    def __init__(self):
        pass
p = People()

print(p.name)           #正确
print(People.name)      #正确
print(p.__age)            #错误,不能在类外通过实例对象访问私有的类属性
print(People.__age)        #错误,不能在类外通过类对象访问私有的类属性
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
注意:类属性是声明在类的内部,实例方法的外部的属性,即在class内,__init__(self)方法之前。
  • 1

实例属性是从属于实例对象的属性,也称为“实例变量”。他的使用有如下几个要点:

(1)实例属性一般在__init__()方法中通过如下代码定义:
self.实例属性名 = 初始值

(2)在本类的其他实例方法中,也是通过self进行访问:
self.实例属性名

(3)创建实例对象后,可通过实例对象访问, obj = 类名() 创建和初始化对象,调用__init__()初始化属性 ,obj.实例属性名 = 值 可以给已有属性赋值,也可以新加属性

(4)实例属性可修改、新增、删除。

需要注意的是,如果在类外修改类属性,必须通过类对象去引用然后进行修改
如果通过实例对象去引用修改,会产生一个同名的实例属性,这种方式修改的是实例属性,不会影响到类属性。当类属性与实例属性同名时,一个实例访问这个属性时实例属性会覆盖类属性,但类访问时不会。举例如下:

class People(object):
    country = 'china' #类属性

# 访问类属性
print(People.country)
# 实例化对象
p = People()
# 访问实例属性
print(p.country)
# 修改实例属性
p.country = 'japan'
# 访问实例属性,实例属性会屏蔽掉同名的类属性
print(p.country)   
# 访问类属性,会发现没有改变
print(People.country)
 #通过类对象去引用修改类属性
People.country = "UK"   
# 访问类属性
print(People.country)

# output
#china
#china
#japan
#china
#UK
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

什么是类对象?下面代码演示说明。

class MyClass:
        name = 'FishC'
        def myFun(self):
                print("Hello FishC!")
                
MyClass.name
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

运行结果:
在这里插入图片描述

class MyClass:
        name = 'FishC'
        def myFun(self):
                print("Hello FishC!")
                
#MyClass.name
MyClass.myFun()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

运行结果:
在这里插入图片描述
问:上面代码为什么会报错?

答:首先要明白类、类对象、实例对象是三个不同的名词。

我们常说的类指的是类定义,由于“Python无处不对象”,所以当类定义完之后,自然就是类对象。在这个时候,你可以对类的属性(变量)进行直接访问(MyClass.name)。

一个类可以实例化出无数的对象(实例对象),Python 为了区分是哪个实例对象调用了方法,于是要求方法必须绑定(通过 self 参数)才能调用。而未实例化的类对象直接调用方法,因为缺少 self 参数,所以就会报错。

实例方法,类方法和静态方法

(1)实例方法

之前的例子中,在类中以def关键字定义的都可以称之为实例方法,
不仅如此,类的初始化方法(__init__() )理论上也属于实例方法,只不过它比较特殊。
实例方法最大的特点就是,它最少也要包含一个 self 参数,用于绑定调用此方法的实例对象(Python 会自动完成绑定)。
实例方法通常会用类对象直接调用。
  • 1
  • 2
  • 3
  • 4

(2)类方法
Python 类方法和实例方法相似,它最少也要包含一个参数,只不过类方法中通常将其命名为 cls,类方法是类对象所拥有的方法,需要用修饰器@classmethod来标识其为类方法,对于类方法,第一个参数必须是类对象,一般以’cls’作为第一个参数,可以通过实例对象和类对象去访问。类方法还有一个用途就是可以对类属性进行修改。实例如下:

class People(object):
    country = 'china'

    #类方法,用classmethod来进行修饰
    @classmethod
    def getCountry(cls):
        return cls.country
    @classmethod
    def setCountry(cls,country):
        cls.country = country

p = People()
print(p.getCountry())    #可以用过实例对象引用
print(People.getCountry())    #可以通过类对象引用


p.setCountry('japan') 
print(p.getCountry())   
print(People.getCountry())

# output
#china
#china
#japan
#japan
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25

类方法什么时候使用呢?我们可以考虑一个场景:
假设我有一个学生类和一个班级类,想要实现的功能为: 学生类继承自班级类,每实例化一个学生,班级人数都能增加。 最后,我想实例化一些学生,并获得班级中的总人数。

思考:这个问题用类方法做比较合适,为什么?

因为我们要实例化的是学生,但是如果我从学生这一个实例中获得班级总人数,在逻辑上显然是不合理的。同时,想要获得班级总人数,如果生成一个班级的实例也是没有必要的。 所以写成类方法最为合适。

(3)静态方法
至于静态方法,需要通过修饰器@staticmethod来进行修饰。静态方法是类中的"函数”。静态方法主要是用来存放逻辑性的代码,逻辑上属于类,但是和类本身没有关系,也就是说在静态方法中,不会涉及到类中的属性和方法的操作。可以理解为,静态方法是个独立的、单纯的函数,它仅仅托管于某个类的名称空间中,便于使用和维护。例如,我想定义一个关于时间操作的类,其中有一个获取当前时间的函数:

import time

class TimeTest(object):
    def __init__(self, hour, minute, second):
        self.hour = hour
        self.minute = minute
        self.second = second

    @staticmethod
    def showTime():
        return time.strftime("%H:%M:%S", time.localtime())

# 使用类对象调用静态方法
print(TimeTest.showTime())
# 实例化对象
t = TimeTest(2, 10, 10)
# 使用实例对象调用静态方法 
print(t.showTime()) 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

如上,使用了静态方法,然而静态方法实现中并没使用实例的属性和方法(但可以通过类名调用类属性和类方法)。若要获得当前时间的字符串时,并不一定需要实例化对象。其实,我们也可以在类外面写一个同样的函数来做这些事,但是这样做就打乱了逻辑关系,也会导致以后代码维护困难。

五、继承

(1)继承的概念

在程序中,继承的概念简单的多,描述的是事物之间的所属关系,例如猫和狗都属于动物,程序中便可以描述为猫和狗继承自动物; 同理,波斯猫和巴厘猫都继承自猫,而沙皮狗和斑点狗都继承自狗。

(2)继承示例

# 定义一个父类,如下:
class Cat( ):

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

    def run(self):
        print("%s--在跑" %self.name)


# 子类在继承的时候,在定义类时,小括号()中为父类的名字,继承Cat类如下:
class TianyuanCat(Cat):

    def setNewName(self, newName):
        self.name = newName

    def eat(self):
        print("%s--在吃"%self.name)


Xuanmao = TianyuanCat("玄猫", "黑色")
print('Xuanmao的名字为:%s' %Xuanmao.name)
print('Xuanmao的颜色为:%s' %Xuanmao.color)
Xuanmao.eat()
Xuanmao.setNewName('小黑')
Xuanmao.run()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27

运行结果:
在这里插入图片描述
虽然子类TianyuanCat没有定义初始化方法,但是父类Cat有,所以在子类继承父类的时候这个方法就被继承了,所以只要创建TianyuanCat的对象,就默认执行了那个继承过来的方法__init__()方法了。

此外,还需要注意的是

(1)类的私有的属性,不能通过对象直接访问,但是可以通过方法访问。对此进行代码演示如下:

 class Person:
        __name = '小甲鱼'
        def getName(self):
                return self.__name

 p = Person()
 p.__name
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

运行结果如下:
在这里插入图片描述

class Person:
        __name = '小甲鱼'
        def getName(self):
                return self.__name

p = Person()
#p.__name
p.getName()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

运行结果如下:
在这里插入图片描述
我们把getName方法称之为“访问器”。Python事实上是采用一种叫“name mangling”技术,将以双下划线开头的变量名巧妙的改了个名字而已,我们仍然可以在外部通过“__类名__变量名”的方式访问:

class Person:
        __name = '小甲鱼'
        def getName(self):
                return self.__name

p = Person()
#p.__name
#p.getName()
p._Person__name
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

运行结果:
在这里插入图片描述
(2)私有的方法,不能通过对象直接访问。
(3)私有的属性、方法,不会被子类继承,也不能被访问。
(4)一般情况下,私有的属性、方法都是不对外公布的,往往用来做内部的事情,起到安全的作用。

示例代码如下:

class Animal(object):

    def __init__(self, name='动物', color='白色'):
        self.__name = name
        self.color = color

    def __test(self):
        print(self.__name)
        print(self.color)

    def test(self):
        print(self.__name)
        print(self.color)



class Dog(Animal):
    def dogTest1(self):
        #print(self.__name) #不能访问到父类的私有属性
        print(self.color)


    def dogTest2(self):
        #self.__test() #不能访问父类中的私有方法
        self.test()


A = Animal()
#print(A.__name) #程序出现异常,不能访问私有属性
print(A.color)
#A.__test() #程序出现异常,不能访问私有方法
A.test()

print("------分割线-----")

D = Dog(name = "小花狗", color = "黄色")
D.dogTest1()
D.dogTest2()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38

(3)多继承

在这里插入图片描述
从图中能够看出,所谓多继承,即子类有多个父类,并且具有它们的特征。
Python中的多继承格式如下:

# 定义一个父类
class A:
    def printA(self):
        print('----A----')

# 定义一个父类
class B:
    def printB(self):
        print('----B----')

# 定义一个子类,继承自A、B
class C(A,B):
    def printC(self):
        print('----C----')

obj_C = C()
obj_C.printA()
obj_C.printB()

#运行结果: 
#----A----
#----B----
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

注意一点,如果在上面的多继承例子中,如果父类A和父类B中,有一个同名的方法,那么通过子类去调用的时候,调用先继承的父类方法。实例如下:

#coding=utf-8
class base(object):
    def test(self):
        print('----base test----')
class A(base):
    def test(self):
        print('----A test----')

# 定义一个父类
class B(base):
    def test(self):
        print('----B test----')

# 定义一个子类,继承自A、B
class C(B,A):
    pass
 
obj_C = C()
obj_C.test()
 
#运行结果:  
#----B---- 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

(4)调用与重写父类方法

所谓重写,就是子类中,有一个和父类相同名字的方法,在子类中的方法会覆盖掉父类中同名的方法。实例如下:

class Cat:
    def sayHello(self):
        print("miaomiao~~")


class Xuanmao(Cat): 
    def sayHello(self):
        print("miao!~~!!!")

xiaohei = Xuanmao()

xiaohei.sayHello()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

在类的继承里面super()非常常用, 它解决了子类调用父类方法的一些问题, 例如,当父类多次被调用时只执行一次, 优化了执行逻辑,下面我们就来详细看一下。

当存在继承关系的时候,有时候需要在子类中调用父类的方法,此时最简单的方法是把对象调用转换成类调用,需要注意的是这时self参数需要显式传递,例如:

class Cat:
    def sayHello(self):
        print("miaomiao~~")
 
class Xuanmao(Cat): 
    def sayHello(self):
    	# 调用父类方法时,直接使用父类名称进行调用
        Cat.sayHello(self)

xiaohei = Xuanmao() 
xiaohei.sayHello()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

这样做有一些缺点,比如说如果修改了父类名称,那么在子类中也会涉及修改,尤其是子类很多的情况,每个子类都需要进行修改。另外,Python是允许多继承的语言,如上所示的方法在多继承时就需要重复写多次,显得累赘。为了解决这些问题,Python引入了super()机制,例子代码如下:

class Cat:
    def sayHello(self):
        print("miaomiao~~")
 
class Xuanmao(Cat): 
    def sayHello(self):
    	# 使用super()代替父类类名,即便类名改变,这里的super也不需要修改。
        super().sayHello()

xiaohei = Xuanmao() 
xiaohei.sayHello()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
一般而言,super在继承中经常用来继承父类的初始化方法,例如 super().__init__()
  • 1

六、多态

什么是多态
多态指的是一类事物有多种形态,比如动物有多种形态:猫、狗、猪

class Animal: #同一类事物:动物
    def talk(self):
        pass
class Cat(Animal): #动物的形态之一:猫
    def talk(self):
        print('喵喵喵')
class Dog(Animal): #动物的形态之二:狗
    def talk(self):
        print('汪汪汪')
class Pig(Animal): #动物的形态之三:猪
    def talk(self):
        print('哼哼哼')

#实例化得到三个对象
cat=Cat()
dog=Dog()
pig=Pig()

cat.talk()
# 喵喵喵
dog.talk()
# 汪汪汪
pig.talk()
# 哼哼哼
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

多态性指的是可以在不用考虑对象具体类型的情况下而直接使用对象,这就需要在设计时,把对象的使用方法统一成一种。
例如上述代码中cat、dog、pig都是动物,但凡是动物肯定有talk方法,于是我们可以不用考虑它们三者的具体是什么类型的动物,而直接使用talk()方法。 更进一步,我们可以定义一个统一的接口来使用,如下代码所示:

class Animal: #同一类事物:动物
    def talk(self):
        pass
class Cat(Animal): #动物的形态之一:猫
    def talk(self):
        print('喵喵喵')
class Dog(Animal): #动物的形态之二:狗
    def talk(self):
        print('汪汪汪')
class Pig(Animal): #动物的形态之三:猪
    def talk(self):
        print('哼哼哼')


#实例化得到三个对象
cat=Cat()
dog=Dog()
pig=Pig()


def Talk(animal):
    animal.talk()

Talk(cat)
# 喵喵喵
Talk(dog)
# 汪汪汪
Talk(pig)
# 哼哼哼
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29

python中一切皆对象,本身就支持多态性。 多态性的好处在于增强了程序的灵活性和可扩展性,比如通过继承animal类创建了一个新的类,实例化得到的对象obj,可以使用相同的方式使用obj.talk()

class Animal: #同一类事物:动物
    def talk(self):
        pass
class Wolf(Animal): #动物的另外一种形态:狼
    def talk(self):
        print('嗷...')

wolf=Wolf() # 实例出一头狼
wolf.talk() # 使用者根本无需关心wolf是什么类型而调用talk
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

最后,可以通过关键字@abc.abstractmethod在父类引入抽象类的概念来硬性限制子类必须有某些方法名。

import abc
# 指定metaclass属性将类设置为抽象类,抽象类本身只是用来约束子类的,不能被实例化
class Animal(metaclass=abc.ABCMeta):
    @abc.abstractmethod  # 该装饰器限制子类必须定义有一个名为talk的方法
    def talk(self):  # 抽象方法中无需实现具体的功能
        pass

class Cat(Animal): # 但凡继承Animal的子类都必须遵循Animal规定的标准
    def talk(self):# 若子类中没有一个名为talk的方法则会抛出异常TypeError,无法实例化
        print('喵喵喵')          # TypeError: Can't instantiate abstract class Cat with abstract methods talk
        #pass

cat=Cat()
cat.talk()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

鸭子类型

实际上,多态的概念是应用于Java和C#这一类强类型语言中,而Python崇尚的是“鸭子类型”。

多态性的本质在于不同的类中定义有相同的方法名, 这样我们就可以不考虑类而统一用一种方式去使用对象。但其实我们完全可以不依赖于继承,只需要制造出外观和行为相同的对象,同样可以实现不考虑对象类型而使用对象, 这正是python崇尚的“鸭子类型”

Duck typing:“如果看起来像、叫声像而且走起路来像鸭子,那么它就是鸭子”。
比起继承的方式,鸭子类型在某种程度上实现了程序的松耦合度,如下:

"""
定义是的类型和运行时的类型不一样,就是多态的体现
Python崇尚鸭子类型
"""
class Cat(object):
    def say(self):
        print("i am Cat")


class Dog(object):
    def say(self):
        print("i am Dog")


class Duck(object):
    def say(self):
        print("i am Duck")


# 以上定义了三个类
animal_list = [Cat, Dog, Duck]    # 这里将三个封装好的类分别作为animal_list的三个元素

for animal in animal_list:    # animal_list是一个列表,是可迭代的对象
    animal().say()            # animal()是实例化对象的过程,然后分别调用 Cat, Dog, Duck的say方法
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

运行结果:
在这里插入图片描述
如上述代码所示,如果定义的若干个类都有同一个方法,(比如上面的say方法),那么无论他们是否继承同一个父类,他们都可以通过统一方法的调用(say方法的调用)实现。

在鸭子类型中,关注的不是对象的类型本身,而是它是如何使用的。 鸭子类型的好处就在于能够避免一些类的重写与继承,无需大量复制相同的代码

鸭子类型使用的前提是需要良好的文档支持,不然会让代码变得很混乱,如果没有良好的文档及说明,有可能会导致"你家大鹅是鸭子"的情况。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/2023面试高手/article/detail/345404
推荐阅读
相关标签
  

闽ICP备14008679号