Python 基础合集7:类和实例

发布于:2022-12-07 ⋅ 阅读:(674) ⋅ 点赞:(0)

一、前言

本小节主要梳理类和实例的基本知识,包含类及其属性、方法的定义和调用,类的实例及其属性、方法的定义和调用,还介绍了3个魔法函数__init__()__str__()__repr__()和私有化变量的使用。

环境说明:Python 3.6、windows11 64位

二、类及其实例和对象相关的基本概念

2.1 类

类的定义:类是定义新类型的程序结构,里面有数据属性,以及用于操作数据属性的方法。类的方法和函数类似。
类的定义可以通过现实世界的类别来类比理解,像狗类、鸟类、鱼类、人类等。每一个类别都有一些普遍的特征,如狗粘人、鸟会飞、鱼会游、人会直立走
这些特征就好比类的属性和方法,属于类特有的一些特征。

2.1.1 创建一个类

创建一个类一般使用class关键字,语法如下:

# 定义一个Myclass类
class Myclass:
    pass

这是一个“光秃秃”的类,接下来给你加一些“枝干”——属性和方法。
类的属性类似变量,定义方式和变量的定义大同小异,通过[属性名]=值进行定义。如果是在类方法内部则需要再加上一个cls声明。
类的方法类似函数,定义方式和函数的定义大同小异,都是通过def关键字声明,不同的是类方法还需要通过@classmethod装饰器进行修饰,@classmethod改变了调用方法的方式,同时需要将类本身作为第一个参数,而不是实例(主要和后面讲的实例self做区分)。
示例如下:

# 定义My_class类
class My_class:
    my_name = 'Xindata'		   		# 定义类属性
    
    @classmethod
    def my_func1(cls):	    		# 定义类方法
        cls.my_eye = 'black'		# 定义类属性

常见装饰类的装饰器除了@classmethod,还有@staticmethod@staticmethod装饰的方法叫静态方法,它也会改变方法的调用方式,但是第一个参数不是特殊的值。静态方法类似普通的函数。由于不像@classmethod装饰的类方法的第一个参数是类本身,所以静态方法在定义类属性时,需要使用类名。

# 定义My_class类
class My_class:
    my_name = 'Xindata'		      # 定义类属性,属性名
    
    @classmethod
    def my_func1(cls):			  # 类方法,默认参数cls是类本身
        cls.my_eye = 'black'      # 定义类属性,cls.属性名
        
    @staticmethod
    def my_func2():               # 静态方法,无默认参数
        My_class.t_shirt = 'white'# 定义类属性,类名.属性名

以上都是在类内部的定义,在类外部也可以进行一些属性方法的定义

# 定义My_class类
class My_class:
    my_name = 'Xindata'		      # 定义类属性,属性名
    
    @classmethod
    def my_func1(cls):			  # 类方法,默认参数cls是类本身
        cls.my_eye = 'black'      # 定义类属性,cls.属性名
        
    @staticmethod
    def my_func2():               # 静态方法,无默认参数
        My_class.t_shirt = 'white'# 定义类属性,类名.属性名
        
My_class.hair = 'black'           # 定义类的属性

@classmethod
def func4(cls):                   # 被@classmethod装饰过的函数
    cls.skill_1 = 'Python'
    print(self.skill_1)
My_class.my_func4 = func4         # 定义类方法

2.1.2 类的调用

注意,在类方法和静态方法中定义的类属性,必须调用类方法之后才能调用相关的属性,否则会报错(可以把一下代码第17和19行的方法调用注释掉,重跑试试)。
类的属性和方法的调用都是通过类名.然后加上对应的名称。

# 类的定义
class My_class:
    my_name = 'Xindata'
    
    @classmethod
    def my_func1(cls):
        cls.my_eye = 'black'
        print(cls.my_eye)
        
    @staticmethod
    def my_func2():
        My_class.t_shirt = 'white'
        print(My_class.t_shirt)

My_class.hair = 'black'

@classmethod
def func4(cls):
    cls.skill_1 = 'Python'
    print(cls.skill_1)
My_class.my_func4 = func4 
        
