Python Class 详解

1、基本概念

1.1 类 class

  • 类是抽象的概念,是万事万物的抽象,是一类事物的共同特征的集合。
  • 用计算机的语言描述类,是属性和方法的集合。

1.2 对象 instance / object

  • 对象是类的具象,是一个实体。
  • 对于我们每个人这个个体,都是抽象概念人类的不同的实体。
  • 对象属性:它是对象状态的抽象,用数据结构来描述
  • 对象操作:它是对象行为的抽象,用操作名和实现该操作的方法来描述

1.3 面向对象三要素

  • 1 封装
    组装:将数据和操作组装到一起
    隐藏数据:对外只暴露一些接口,通过接口访问对象

  • 2 继承
    多复用,继承来的就不用自己写了
    多继承少修改,OCP(open-closed principle),使用继承来改变,来体现个性

  • 3 多态
    面向对象编程最灵活的地方,动态绑定

1.4 哲学思想

  • 一切皆对象
  • 对象是数据和操作的封装
  • 对象是独立的,但是对象之间可以相互作用
  • 目前 OOP(Object-oriented programming) 是最接近人类认知的编程范式

2、类

2.1 类的定义

  • 1 必须使用 class 关键字
  • 2 类名必须是用 大驼峰 命名
  • 3 类定义完成后,就产生了一个类对象,绑定到了标识符 ClassName 上
class ClassName:
    语句块

2.2 类对象及类属性

  • 1 类对象,类的定义执行后会生成一个类对象
  • 2 类的属性,类定义中的变量和类中定义的方法都是类的属性
  • 3 类变量,直接定义再类中的属性;一般来讲,类变量可使用全大写来命名

2.3 类的实例化

  • 1 在类对象名称后面加上一个括号,就是调用类的实例化方法,完成实例化
  • 2 实例化就是真正的创建一个该类的对象(实例)
  • 3 每次实例化后获得的实例,都是不同的实例
  • 4 即使是使用同样的参数实例化,也得到不一样的对象
  • 5 Python 类实例化后,会自动调用 __init__ 方法,这个方法的第一个形式参数必须留给 self,其它参数随意

2.4 __init__ 方法

  • 1 类的实例化,实际上就是调用的 __init__(self) 方法
  • 2 这个方法可以不定义,如果没有定义会在实例化后隐式调用
  • 3 作用:对实例进行初始化
  • 4 初始化参数可以多个参数,但是第一个位置必须是 self
  • 5 此方法不能有返回值,也就是只能 return None
  • 6 实例化调用__init__魔术方法来进行初始化-出厂配置,对生成的实例进行属性配置,__init__ 定义的是实例的属性

2.5 实例对象 instance

  • 1 类实例化后一定会获得一个类的实例,就是 实例对象
  • 2 __init__ 方法的第一参数 self 就是指代某一个实例自身
  • 3 类实例化后,得到一个实例对象,调用类方法时,采用 instance.class_method() 的方式,实例对象就会绑定到方法上
  • 4 调用类方法时,类方法的 self 参数,就是 实例自身
  • 5 __init__ 中所定义的变量,是保存在 实例 上的,并不是 类 上,所以,称为 实例变量
  • 6 实例变量是每一个实例自己的变量,是自己独有的
  • 5 类变量是类的变量,是类的所有实例共享的属性和方法

2.6 示例 1

class MyClass:
    """My first class"""
    print('in class')
    xx = 'abc'   # 类的属性
    
    def __init__(self, x, y):
        print('in init')
        self.x = x
        self.y = y
    
    def foo(self):
        print('in foo')
        return "My class"
    
print(MyClass.xx, MyClass.foo, MyClass.__doc__)
myc1 = MyClass(1, 2)
myc1 = MyClass(1, 2)
print(myc1.foo())
print(MyClass.foo(myc1))

# 上例中 xx 和 foo 都是类的属性,__doc__ 也是类的特殊属性
# foo 是 方法method,本质上就是普通函数对象 function
# 第一个形式参数 self,指代当前实例本身
in class
abc <function MyClass.foo at 0x000002A221E76F70> My first class
in init
in init
in foo
My class
in foo
My class

2.7 示例 2

# self
class MyClass:
    def __init__(self):
        print(1, 'self in init = {}'.format(id(self)))
        
    def showself(self):
        print(2, 'self in showself() = {}'.format(id(self)))

