python:类

2023-11-11

类与对象

Python从设计之初就已经是一门面向对象的语言。

面向对象编程OOP是一种程序设计思想。它把对象作为程序的基本单元,一个对象包含了数据和操作数据的函数。

  • 面向过程的程序设计把计算机程序视为一系列的命令集合,即一组函数的顺序执行。为了简化程序设计,面向过程把函数继续切分为子函数,即把大块函数通过切割成小块函数来降低系统的复杂度。
  • 而面向对象的程序设计把计算机程序视为一组对象的集合,而每个对象都可以接收其他对象发过来的消息,并处理这些消息,计算机程序的执行就是一系列消息在各个对象之间传递。

在Python中,所有数据类型都可以视为对象,当然也可以自定义对象。自定义的对象数据类型就是面向对象中的类(Class)的概念。

面向对象最重要的概念就是类(Class)和实例(Instance):

  • 类是创建实例的模板,而实例则是一个一个具体的对象,各个实例拥有的数据都互相独立,互不影响;
  • 方法就是与实例绑定的函数,和普通函数不同,方法可以直接访问实例的数据;

高级特性

@property装饰器

  • 在绑定属性时,如果我们直接把属性暴露出去,虽然写起来很简单,但是,没办法检查参数,导致可以把成绩随便改:
s = Student()
s.score = 9999
  • 这显然不合逻辑。为了限制score的范围,可以通过一个set_score()方法来设置成绩,再通过一个get_score()来获取成绩,这样,在set_score()方法里,就可以检查参数:
class Student(object):

    def get_score(self):
         return self._score

    def set_score(self, value):
        if not isinstance(value, int):
            raise ValueError('score must be an integer!')
        if value < 0 or value > 100:
            raise ValueError('score must between 0 ~ 100!')
        self._score = value
  • 现在,对任意的Student实例进行操作,就不能随心所欲地设置score了:
>>> s = Student()
>>> s.set_score(60) # ok!
>>> s.get_score()
60
>>> s.set_score(9999)
Traceback (most recent call last):
  ...
ValueError: score must between 0 ~ 100!
  • 但是,上面的调用方法又略显复杂,没有直接用属性这么直接简单。有没有既能检查参数,又可以用类似属性这样简单的方式来访问类的变量呢?

  • 建议是将属性命名以单下划线开头,通过这种方式来暗示属性是受保护的,不建议外界直接访问,如果想访问属性可以通过属性的getter(访问器)和setter(修改器)方法进行对应的操作。如果要做到这点,就可以考虑使用@property包装器来包装getter和setter方法,使得对属性的访问既安全又方便

class Person(object):

    def __init__(self, name, age):
        self._name = name
        self._age = age

    # 访问器 - getter方法
    @property
    def name(self):
        return self._name

    # 访问器 - getter方法
    @property
    def age(self):
        return self._age

    #  setter方法的装饰器:@属性名.setter
    @age.setter
    def age(self, age):
        self._age = age

    def play(self):
        if self._age <= 16:
            print('%s正在玩飞行棋.' % self._name)
        else:
            print('%s正在玩斗地主.' % self._name)


def main():
    person = Person('王大锤', 12)
    person.play()
    person.age = 22
    person.play()
    # person.name = '白元芳'  # AttributeError: can't set attribute


if __name__ == '__main__':
    main()
  • 要特别注意:属性的方法名不要和实例变量重名。例如,以下的代码是错误的:
class Student(object):

    # 方法名称和实例变量均为birth:
    @property
    def birth(self):
        return self.birth
  • 这是因为调用s.birth时,首先转换为方法调用,在执行return self.birth时,又视为访问self的属性,于是又转换为方法调用,造成无限递归,最终导致栈溢出报错RecursionError。

__slots__限定

Python是一门动态语言。

  • 通常,动态语言允许我们在程序运行时给对象绑定新的属性或方法,当然也可以对已经绑定的属性和方法进行解绑定。
  • 但是如果我们需要限定自定义类型的对象只能绑定某些属性,可以通过在类中定义__slots__变量来进行限定。
  • 需要注意的是__slots__的限定只对当前类的对象生效,对子类并不起任何作用。