# 类的调用
print(My_class.my_name)    # 调用属性my_name
My_class.my_func1()        # 调用方法my_func1()
print(My_class.my_eye)     # 调用属性my_eye,须先调用my_func1()
My_class.my_func2()        # 调用方法my_func2()
print(My_class.t_shirt)    # 调用属性t_shirt,须先调用my_func2()
print(My_class.hair)       # 调用属性hair
My_class.my_func4()        # 调用方法my_func4()
print(My_class.skill_1)    # 调用属性skill_1

2.2 类的实例

2.2.1 创建类的实例

实例的属性跟类的属性非常相似,不同的是对象不一样,在类中,一般使用self.属性名创建实例对象;而实例方法的创建更加简单,不需要装饰器修饰,直接定义,并使用类的实例对象self作为第一个参数。
同样地,在类外部也可以创建实例的属性和方法,只不过创建属性前需要先对类进行实例化,实例化调用可以通过类名()实现。
具体例子如下:(作为对比,保留以上代码)

# 类的定义
class My_class:
    my_name = 'Xindata'
    
    @classmethod
    def my_func1(cls):
        cls.my_eye = 'black'
        print(cls.my_eye)
        
    @staticmethod
    def my_func2():
        My_class.t_shirt = 'white'
        print(My_class.t_shirt)

    def my_func3(self):           # 创建实例方法
        self.hobby = 'writting'   # 创建实例属性
        print(self.hobby)

My_class.hair = 'black'

@classmethod
def func4(cls):
    cls.skill_1 = 'Python'
    print(cls.skill_1)
My_class.my_func4 = func4 

# 创建实例属性和方法
my_eg = My_class()     # 类的实例化,实例my_eg拥有类的所有属性和方法
my_eg.height = 175

def func5(self):
    self.skill_2 = 'SQL'
    print(self.skill_2)
My_class.my_func5 = func5 # 注意这里还是My_class,而不是my_eg

注:**cls****self**是习惯用法,改用其他的参数名也可以,重点是作为首位参数。

2.2.2 实例化调用

实例化之后,类的实例对象都拥有类的相同属性和方法,可以通过实例对象进行调用,类就相当于一个模板,每个实例都是它的复刻版本。在调用的实例方法内部的属性,同样需要先调用方法。
实例可以调用类的属性和方法,但是类不能调用实例的属性和方法。如下第47~51行,注释掉代码运行时会报错。

# 类的定义
class My_class:
    my_name = 'Xindata'
    
    @classmethod
    def my_func1(cls):
        cls.my_eye = 'black'
        print(cls.my_eye)
        
    @staticmethod
    def my_func2():
        My_class.t_shirt = 'white'
        print(My_class.t_shirt)
        
    def my_func3(self):           
        self.hobby = 'writting'   
        print(self.hobby)

My_class.hair = 'black'

@classmethod
def func4(cls):
    cls.skill_1 = 'Python'
    print(cls.skill_1)
My_class.my_func4 = func4 

# 创建实例属性和方法
my_eg = My_class()        # 实例化调用类,实例拥有类的所有属性和方法
my_eg.height = 175

def func5(self):
    self.skill_2 = 'SQL'
    print(self.skill_2)
My_class.my_func5 = func5 

# 类的调用
print(My_class.my_name)
My_class.my_func1()    
print(My_class.my_eye) 
My_class.my_func2()    
print(My_class.t_shirt)
print(My_class.hair)   
My_class.my_func4()       
print(My_class.skill_1)  

# 实例的属性和方法不属于类,类不能对其进行调用
# My_class.my_func3()        # 报错,调用类的实例方法my_func3()
# print(My_class.hobby)      # 报错,调用类的实例属性hobby
# print(My_class.height)     # 报错,调用类的实例属性
# My_class.my_func5()        # 报错,调用类的实例方法my_func5()
# print(My_class.skill_2)    # 报错,调用类的实例属性