c = MyClass()
print('After instance c')
print(3, 'c = {}'.format(id(c)))
print('=' * 30)
c.showself()
print(MyClass.showself)
print(c.showself)

# 类实例化后,得到一个实例对象,调用类方法时,采用 instance.class_method() 的方式,实例对象就会绑定到方法
# self 就是调用者,self 这个名字只是一个惯例,可以修改,但最好不要修改
# 查看打印结果,思考一下代码的执行顺序
1 self in init = 2895381183888
After instance c
3 c = 2895381183888
==============================
2 self in showself() = 2895381183888
<function MyClass.showself at 0x000002A221D851F0>
<bound method MyClass.showself of <__main__.MyClass object at 0x000002A2222ABD90>>

2.8 示例 3

# 实例变量和类变量
# 实例变量是每一个实例自己的变量,是自己独有的
# 类变量是类的变量,是类的所有实例共享的属性和方法

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

tom  = Person('Tom')
jerry = Person('Jerry')
print(tom.name, jerry.name)
print(tom.age, jerry.age)
print(Person.age)
Person.age = 30
print(Person.age,  tom.age, jerry.age)
Tom Jerry
3 3
3
30 30 30

3、对象属性

3.1属性介绍

  • 1 特殊属性 __name__ __class__ __dict__ __qualname__

  • 2 Python 中每一种对象都拥有不同的属性

  • 3 函数、类都是对象,类的实例也是对象

  • 4 类属性保存在类的__dict__中,实例属性保存在实例的__dict__

  • 5 属性是类的,也是这个类所有实例的,其它实例都可以访问到

  • 6 属性是实例的,就是这个实例自己的,通过类访问不到

  • 7 对象(实例或类)可以动态的给自己增加一个属性(赋值即定义一个新属性)

  • 8 实例.__dict__[变量名]实例.变量名 都可以访问到实例自己的属性(注意这两种访问是有本质区别的)

  • 9 实例的同名变量会隐藏掉类变量,或者说是覆盖了这个类变量,但是注意类变量还在那里,并没有真正的被覆盖

  • 10 类方法(也是类属性)在定义时,第一个参数必须时 self,而 self 必须指向一个对象,也就是类实例化之后,由实例来调用这个方法

    class Person:
        pass
    
    Person.__name__  # 'Person'
    Person.__class__.__name__, type(Person).__name__  # ('type', 'type')
    # 结果是字符串
    type(Person), Person.__class__  # (type, type)
    # 结果是类
    

3.2 实例属性的查找顺序

  • 1 实例使用 .点号 来访问属性,会先找自己的 __dict__,如果没有,再通过属性 __class__ 找到自己的类,再去类的 __dict__ 中找
  • 2 如果实例使用 __dict__[变量名] 来访问变量,就不会按照上面的查找顺序找变量了,这是指明使用字典的 key 查找,不是属性查找

3.3 示例 1

class Person:
    age = 3
    def __init__(self, name):
        self.name = name
        
print('----- class -----')
print(Person.__class__, type(Person))
print(sorted(Person.__dict__.items()))
print('=' * 66)
tom = Person('Tom')
print(tom.__class__, type(tom))
print(sorted(tom.__dict__.items()))
print('=' * 66)
print(tom.__class__.__name__)
print(sorted(tom.__class__.__dict__.items()))
print(sorted(type(tom).__dict__.items()))
----- class -----
<class 'type'> <class 'type'>
[('__dict__', <attribute '__dict__' of 'Person' objects>), ('__doc__', None), ('__init__', <function Person.__init__ at 0x000002A2221BB790>), ('__module__', '__main__'), ('__weakref__', <attribute '__weakref__' of 'Person' objects>), ('age', 3)]
==================================================================
<class '__main__.Person'> <class '__main__.Person'>
[('name', 'Tom')]
==================================================================
Person
[('__dict__', <attribute '__dict__' of 'Person' objects>), ('__doc__', None), ('__init__', <function Person.__init__ at 0x000002A2221BB790>), ('__module__', '__main__'), ('__weakref__', <attribute '__weakref__' of 'Person' objects>), ('age', 3)]
[('__dict__', <attribute '__dict__' of 'Person' objects>), ('__doc__', None), ('__init__', <function Person.__init__ at 0x000002A2221BB790>), ('__module__', '__main__'), ('__weakref__', <attribute '__weakref__' of 'Person' objects>), ('age', 3)]