class Person(object):

    # 限定Person对象只能绑定_name, _age和_gender属性
    __slots__ = ('_name', '_age', '_gender')

    def __init__(self, name, age):
        self._name = name
        self._age = age

    @property
    def name(self):
        return self._name

    @property
    def age(self):
        return self._age

    @age.setter
    def age(self, age):
        self._age = age

    def play(self):
        if self._age <= 16:
            print('%s正在玩飞行棋.' % self._name)
        else:
            print('%s正在玩斗地主.' % self._name)


def main():
    person = Person('王大锤', 22)
    person.play()
    person._gender = '男'
    # AttributeError: 'Person' object has no attribute '_is_gay'
    # person._is_gay = True

类方法、静态方法

from math import sqrt


class Triangle(object):

    def __init__(self, a, b, c):
        self._a = a
        self._b = b
        self._c = c

    @staticmethod  #表示静态方法
    def is_valid(a, b, c):
        return a + b > c and b + c > a and a + c > b

    def perimeter(self):
        return self._a + self._b + self._c

    def area(self):
        half = self.perimeter() / 2
        return sqrt(half * (half - self._a) *
                    (half - self._b) * (half - self._c))


def main():
    a, b, c = 3, 4, 5
    # 静态方法和类方法都是通过给类发消息来调用的
    if Triangle.is_valid(a, b, c):  
        t = Triangle(a, b, c)
        print(t.perimeter())
        # 也可以通过给类发消息来调用对象方法但是要传入接收消息的对象作为参数
        # print(Triangle.perimeter(t))
        print(t.area())
        # print(Triangle.area(t))
    else:
        print('无法构成三角形.')


if __name__ == '__main__':
    main()

类方法是类对象所拥有的方法:

  • 需要用修饰器@classmethod来标识其为类方法
  • 对于类方法,第一个参数必须是类对象,一般以cls作为第一个参数(当然可以用其他名称的变量作为其第一个参数,但是大部分人都习惯以’cls’作为第一个参数的名字,就最好用’cls’了)
  • 能够通过实例对象和类对象去访问。

枚举类

当我们需要定义常量时,一个办法是用大写变量通过整数来定义,例如月份:

JAN = 1
FEB = 2
MAR = 3
...
NOV = 11
DEC = 12

好处是简单,缺点是类型是int,并且仍然是变量。

更好的方法是为这样的枚举类型定义一个class类型,然后,每个常量都是class的一个唯一实例。Python提供了Enum类来实现这个功能:

from enum import Enum

Month = Enum('Month', ('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'))

如果需要更精确地控制枚举类型,可以从Enum派生出自定义类:

from enum import Enum, unique

@unique
class Weekday(Enum):
    Sun = 0 # Sun的value被设定为0
    Mon = 1
    Tue = 2
    Wed = 3
    Thu = 4
    Fri = 5
    Sat = 6

@unique装饰器可以帮助我们检查保证没有重复值。

访问这些枚举类型可以有若干种方法:

>>> day1 = Weekday.Mon
>>> print(day1)
Weekday.Mon
>>> print(Weekday.Tue)
Weekday.Tue
>>> print(Weekday['Tue'])
Weekday.Tue
>>> print(Weekday.Tue.value)
2
>>> print(day1 == Weekday.Mon)
True
>>> print(day1 == Weekday.Tue)
False
>>> print(Weekday(1))
Weekday.Mon
>>> print(day1 == Weekday(1))
True
>>> Weekday(7)
Traceback (most recent call last):
  ...
ValueError: 7 is not a valid Weekday
>>> for name, member in Weekday.__members__.items():
...     print(name, '=>', member)
...
Sun => Weekday.Sun
Mon => Weekday.Mon
Tue => Weekday.Tue
Wed => Weekday.Wed
Thu => Weekday.Thu
Fri => Weekday.Fri
Sat => Weekday.Sat

元类

type()

动态语言和静态语言最大的不同,就是函数和类的定义,不是编译时定义的,而是运行时动态创建的。

比方说我们要定义一个Hello的class,就写一个hello.py模块:

class Hello(object):
    def hello(self, name='world'):
        print('Hello, %s.' % name)

当Python解释器载入hello模块时,就会依次执行该模块的所有语句,执行结果就是动态创建出一个Hello的class对象,测试如下:

>>> from hello import Hello
>>> h = Hello()
>>> h.hello()
Hello, world.
>>> print(type(Hello))
<class 'type'>
>>> print(type(h))
<class 'hello.Hello'>

type()函数可以查看一个类型或变量的类型,Hello是一个class,它的类型就是type,而h是一个实例,它的类型就是class Hello。

我们说class的定义是运行时动态创建的,而创建class的方法就是使用type()函数。

type()函数既可以返回一个对象的类型,又可以创建出新的类型,比如,我们可以通过type()函数创建出Hello类,而无需通过class Hello(object)…的定义:

>>> def fn(self, name='world'): # 先定义函数
...     print('Hello, %s.' % name)
...
>>> Hello = type('Hello', (object,), dict(hello=fn)) # 创建Hello class
>>> h = Hello()
>>> h.hello()
Hello, world.
>>> print(type(Hello))
<class 'type'>
>>> print(type(h))
<class '__main__.Hello'>

要创建一个class对象,type()函数依次传入3个参数:

  • class的名称;
  • 继承的父类集合,注意Python支持多重继承,如果只有一个父类,别忘了tuple的单元素写法;
  • class的方法名称与函数绑定,这里我们把函数fn绑定到方法名hello上。

通过type()函数创建的类和直接写class是完全一样的,因为Python解释器遇到class定义时,仅仅是扫描一下class定义的语法,然后调用type()函数创建出class。

正常情况下,我们都用class Xxx…来定义类,但是,type()函数也允许我们动态创建出类来,也就是说,动态语言本身支持运行期动态创建类,这和静态语言有非常大的不同,要在静态语言运行期创建类,必须构造源代码字符串再调用编译器,或者借助一些工具生成字节码实现,本质上都是动态编译,会非常复杂。

metaclass

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

python:类 的相关文章