# 如果类需要对相关的实例进行调用,需要传入类的实例给到参数self
# my_eg = My_class()        # 上文已实例化,直接使用
My_class.my_func3(my_eg)
print(my_eg.hobby)
print('-----------------------')
# 类的实例化,全部调用一遍
# my_eg = My_class()        # 上文已实例化,直接使用
print(my_eg.my_name)      # 调用的实例对象属性my_name
my_eg.my_func1()          # 调用的实例对象方法my_func1()
print(my_eg.my_eye)       # 调用的实例对象属性my_eye,须先调用my_func1()
my_eg.my_func2()          # 调用的实例对象方法my_func2()
print(my_eg.t_shirt)      # 调用的实例对象属性t_shirt,须先调用my_func2()
my_eg.my_func3()          # 调用的实例对象方法my_func3()
print(my_eg.hobby)        # 调用的实例对象属性hobby,须先调用my_func3()
print(my_eg.hair)         # 调用的实例对象属性hair
my_eg.my_func4()          # 调用的实例对象方法my_func4()
print(my_eg.skill_1)      # 调用的实例对象属性skill
print(my_eg.height)       # 调用的实例对象属性height
my_eg.my_func5()          # 调用的实例对象方法my_func5()
print(my_eg.skill_2)      # 调用的实例对象属性skill

如果要修改实例化属性可以在实例化之后进行重新赋值;
如果要修改方法的功能,可以重新定义一个函数,然后通过上述my_func5的方式进行修改。
具体例子如下:
由执行结果可以看出,给方法重新赋值前的方法是来自于类内部的my_func3,赋值后变成了func6,说明赋值成功了,相关的功能也变成了赋值后的方法的功能。

# 类的定义
class My_class:
    my_name = 'Xindata'
        
    def my_func3(self):           
        self.hobby = 'writting'   
        print(self.hobby)

def func6(self):
    self.hobby = 'writting' 
    print('My hobby is %s'%self.hobby)


my_eg = My_class()        # 实例化调用类
print(my_eg.my_name)      # 调用属性my_eg.my_name
my_eg.my_name = 'Xin学数据'# 给my_name重新赋值
print(my_eg.my_name)      # 再次调用my_eg.my_name

my_eg.my_func3()          # 调用类的实例方法my_func3()
print(my_eg.my_func3)
My_class.my_func3 = func6 # 重新给my_func3()赋值
my_eg.my_func3()          # 再次调用my_func3()
print(my_eg.my_func3)

# 结果为:
# Xindata
# Xin学数据
# writting
# <bound method My_class.my_func3 of <__main__.My_class object at 0x0000020FAD8D2880>>
# My hobby is writting
# <bound method func6 of <__main__.My_class object at 0x0000020FAD8D2880>>

2.2.3 魔法方法

在python中方法名诸如__xxx__()的,都有特殊的功能,因此被叫做“魔法”方法。本小结主要讲三个魔法方法:__init__()__str__()__repr__()

每次要使用实例方法的属性都要调用对应的方法才能使用,有没有实例化类之后就可以直接使用的呢?有,这就是接下来上场的函数,叫初始化函数:__init__()

基本格式如下,由 init 左右两边加双下划线组成。

class A():
    def __init__(self):
        self.name = 'Xindata'
        print('执行初始化函数。')
a=A()
print(a.name)
# 结果为:
# 执行初始化函数。
# Xindata

初始函数的特性之一是当对类进行实例化调用的时候,该函数会自动执行,无须额外调用
利用这个特性,在编写习惯上,我们会在初始化函数内部完成类实例属性的创建,为类实例属性设置初始值,这样类中的其他方法就能直接、随时调用。

class A():
    def __init__(self):
        self.name = 'Xindata'
        print('执行初始化函数。')
        
    def my_func1(self):
        print(self.name)
a=A()
a.my_func1()
# 结果为:
# 执行初始化函数。
# Xindata

初始函数带除了self之外的参数时,在类实例化调用时,需要进行传参。
比如我将'Xindata'作为初始函数的参数name的参数值进行传递,然后将参数name再赋值给类实例属性self.name,最后在调用my_func1()函数时进行打印。

class A():
    def __init__(self,name):
        self.name = name
        print('执行初始化函数。')
        
    def my_func1(self):
        print(self.name)
a=A('Xindata')
a.my_func1()
# 结果为:
# 执行初始化函数。
# Xindata

初始函数的特性二:不return。即没有返回值。
如下代码会报错。

class A():
    def __init__(self,name):
        self.name = name
        print('执行初始化函数。')
        return self.name      # 加上return返回self.name,结果报错

a=A('Xindata')

如果需要返回值,怎么办?可以通过__str__()函数。
__str__()函数也是调用即执行。返回值可以在打印实例的时候看到相关的结果(如下,打印结果为Xindata)。但是当不通过打印,而是通过测试环境返回实例a,或者在Jupyter notebook直接写a(效果等同于测试环境返回a),返回的结果确是<__main__.A at 0x290f383a6d0>
这个区别主要是用于面向用户和测试者或开发者返回不同的内容,打印出来的内容用于呈现给用户,而测试环境返回的内容用于呈现给测试者或开发者。