3.4 示例 2

class Person:
    age = 3
    height = 170
    
    def __init__(self, name, age=18):
        self.name = name
        self.age = age
    
tom = Person('Tom')
jerry = Person('Jerry', 20)

Person.age = 30
print(Person.age, tom.age, jerry.age)  # 30 18 20
print(Person.height, tom.height, jerry.height)  # 170 170 170
print(jerry.__dict__)  # {'name': 'Jerry', 'age': 20}
jerry.height = 175  
print(Person.height, tom.height, jerry.height)  # 170 170 175
print(jerry.__dict__)  # {'name': 'Jerry', 'age': 20, 'height': 175}
tom.height += 10
print(Person.height, tom.height, jerry.height)  # 170 180 175
Person.height += 15
print(Person.height, tom.height, jerry.height)  # 185 180 175
Person.weight = 70
print(Person.weight, tom.weight, jerry.weight)  # 70 70 70
print(tom.__dict__) # {'name': 'Tom', 'age': 18, 'height': 180}
print(tom.__class__.__dict__['weight'])  # 70
print(tom.weight)  # 70
# print(tom.__dict__['weight'])  # KeyError: 'weight'

3.5 示例 3

class MyClass:
    def normal_method():
        print('Normal Method')

MyClass.normal_method()  # Normal Method
# MyClass().normal_method()  # normal_method() takes 0 positional arguments but 1 was given
# 由于 normal_method 在定义的时候没有指定 self,所以不能完成实例对象的绑定
# 不能用 MyClass().normal_method() 调用
# 严禁这么使用,做实验只是为了加深大家对 类 的理解

4、类方法 和 静态方法

4.1 类方法

  • 1 在定义中,使用 @classmethod 装饰器修饰的方法
  • 2 必须至少有一个参数,且第一个参数留给了 clscls 指代调用者即类对象自身
  • 3 cls 这个标识符可以是任意合法名称,但是为了易读,请不要修改
  • 4 通过 cls 可以直接操作类的属性
  • 5 无法通过 cls 操作类的实例

4.2 静态方法

  • 1 在类定义中,使用 @staticmethod 装饰器修饰的方法
  • 2 调用时,不会隐式的传入参数
  • 3 静态方法,只是表明这个方法属于这个名词空间。函数归在一起,方便组织管理

4.3 总结

  • 1 类几乎可以调用所有内部定义的方法,但是调用普通的方法 时会报错,原因是第一参数必须是类的实例
  • 2 实例几乎也可以调用所有的方法,普通的函数的调用一般不可能出现,因为不允许这么定义
  • 3 类除了普通方法都可以调用,普通方法需要对象的实例作为第一参数
  • 4 实例可以调用所有类中定义的方法(包括类方法/静态方法),普通方法传入实例自身,静态方法和类方法需要找到实例的类

4.4 示例 1

class Person:
    def method(self):
        print("{}'s method".format(self))
    
    @classmethod
    def class_method(cls):
        print('class = {0.__name__}({0})'.format(cls))
        cls.HEIGHT = 170
    
    @staticmethod
    def static_method():
        print(Person.HEIGHT)
    
# 类访问
# print(Person.method()) # method() missing 1 required positional argument: 'self'
print(Person.class_method())  # class = Person(<class '__main__.Person'>)  None
print(Person.static_method())  # 170 None
print(Person.__dict__)
# {'__module__': '__main__', 'method': <function Person.method at 0x000002A2220C34C0>, 'class_method': <classmethod object at 0x000002A22294BB80>, 'static_method': <staticmethod object at 0x000002A22294B340>, '__dict__': <attribute '__dict__' of 'Person' objects>, '__weakref__': <attribute '__weakref__' of 'Person' objects>, '__doc__': None, 'HEIGHT': 170}

tom = Person()
print(tom.method())  # <__main__.Person object at 0x000002A222314970>'s method  None
print(tom.class_method())  # class = Person(<class '__main__.Person'>)  None
print(tom.static_method())  # 170  None