随机推荐

  • 旋转矩阵中6保6_双色球旋转矩阵公式中6保4的

    满意答案 liuerxing 推荐于 2017 10 10 采纳率 51 等级 12 已帮助 6602人 双色球旋转矩阵公式中6保4的选法共是38注 1 2 3 4 5 6 7 8 9 10 11 12 代表选的数字 1 6 8 9 10
  • 4种GC方法+分代回收+触发GC情况+内存申请过程

    引用计数 对象增加一个引用时 引用数 1 减少一个时 引用数 1 当进行垃圾回收时 只回收引用数为0的对象 面对互相引用无解 方法比较老 基本弃用 标记 清除 从根节点遍历标记对象 然后遍历整个堆 清除没有标记的对象 缺点 运行效率不高 产
  • Odoo多公司指南

    启动环境 首先新建一个odoo12环境 并在应用列表中搜索sales并安装 开启多公司功能 打开settings页面 然后点击点击General Settings 接着再勾选Multi companies之后点击保存 页面会重新加载 再次回
  • MSSQL更改数据库文件路径

    MSSQL 数据库文件变更目录 1 变更数据库文件指向 2 关闭数据库 3 移动数据库文件 4 给文件和目录添加NT Service MSSQLSERVER用户的完全控制权限 5 启动数据库 6 注意事项 1 变更数据库文件指向 查到真实名
  • 智慧门店对实体商家的价值始于连接和引流

    刷脸成为了现实 各地实行刷脸的试点都取得成功 刷脸也就这样的走进我们的生活 不需要带手机 钱包 直接选择刷脸 当然 方便的还是无须等待排队 原本一个收银台需要配备一名收银员 所以考虑人力成本收银柜台数量是有限的 但是使用刷脸支付 商家可以同
  • 顺序主子式的英文翻译(定义)

    顺序主子式的英文翻译 定义 为了查明顺序主子式的英文翻译 我在国内知网翻译助手 金山词霸等诸多翻译系统查了一下 给出的答案不外乎以下几个答案 知网翻译助手的答案 我喜欢刨根问底 很明显这样的答案就是中式英语 经过一番努力好 终于找到正确答案
  • Qt 在主界面程序中,调用子界面(另一个界面)的控件

    问题 在主界面程序mainwindow cpp中 想调用子界面 另一个界面 上的控件 已经在 pro工程中添加好了一个设计师界面类作为子界面 这里我在工程中添加的是show netlist dialog cpp h ui 解决步骤 1 在子
  • MYSQL查询一对多的数据表关联,产生重复数据怎么处理

    在 MySQL 中 当进行一对多的数据表关联查询时 有时会导致结果中出现重复数据的情况 这是由于多个关联的子表记录与主表记录进行了笛卡尔积 从而产生了重复的结果 为了处理这种情况 可以使用以下方法之一 使用DISTINCT关键字 可以在查询
  • micropython Esp32 外接LED使用Thonny ValueError: pin can only be input解决办法

    micropython Esp32 外接LED使用Thonny ValueError pin can only be input解决办法 1 连接方式如下图 注意一定要加电阻 2 通过输入以下代码 报错 ValueError pin can
  • 关于vscode调试php

    1 PHP 5 4 0起 CLI SAPI 提供了一个内置的Web服务器 URI请求会被发送到PHP所在的的工作目录 Working Directory 进行处理 除非你使用了 t参数来自定义不同的目录 如果请求未指定执行哪个PHP文件 则
  • 【查询代码提交数】

    后端 shell git log all since 2021 10 01 until 2021 12 31 format aN sort u while read name do echo en name t git log all si
  • draw.io基础使用

    转自 Draw io 一款强大且支持在线编辑和到处的画图软件 转自 https blog csdn net feeltouch article details 105476275
  • Buildroot笔记

    CSDN仅用于增加百度收录权重 排版未优化 日常不维护 请访问 www hceng cn 查看 评论 本博文对应地址 https hceng cn 2019 09 05 Buildroot E7 AC 94 E8 AE B0 more 整理
  • 三菱系统四轴正反转参数_三菱M70四轴调试

    三菱M70四轴安装 放大器 MDS D SVJ3 10 马达型号 HF104 1 放大器的旋钮开关 SW1 X 0 Y 1 Z 2 4轴 3 主轴 4 注 3合一的放大器 4轴 4 2 参数设置 先输入参数修改密码 MPARA 1 基本规格
  • cookie 封装

    npm i universal cookie npm i vueuse integrations 1 新建 utils storage js import useCookies from vueuse integrations useCoo
  • 若依框架——使用自定义用户表登录系统

    修改数据库配置 修改登录用户表 原JavaBean package com ruoyi common core domain entity import java util Date import java util List import
  • 很有道理的十句话

    第一句如果我们之间有1000 步的距离 你只要跨出第1 步我就会朝你的方向走其余的 999步 第二句 通常愿意留下来跟你争吵的人 才是真正爱你的人 第三句付出真心 才会得到真心 却也可能伤得彻底保持距离 就能保护自己 却也注定永远寂寞 第四
  • antd上传组件使用fileList属性展示图片,onchage事件只会执行一次的问题

    在工作中使用到了antd的照片墙组件时 遇到了官方文档上提出的一个问题 然而官方的解答是回退版本 看了github上网友留言 加上自己测试 找到一种解决方式 一定要在判断 等于 uploading状态的时候进行一次setState 之后在d
  • 通过华为杯竞赛、高教社杯和数学建模国赛实现逆袭;助力名利双收

    文章目录 赛事介绍 参赛好处 辅导比赛 写在最后 赛事介绍 华为杯全国研究生数学建模竞赛是由华为公司主办的一项面向全国研究生的数学建模竞赛 该竞赛旨在通过实际问题的建模和解决 培养研究生的创新能力和团队合作精神 推动科技创新和应用 华为杯竞
  • python:类

    类与对象 Python从设计之初就已经是一门面向对象的语言 面向对象编程OOP是一种程序设计思想 它把对象作为程序的基本单元 一个对象包含了数据和操作数据的函数 面向过程的程序设计把计算机程序视为一系列的命令集合 即一组函数的顺序执行 为了