class A():
    def __init__(self,name):
        self.name=name
        print('执行初始化函数。')
    def __str__(self):
        return self.name

a=A('Xindata')
print(a)
a
# 结果为:
# 执行初始化函数。
# Xindata
# >>><__main__.A at 0x290f383a6d0>

image.png
如果要使得打印和测试环境返回的结果都一样,可以通过__repr__()函数来实现,具体有两种方法:

  • 方法一:直接加上__repr__ = __str__
  • 方法二:只定义__repr__()函数
# 方法一
class A():
    def __init__(self,name):
        self.name=name
        print('执行初始化函数。')
    def __str__(self):
        return self.name
    __repr__ = __str__
    
a=A('Xindata')
print(a)
a
# 方法二
class A():
    def __init__(self,name):
        self.name=name
        print('执行初始化函数。')
    def __repr__(self):
        return self.name
    
a=A('Xindata')
print(a)
a

# 结果都是:
# 执行初始化函数。
# Xindata
# >>>Xindata

image.png
如果要使得打印和测试环境返回的结果都不一样,而且要进行自定义,则需要分别定义__str__()函数和__repr__()函数。

class A():
    def __init__(self,name):
        self.name=name
        print('执行初始化函数。')
    def __str__(self):
        return self.name
    
    def __repr__(self):
        return 'test: %s.'%self.name
    
a=A('Xindata')
print(a)
a
# 结果是:
# 执行初始化函数。
# Xindata
# >>>test: Xindata.

image.png
在查看__str__()函数和__repr__()函数返回的内容的时候,还可以直接使用其对应的方法str()repr(),具体如下:

class A():
    def __init__(self,name):
        self.name=name
        print('执行初始化函数。')
    def __str__(self):
        return self.name
    
    def __repr__(self):
        return 'test: %s.'%self.name
    
a=A('Xindata')
print(str(a))
print(repr(a))

image.png

2.2.4 私有化变量

如果要让内部属性不被外部访问,可以把属性的名称前加上两个下划线__,在Python中,实例的变量名如果以__开头,就变成了一个私有变量(private),只有内部可以访问,外部不能访问。

class A():
    def __init__(self,name):
        self.__name=name
        print('执行初始化函数。')
    def my_func1(self):
        print(self.__name)
a = A('Xindata')
a.my_func1()         # 正常执行
print(a.name)        # 报错

如果外部需要获取到内部的属性name,可以通过写一个方法(如下get_name()),提供一个接口,给到外部调用。

class A():
    def __init__(self,name):
        self.__name=name
        print('执行初始化函数。')
    def my_func1(self):
        print(self.__name)
    def get_name(self):
        return self.__name
a = A('Xindata')
a.my_func1()         # 正常执行
name = a.get_name()
print(name)

2.2.5 拓展

通过类调用类的实例方法。
执行以下代码,会返回一个错误:

TypeError: get_name() missing 1 required positional argument: ‘self’

没有给self传参,那是不是给self传递一个参数就可以了呢?(是的!)还有要传什么参数呢?(self是类的实例,所以传递的参数应该也是类的实例,如下代码最后一行给self传递类实例A('Xindata'))。

class A():
    def __init__(self,name):
        self.name = name
        print('执行初始化函数。')
    def my_func1(self):
        print(self.name)
    def get_name(self):
        return self.name
A.get_name()

# A.get_name(A('Xindata'))

2.3 对象

对象是一个比较广义的概念。在现实世界中,随处可见的一个事物,比如某一棵大树、某一颗小草,就是对象。在Python中,对象是某个类的某一个具体实例,它包含类中的属性和方法。
在2.2 中讲的类的实例,它就是一种类的实例化对象。
类是对象的模板,描述不同对象的共同特征或行为,而对象是类的具体实例,这些实例都拥有类的所有特征和行为(即属性和方法)。

三、小结

知识点比较琐碎和繁杂,通过思维导图梳理一下:
类和实例.png




<下节预告:类的继承和多态>


- End -

本文含有隐藏内容,请 开通VIP 后查看

网站公告

今日签到

点亮在社区的每一天
去签到