5、访问控制

  • 私有属性:使用双下划线开头的属性名;类定义的时候,如果声明一个实例变量的时候,使用双下划线,Python解释器会将其改名,转换名称为 _类名__变量名的名称。【注意,隐藏属性改名的只和当前类有关系,设置私有属性的当前类,在谁里面改谁的名】
  • 保护属性:在变量名前使用一个下划线,保护属性没有改变名称,和普通属性一样,解释器不做任何特殊处理。看见这种变量,就如同私有变量,不要直接使用
  • 私有方法:单下划线的方法只是开发者之间的约定,解释器不做任何改变。双下划线会改变名称,同属性一样
  • Python中使用单 双下划线来标识一个成员被保护或者被私有化隐藏起来,不要万不得已,不要修改

5.1 示例 1

class Person:
    def __init__(self, name, age=10):
        self.name = name
        self.__age = age
    
    def growup(self, i=1):
        if i > 0 and i < 100:
            self.__age += i
    
    def showage(self):
        return self.__age
        
p1 = Person('Tom')
print(p1.__dict__)  # {'name': 'Tom', '_Person__age': 10}
p1.growup(55)
print(p1.__dict__)  # {'name': 'Tom', '_Person__age': 65}
# print(p1.__age)  #  'Person' object has no attribute '__age'
print(p1._Person__age)  # 65
print(p1.showage())  # 65  这是正确访问隐藏属性的方法
p1.__age = 100
print(p1.showage())  # 65
print(p1.__age)  # 100
print(p1.__dict__)  # {'name': 'Tom', '_Person__age': 65, '__age': 100}

5.2 示例 2

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

tom = Person('Tom')
# print(tom.age)  # 'Person' object has no attribute 'age'
print(tom.__dict__)  # {'name': 'Tom', '_age': 18}
print(tom._age)  # 18

6、属性装饰器

  • 属性装饰器:使用property装饰器的时候,方法要同名
  • 使用了属性装饰器时,函数方法就会变为属性,可以直接使用. + 属性名称进行访问
  • 1 property装饰器:后面跟的函数名就是以后的属性名,它就是getter,这个必须有,有了它至少是只读属性
  • 2 setter装饰器:与属性名同名,且接收两个参数,第一个是self,第二个是将要赋值的值,有了它,属性可写
  • 3 deleter装饰器:可以控制是否删除属性,很少用
  • 4 property装饰器必须在前,setter deleter装饰器在后
  • 5 property装饰器能通过简单的方式,把对方法的操作变成对属性的访问,并起到了一定隐藏结果
class A:
    def test(self):
        print('test 1')
 
A().test()
# test 1
class A:
    @property
    def test(self):
        print('test 1')
 
# A().test()  # 'NoneType' object is not callable
A().test
# test 1

6.1 示例 1

class Person:
    def __init__(self, name, age=18):
        self.name = name
        self.__age = age
        
    @property
    def age(self):
        print('getter')
        return self.__age
    
    @age.setter
    def age(self, age):
        print('setter')
        self.__age = age
    
    @age.deleter
    def age(self):
        print('deleter')
        del self.__age
        
tom = Person('Tom')
print(tom.age)  # getter  18
tom.age = 22  # setter
print(tom.age)  # getter  18
print(tom.__dict__)  # {'name': 'Tom', '_Person__age': 22}
del tom.age  # deleter
print(tom.__dict__)  # {'name': 'Tom'}

6.2 示例 2

class Person:
    def __init__(self, name, age=18):
        self.name = name
        self.__age = age
        
    def getage(self):
        print('getter')
        return self.__age
    
    def setage(self, age):
        print('setter')
        self.__age = age
    
    def delage(self):
        print('deleter')
        del self.__age
    
    age = property(getage, setage, delage, 'age property')
    
tom = Person('Tom')
print(tom.age)  # getter  18
tom.age = 22  # setter
print(tom.age)  # getter  18
print(tom.__dict__)  # {'name': 'Tom', '_Person__age': 22}
del tom.age  # deleter
print(tom.__dict__)  # {'name': 'Tom'}

7、对象的销毁

  • 类中可以定义 __del__ 方法,称为析构函数(方法)。
  • 作用:销毁类的实例的时候调用,以释放占用的资源。其中就放些清理资源的代码,比如释放链接。
  • 注意这个方法不能引起对象的真正的销毁,只是对象销毁的时候会自动调用它。
  • 使用del语句删除实例,引用计数减1,当引用计数为0时,会自动调用 __del__ 方法。
  • 由于Python实现了垃圾回收机制,不能确定对象何时执行垃圾回收。
  • 由于垃圾回收对象销毁时,才会真正清理对象,还会再回收对象之前自动调用 __del__ 方法,除非你明确知道自己的目的,否则不要手动调用这个方法
