分享

Python面向对象编程:类和实例

 龙书意 2023-07-14 发布于广东

公众号:尤而小屋
作者:Peter
编辑:Peter

本文讲解一个python的基础知识点:Python中类和实例的讲解。主要包含:

  • Python类的创建

  • 调用类和实例的属性

  • 调用类的方法:绑定方法和非绑定方法

  • 装饰器使用:类方法(@classmethod装饰)和静态方法(@staticmethod装饰

基础类

首先创建一个类Person

class Person:
    '''
    this is a sample of class
    '''

    
    # 1 特殊方法--构造函数---初始化函数(方法)
    def __init__(self, name):
        self.name = name  # 建立实例的属性name,传入的值恰好也是name
    
    # 2
    def get_name(self):
        return self.name  # 直接调用构造函数中类的属性
    
    # 3
    def color(self, color):
        d = {}
        d[self.name] = color  # 调用类的属性(来自构造函数)
        return d

python3中所有的类都是Object类的子类,且类名首字母需要大写。

  • 1、 函数1是初始化方法:在用类创建实例时,首先需要执行初始化函数

  • 2-3:两个函数的第一个参数必须是self,其他和普通函数的定义与使用完全相同

类是对象的定义,实例才是真实的物件:实例化一个Person对象

xiaoming = Person('xiaoming')   # 实例化对象xiaoming
print('name of Person: ', xiaoming.name)  # 实例的name属性
name = xiaoming.get_name()  # 调用实例的方法get_name
print('get_name: ', name)
color = xiaoming.color('white')  # 调用方法color,并传入参数white
print('color: ',color)

类的属性

定义一个简单的类:

class Person:
    '''在这里我们创建一个非常简单的类Person'''
    age = 20

其中,age就是Person类的属性。

1、查看属性

Person.age

2、修改属性的值

Person.age = 24
Person.age

3、查看属性相关信息

print(dir(Person))

结论:可以看到只有最后的age是创建时候自定义的,其他都是默认的。

# 以字典的形式返回所有属性

Person.__dict__  

在这里我们可以看到在创建时候的注释文档:

# 显示类内部的注释说明文档

Person.__doc__  
# 显示类的名字

Person.__name__

所有的类都是基于基类object:

# 显示类继承的基类object

Person.__base__
# 删除类的属性

del Person.age

再次运行则会报错:

# Person.age

创建实例

还是使用之前的Person类:

class Person:
    '''
    this is a sample of class
    '''

    # 1 特殊方法--构造函数---初始化函数(方法)
    def __init__(self, name):
        self.name = name  # 建立实例的属性name,传入的值恰好也是name
    
    # 2
    def get_name(self):
        return self.name  # 直接调用构造函数中类的属性
    
    # 3
    def color(self, color):
        d = {}
        d[self.name] = color  # 调用类的属性
        return d

基于类创建实例

首先,我们创建一个实例:

boy = Person('xiaoming')
boy

语句boy = Person('xiaoming')的本质是将变量boyPerson('xiaoming')建立引用关系。这种关系的确定和我们创建变量的赋值语句,比如a=2是类似的。

其实,我们也可以看到:创建实例就是在调用类的过程。

type(boy)

__init__函数说明

在这里说明下:__init__()是一个特殊方法

  1. 在该方法里面主要是规定一些属性或者做一些初始化工作
  2. 该方法的第一个参数必须是self
  3. __init__()方法没有return语句,其实是return None,一般就不写
>>> class Foo:
...     def __init__(self):
...         print('I am __init__()')
...         return 100
...
>>> foo = Foo()
I am __init__()
Traceback (most recent call last):
  File '<stdin>', line 1in <module>
TypeError: __init__() should return Nonenot 'int'

实例属性

上面我们创建了实例boy对象,它具有name属性:

boy.name

还可以修改实例的属性:

boy.name = 'lihua'
boy.name  # 再次调用

除了自定义的name属性,实例还具有其他属性:

print(dir(boy))  # 最后的name属性

二者相互影响

实例属性可以随意修改,但是类的属性却不行:

class Foo:
    x = 25
    
foo = Foo()  # 类的实例化
foo.x  # 实例的属性x
# 修改实例属性

foo.x = 32
foo.x

我们查看类的x属性,确实是没有变化的:

Foo.x

也就是说:实例属性的变化不会影响类的属性

但是类的属性变化却能够影响实例属性的变化,因为实例本身就是通过调用类来建立的。

class Foo:
    x = 25
    
foo = Foo()  # 类的实例化
foo.x  # 原实例属性值
Foo.x += 5  # Foo.x = Foo.x + 5

Foo.x

此时再查看实例的属性:

foo.x  # 修改类的属性后的实例foo属性值

上面的例子中,类中的变量引用的是不可变对象,比如数值、字符串等。但是当类中引用的就是可变数据类型,比如列表等,则会不同:

class Foo:
    x = [1,2,3,4]
# 类的属性

Foo.x
# 实例的属性

foo = Foo()
foo.x

修改实例的属性:

foo.x.append(55)

再次查看实例和类的属性:

foo.x
Foo.x

可以看到同时发生了变化。

修改类的属性:也会同时发生了变化。

Foo.x.append(66)
Foo.x
foo.x

self作用

在上面的多个例子中我们创建实例的时候,都没有用到self参数,它到底有什么用?

首先创建一个简单的类Person:

class Person:
    def __init__(self,age):
        self.age = age  
        #print('self:', self)
        print('self的类型:',type(self))
age = Person(20)  # 创建实例
age
print('实例age的类型:',type(age)) 

可以看到我们创建的实例age的类型和self的类型是一样的。可以理解成:self就是类Person的实例,self和age实例所引用的对象是一样的

但是在类的外面不能这样使用:

# self.age

在Python类中,这个self参数必须显式地写,在类的内部是不能省略的,其他方法都必须是承接self实例对象。

比如下面的类中,3个方法中都使用了self作为第一个或者唯一参数:

class Person:
    '''
    this is a sample of class
    '''

    # 1 构造函数---初始化函数
    def __init__(self, name, age, sex): # 通过self增加多个属性
        self.name = name  # 实例属性name
        self.sex = sex  # 实例属性sex
    
    # 2  
    def age(self,n):
        self.age = n   # 属性age
    
    # 3
    def get_name(self):
        return self.name  # 直接调用构造函数中类的属性
    
    # 4
    def color(self, color):
        d = {} 
        d[self.name] = color
        return d

类的方法

在类中除了属性,其他就是方法,可以简单理解成函数,一个函数就是一个方法;还有其他的注释和文档(可忽略)。

首先理解下绑定方法和非绑定方法。先创建一个类:

class Foo:  # 定义Foo类
    def test(self):
        print('this is a normal mthod of class!')

创建实例f:

f = Foo()  

类和实例调用方法

通过实例调用方法:test()函数就是我们说的方法。在调用的时候self参数作为第一参数隐式的传递给了方法,在调用的时候无需再写。

self就是实例f。

f.test()  # 调用方法

在调用方法的时候最后一定要加上括号;调用属性则是不需要的。

# 也可以显式把实例f地传递方法

Foo.test(f)  # self 就是 f

如果类调用方法的时候不传递实例则会报错:

Foo.test()

TypeError                                 Traceback (most recent call last)
~\AppData\Local\Temp\ipykernel_6936\659740599.py in <module>
----> 1 Foo.test()

TypeError: test() missing 1 required positional argument: 'self'

python中一切皆对象,类Foo的test()方法也是对象:函数对象

非绑定方法

(1)通过类调用方法得到的是非绑定方法

Foo.test  # 对象

这种通过类的名字来调用方法对象,叫做:非绑定方法unbound method

绑定方法

(2)通过实例调用类的方法得到的是绑定方法

通过实例来得到方法对象:

f.test   

可以看到结果中是绑定方法bound method

Foo.__dict__

在类的__dict__属性中,可以看到:test方法就是一个function函数对象

Foo.__dict__['test']  # 获取字典的test键

描述器

python类中特殊的方法:__get__()、__set__()、__delete__(),有这些方法的对象都叫做描述器

关于描述器的使用:

descr.__get__(self, obj, type=None) --> value
descr.__set__(self, obj, value) --> None
descr.__delete__(self, obj) --> None

通过类的__dict__属性调用__get__()方法:

Foo.__dict__['test'].__get__(None, Foo)  # 非绑定方法 

对描述__get__的self赋值为None,表示没有给定的实例,得到是非绑定方法。等同于:

Foo.test   # 非绑定方法

当我们给描述器传入实例对象f,得到的是绑定方法:

Foo.__dict__['test'].__get__(f, Foo)  #  绑定方法
f.test  #  绑定方法
  • 通过类获取方法对象的时候,得到非绑定方法对象(继承相关知识用到)
  • 通过实例获取方法对象的时候,得到绑定方法对象

类方法和静态方法

# 定义一个类
class Foo:
    name = 'xiaoming'  # 类属性
    def __init__(self):
        self.name = 'lisi'  # 实例属性
        
        
def get_class_attr(cls):
    return cls.name  # 调用类的属性name


if __name__ == '__main__':
    print('Foo.name:', Foo.name)  # 类的属性
    r = get_class_attr(Foo)  # 传入类;得到类属性
    print('get class attribute(cls.name):', r)
    f = Foo()  # 类的实例化;得到实例属性
    print('instance attrbute:', f.name)

注意上面的结果:Foo.name就是类的属性,get_class_attr得到的也是类的属性,经过实例化后f.name得到的是实例的属性。

类方法

get_class_attr函数是写在类的外面,调用的是前面的Foo类对象,类和函数的耦合性太强,相互之间的影响很大,如何优化?将类和函数放在一起:使用装饰器

# 定义一个类
class Foo:
    name = 'xiaoming'  # 类属性
    def __init__(self):
        self.name = 'lisi'  # 实例属性
        
    @classmethod    # 类方法 
    def get_class_attr(cls):  # 将类本身传入方法中 
        return cls.name  # 直接调用类的属性name

    
if __name__ == '__main__':
    print('Foo.name:', Foo.name)  # 1-类属性
    r = get_class_attr(Foo)  # 2-传入类-得到类的属性
    print('get class attribute:', r)
    f = Foo()  
    print('instance attrbute:', f.name)  # 3-类的实例化;得到实例的属性
    print('get class attribute:', f.get_class_attr())  # 4-通过实例调用方法,仍然得到类属性

在2和4中,不管是通过类还是实例来执行get_class_attr方法,得到都是类属性的值。说明装饰器@classmethod所装饰的方法,传入的第一个参数不是self,这个和类中其他方法是不同的。

其参数引用的是类对象Foo。cls其实就是将类本身作为引用对象传入到装饰器方法中。

定义类方法:类里面定义的方法,比如上面的get_class_attr就是类方法。

静态方法

提供一份待优化的代码来理解静态方法:

# 待优化代码

import random 

# 函数在外面,类中待调用
def select(n):
    a = random.randint(1,100)
    return a - n > 0

class Foo:
    def __init__(self,name):
        self.name = name
        
    def get_name(self,age):
        if select(age):  # 调用上面定义的select函数
            return self.name
        else:
            return 'name unknown!'
        
        
if __name__ == '__main__':
    f = Foo('xiaoming')
    age = f.get_name(25)
    print(age)

经过优化后的代码:将原来类外面的函数放到类里面,加上装饰器@staticmethod;装饰器里面的函数和普通函数一样定义和使用。

# 待优化代码

import random

# 创建类
class Foo:
    def __init__(self,name):
        self.name = name
        
    def get_name(self,age):
        # 调用装饰器定义的select函数
        if select(age):   # 等价于self.select(age)
            return self.name
        else:
            return 'name unknown!'
        
    @staticmethod
    def select(n):
        a = random.randint(1,100)
        return a - n > 0        
    
if __name__ == '__main__':
    f = Foo('xiaoming')
    age = f.get_name(25)
    print(age)

像这种使用@staticmethod装饰器进行装饰的函数或者方法,我们称之为:静态方法。静态方法的第一个参数也不是self。使用的时候可以通过实例self.select()调用,也可以通过类调用Foo.select()

总结

(1)__init__函数

  • 一般称之为初始化函数、构造函数、特殊方法
  • 在类中是非必须,如果存在第一个参数必须是self
  • 没有return语句;其实是return None

(2)self参数

  • self就是类的实例;self和实例的引用对象是一样的
  • 类中所有的方法的第一个参数必须是self,表示实例继承类的所有方法和属性
  • self在类中必须显式地写,不能省略

(3)绑定方法和非绑定方法

  • 通过类获取方法的时候,得到的是非绑定方法

  • 通过类的实例获取方法的时候,得到的是绑定方法

(4)类方法和静态方法

  • 类方法:经过@classmethod装饰的方法,第一个参数不是self,一般用作cls,将类本身作为参数传入。类方法可以调用类中的其他属性或者方法供自身使用。

  • 静态方法:经过@staticmethod装饰的方法,第一个参数也不是self,使用和普通函数相同。类中的其他方法可以直接实例调用装饰器下的方法,或者通过类调用。静态方法不能访问类中的其他属性或者方法。

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多