import time

class Person:
    def __init__(self, name, age=18):
        self.name = name
        self.__age = age
        
    def __del__(self):
        print('delete {}'.format(self.name))


tom = Person('tom')
tom.__del__()
tom.__del__()
tom.name
tom2 = tom
tom3 = tom2
print(tom, tom2, tom3)
del tom
time.sleep(3)
del tom2
print('=' * 33)
del tom3
delete tom
delete tom
<__main__.Person object at 0x000002A221A59F10> <__main__.Person object at 0x000002A221A59F10> <__main__.Person object at 0x000002A221A59F10>
=================================
delete tom

8、__init____del__

  • 构造方法__init__,具有初始化的作用,也就是当该类被实例化的时候就会自动执行该函数。那么通常就可以把要先初始化的属性放到这个方法里面
  • 析构方法__del__是对象在被垃圾回收的时候起作用的一个方法,它的执行一般也就意味着对象不能够继续引用, 回收内存

8.1 示例 1

运行脚本结束后,系统会自动执行析构函数,所以 ‘del method’ 晚于 'The last line code in the test5.py' 打印。

class Person:

    def __init__(self):
        print('init method')

    def __del__(self):
        print('del method')

p = Person()
print('The last line code in the test5.py')
D:\PycharmProjects\pythonProject\202108089\venv\Scripts\python.exe D:/PycharmProjects/pythonProject/202108089/test5.py
init method
The last line code in the test5.py
del method

Process finished with exit code 0

8.2 示例 2

del 语句执行时,内存立即被回收,所以 ‘del method’ 早于 'The last line code in the test6.py' 打印

class Person:

    def __init__(self):
        print('init method')

    def __del__(self):
        print('del method')

p = Person()
del p
print('The last line code in the test6.py')
D:\PycharmProjects\pythonProject\202108089\venv\Scripts\python.exe D:/PycharmProjects/pythonProject/202108089/test6.py
init method
del method
The last line code in the test6.py

Process finished with exit code 0

9、Class 总结

  • 类是抽象的,对象是具体的,类就是属性和方法的集合。属性 一般使用数据结构来保存,方法 操作或者动作或者能力

  • 类对象是由type构造出来的对象,其实类对象也是一个实例,由type创造出来的实例(涉及到元编程,后续在更新)

    class MyClass:
        pass
    
    A = MyClass()
    
    type(MyClass), type(A)  # (type, __main__.MyClass)
    type(int), type(str)  # (type, type)  
    # int str 都是类对象,自定义类请全使用大驼峰命名方式
    
  • 类属性和类变量 可以使用 . 来进行获取,每次实例化都会产生不同的对象,单例模式除外

  • 类方法:不管是实例调用,还是类调用,都会自动把类注入为第一参数;换言之,第一个参数永远是cls,如果写为别的参数名称或者不写参数时,实际运行时,也会把类作为第一个参数

  • 静态方法:可以不需要任何参数,类和实例都可以调用;如果有类似需求,可以使用静态方法(静态方法装饰器会阻止任何参数注入

  • 普通的类方法(带self,也就是有参):如果有参数的话,第一个参数肯定是self,如果写为别的参数名称,实际运行时,如果是实例,会注入实例本身self作为参数;如果是类调用,则是任何参数就行;因为实例有绑定效果,类没有绑定;普通的类方法(不带self,也就是无参):语法可以,但是禁止这么使用,如果有无参方法需求的话,请使用静态方法

  • 为了大家便于查阅代码,self cls 等参数,大家不要随意替换。

class MyClass:
    
    def showmyclass(self):
        print(id(self))

A = MyClass()
B = MyClass()

MyClass.showmyclass
# <function __main__.MyClass.showmyclass(self)>
A.showmyclass  # 实例对象和self绑定在一起,在调用时,是不需要输入self参数
# <bound method MyClass.showmyclass of <__main__.MyClass object at 0x0000017DB2795DF0>>

# MyClass.showmyclass()  # showmyclass() missing 1 required positional argument: 'self'
A.showmyclass()  # 1639376838128
MyClass.showmyclass(A)  # 1639376838128
  • 31
    点赞
  • 221
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值