Python学习笔记(五) 类与对象

简介: Python学习笔记(五) 类与对象

这篇文章介绍有关Python 类中一些常被大家忽略的知识点,帮助大家更全面地掌握 Python



1、类方法


在类中定义的 实例方法 ,传入的第一个参数多为 self,这个 self 究竟是什么呢

其实 Python 中的 self 就相当于 C++ 中的 this,指向调用该方法的实例化对象

class Person():
    def __init__(self, name):
        self.name = name
    def set_name(self, name):
        self.name = name
    def get_name(self):
        return self.name
person = Person('Jack')
print(person.get_name()) # Jack
person.set_name('Dango')
print(person.get_name()) # Dango

一般情况下,类中的方法都应该加上参数 self,但是也并非全部如此,例如 类方法

该方法传入的第一个参数是 cls,代表类本身,这样就能在该方法访问类中的属性和方法

class Person():
    instanceNum = 0
    def __init__(self, name):
        self.name = name
    @classmethod
    def create(cls, name):
        cls.instanceNum += 1
        return cls(name = name)
    @classmethod
    def getInstNum(cls):
        return cls.instanceNum
# 类方法既能通过实例对象调用,也能通过类本身调用
person1 = Person.create('Jack')
print(Person.getInstNum()) # 1
person2 = Person.create('Dango')
print(Person.getInstNum()) # 2

还有一类方法,既不需要使用 self 也不需要使用 cls,这类方法就是 静态方法

这些方法一般与类相关,但在实现时并不需要引用类或实例

STATUS = 'SUCCESS'
class Person():
    def __init__(self, name):
        self.name = name
    def get_name(self):
        return self.name
    @staticmethod
    def check(status):
        if status == 'SUCCESS':
            return True
        else:
            return False
person = Person('Jack')
if Person.check(STATUS):
    print(person.get_name()) # Jack
else:
    print('Permission Denied')


2、内置方法


  • == 比较两对象是否 相等
  • is 比较两对象是否 相同
a = [1, 2, 3]
b = [1, 2, 3]
# 比较两个对象的值是否相等
print(a == b) # True
# 比较两个对象在内存的位置是否相同
print(a is b) # False
  • issubclass(class, classinfo)  :检查 class   是否为 classinfo 的子类
  • isinstance(object, classinfo):检查 object 是否为 classinfo 的实例对象
class Person():
    def __init__(self, name):
        self.name = name
class Worker(Person):
    def __init__(self, name, wid):
        super(Worker, self).__init__(name)
        self.wid = wid
person = Person('Dango')
worker = Worker('Jack', 202101)
# 函数 issubclass 可以检查 `class`  是否为 `classinfo` 的子类
# 参数 class  一定要是类,参数 classinfo 可以是一个类也可以是由多个类组成的元组
print(issubclass(Worker, Person)) # True
print(issubclass(Person, Worker)) # False
# 所有类都被认为是自身的子类
print(issubclass(Worker, Worker)) # True
print(issubclass(Person, Person)) # True
# 所有类都被认为是 `object` 的子类
print(issubclass(Person, object)) # True
print(issubclass(Worker, object)) # True
# 函数 isinstance 可以检查 `object` 是否为 `classinfo` 的实例对象
# 参数 object 必须是对象,参数 classinfo 可以是一个类也可以是由多个类组成的元组
print(isinstance(worker, Worker)) # True
print(isinstance(worker, Person)) # True
print(isinstance(person, Worker)) # False
print(isinstance(person, Person)) # True
# 如果 object 不是对象类型,直接返回 `False`
print(isinstance(Worker, Worker)) # False
print(isinstance(Person, Person)) # False
  • hasattr(object, name):检查 object 中是否有属性 name
  • getattr(object, name [,default]):获取 object 中特定属性 name 的值
  • setattr(object, name, value):设置 object 中特定属性 name 的值
  • delattr(object, name):删除 object 中特定属性 name 及其值
class Person():
    def __init__(self, name):
        self.name = name
person = Person('Jack')
# 函数 hasattr 可以检查 `object` 中是否有属性 `name`
print(hasattr(person, 'name')) # True
print(hasattr(person, 'wid'))  # False
# 函数 getattr 可以获取 `object` 中特定属性 `name` 的值
# 如果属性 name 不存在,则抛出异常 (没设置 `default` 属性) 或返回指定值 (有设置 `default` 属性)
print(getattr(person, 'name'))   # Jack
print(getattr(person, 'wid'))    # AttributeError
print(getattr(person, 'wid', 0)) # 0
# 函数 setattr 可以设置 `object` 中特定属性 `name` 的值
# 如果属性 name 不存在,则新建属性后赋值;如果属性 name 已存在,则修改该属性对应的值
setattr(person, 'name', 'Dango')
print(person.name) # Dango
setattr(person, 'wid', 202101)
print(person.wid)  # 202101
# 函数 delattr 可以删除 `object` 中特定属性 `name` 及其值
# 如果属性 name 不存在,则抛出 AttributeError 异常
delattr(person, 'wid')
print(person.wid) # AttributeError
delattr(person, 'age') # AttributeError


3、魔法方法


魔法方法是一种特殊的方法,它的名称有以下的格式:__name__,以两个下划线开头,以两个下划线结尾


(1)生命周期


  • __init__(self):在创建对象时自动调用,用于初始化对象,返回值必须为 None
  • __del__(self)  :在销毁对象前自动调用,用于做收尾工作
class Person():
    def __init__(self, name):
        print('Person __init__')
        self.name = name
    def set_name(self, name):
        self.name = name
    def get_name(self):
        return self.name
    def __del__(self):
        print('Person __del__')
# 实际上, `__del__` 不会在销毁对象时立即调用
# 而是当所有指向该对象的标签都被销毁时才会调用
person = Person('Jack')  # Person __init__
refer2 = person
del person
print('del person')      # del person
del refer2               # Person __del__
print('del refer2')      # del refer2
class Worker(Person):
    def __init__(self, name, wid):
        print('Worker __init__')
        super(Worker, self).__init__(name)
        self.wid = wid
    def set_wid(self, wid):
        self.wid = wid
    def get_wid(self):
        return self.wid
    def __del__(self):
        print('Worker __del__')
worker = Worker('Jack', 202101)
# Worker __init__
# Person __init__
worker.set_name('Dango')
print(worker.get_name()) # Dango
worker.set_wid(202102)
print(worker.get_wid())  # 202102
# Worker __del__
  • __new__(cls):实例化对象时第一个调用的方法,用于创建实例化对象
class Person():
    def __new__(cls, name):
        # 接收类本身作为第一个参数,若还有其他参数,则会原封不动地传给 __init__
        # 最终要返回一个实例化对象
        print('__new__')
        return super(Person, cls).__new__(cls)
    def __init__(self, name):
        print('__init__')
        self.name = name 
    def __del__(self):
        print('__del__')
person = Person('Jack')
# __new__
# __init__
# __del__

正常情况下极少重写该方法,当继承一个不可变的类 (例如 intstr) 又需要对其进行修改时才会重写

# 只有大写的字符串
class String(str):
    def __new__(cls, string):
        string = string.upper()
        return str.__new__(cls, string)
string = String('abcdefg')
print(string) # 'ABCDEFG'

__new__ 还可以用来实现单例模式

class Singleton():
    _instance = None
    def __new__(cls):
        if not cls._instance:
            cls._instance = super(Singleton, cls).__new__(cls)
        return cls._instance
obj1 = Singleton()
obj2 = Singleton()
print(id(obj1)) # 60550000
print(id(obj2)) # 60550000

__new__ 也可以用来实现简单工厂模式

class SupClass():
    def __init__(self):
        pass
    def show_msg(self):
        print('SupClass')
class SubClassOne(SupClass):
    def __init__(self):
        pass
    def show_msg(self):
        print('SubClassOne')
class SubClassTwo(SupClass):
    def __init__(self):
        pass
    def show_msg(self):
        print('SubClassTwo')
class Factory():
    _base = SupClass
    _item = {'one': SubClassOne, 'two': SubClassTwo}
    def __new__(cls, name):
        if name in cls._item.keys():
            return cls._item[name]()
        else:
            return cls._base()
supClass = Factory(None)
subClassOne = Factory('one')
subClassTwo = Factory('two')
supClass.show_msg()    # SupClass
subClassOne.show_msg() # SubClassOne
subClassTwo.show_msg() # SubClassTwo


(2)元素访问


以下魔法方法可以控制元素访问行为,合理使用能帮助我们创建自己的元素集合(序列和映射)

不可变对象只需要实现 2 个方法(__len____getitem__

而可变对象则需要实现 4 个方法(__len____getitem__ 加上 __setitem____delitem__

  • __len__(self):返回集合包含的项数,对序列而言是元素个数,对映射而言是键值对个数
  • __getitem__(self, key):返回与指定键对应的值,当访问元素时被调用
  • __setitem__(self, key, value):修改指定键对应的值,当修改元素时被调用
  • __delitem__(self, key):删除指定键及对应的值,当删除元素时被调用
class MyDict():
    def __init__(self):
        self.dict = dict()
    def __len__(self):
        print('__len__ is called')
        return len(self.dict)
    def __getitem__(self, key):
        print('__getitem__ is called')
        return self.dict[key]
    def __setitem__(self, key, value):
        print('__setitem__ is called')
        self.dict[key] = value
    def __delitem__(self, key):
        print('__delitem__ is called')
        del self.dict[key]
dic = MyDict()
dic['A'] = 7    # __setitem__ is called
print(dic['A']) # __getitem__ is called 7
print(len(dic)) # __len__ is called     1
del dic['A']    # __delitem__ is called


(3)属性访问


以下魔法方法可以控制属性访问行为,一般用于执行权限检查,日志记录等的操作

  • __getattribute__(self, name):在属性被访问时自动调用
  • __getattr__(self, name):在属性被访问且对象没有该属性时自动调用
  • __setattr__(self, name, value):在试图给属性赋值时自动调用
  • __delattr__(self, name):在试图删除属性时自动调用
class Object():
    def __init__(self):
        self.num = 0
    def __getattribute__(self, name):
        print('__getattribute__ is called') 
        return super().__getattribute__(name)
        # 注意,在这里使用 self.name 或 self.__dict__[name] 都是错误的
        # 因为,以上语句会再次调用 __getattribute__ 从而导致无限循环
        # 所以,唯一安全的方法是使用 super
    def __getattr__(self, name):
        print('__getattr__ is called')
        raise AttributeError
        # 此时,无法找到属性,返回异常 AttributeError
    def __setattr__(self, name, value):
        print('__setattr__ is called')
        self.__dict__[name] = value
        # 同样,在这里使用 self.name = value 也是错误的
        # 因为,以上语句会再次调用 __setattr__ 从而导致无限循环
        # 所以,可以使用对象内置属性 __dict__ 进行赋值
obj = Object()     # __init__ 被调用,属性 num 被赋值
# __setattr__ is called
# __getattribute__ is called
print(obj.num)     # 访问已存在的属性 num
# __getattribute__ is called
# 0
print(obj.void)    # 访问不存在的属性 void
# __getattribute__ is called
# __getattr__ is called
# AttributeError


(4)运算符


以下魔法方法可以控制运算符的行为,一般用于运算符重载

  • __pos__(self):定义正号行为 +
  • __neg__(self):定义减号行为 -
  • __abs__(self):定义绝对值行为 abs()__invert__(self):定义按位取反行为 ~
# 以上操作符称为一元算术运算符
# 这些方法只接收一个参数,然后执行算术运算
class String():
    def __init__(self, string):
        self.string = string
    def __pos__(self):
        return self.string.upper()
    def __neg__(self):
        return self.string.lower()
string = String('Hello')
print(+string) # HELLO
print(-string) # hello
  • __add__(self, other):定义加法行为 +
  • __sub__(self, other):定义减法行为 -
  • __mul__(self, other):定义乘法行为 *
  • __truediv__(self, other)  :定义真正除法行为 /__floordiv__(self, other):定义整数除法行为 //__mod__(self, other):定义取模运算行为 %
  • __truediv__(self, other)  :定义真正除法行为 /
  • __floordiv__(self, other):定义整数除法行为 //
  • __mod__(self, other):定义取模运算行为 %
  • __pow__(self, other):定义取幂运算行为 **
  • __lshift__(self, other):定义按位左移行为 <<
  • __rshift__(self, other):定义按位右移行为 >>
  • __and__(self, other):定义按位与行为 &
  • __or__(self, other)  :定义按位或行为 |
  • __xor__(self, other):定义按位异或行为 ^
# 以上操作符称为二元算术运算符
# 这些方法会接收两个参数,然后执行算术运算
class String():
    def __init__(self, string):
        self.string = string
    def __lshift__(self, bits):
        return self.string[bits: ]
    def __rshift__(self, bits):
        return self.string[:-bits]
string = String('Hello')
print(string << 2) # llo
print(string >> 2) # Hel
  • __eq__(self, other):定义等于行为 =
  • __ne__(self, other):定义不等于行为 !=
  • __lt__(self, other):定义小于行为 <
  • __le__(self, other):定义小于等于行为 <=
  • __gt__(self, other):定义大于行为 >
  • __ge__(self, other):定义大于等于行为 >=
# 以上操作符称为二元比较运算符
# 这些方法会接收两个参数,然后执行比较运算
class String():
    def __init__(self, string):
        self.string = string
    def __lt__(self, other):
        return len(self.string) < len(other.string)
    def __gt__(self, other):
        return len(self.string) > len(other.string)
string1 = String('Hello')
string2 = String('Hi')
print(string1 < string2) # False
print(string1 > string2) # True

对于二元算术运算符,经过简单的变化还能得到其他的魔法方法

  • 反向运算:在二元算术运算函数名前加字符 r,当左操作数不支持该运算时 ,尝试使用右操作数调用
  • 增量赋值:在二元算术运算函数名前加字符 i,支持类似于 x *= y 的操作
class String():
    def __init__(self, string):
        self.string = string
    def __add__(self, other):
        print('__add__ is called')
        return String(self.string + other)
    def __radd__(self, other):
        print('__radd__ is called')
        return String(other + self.string)
    def __iadd__(self, other):
        print('__iadd__ is called')
        self.string += other.string
        return self
string1 = String('Hello')
string2 = String('World')
temp1 = string1 + 'World' # __add__ is called
temp2 = 'Hello' + string2 # __radd__ is called
print(temp1.string) # HelloWorld
print(temp2.string) # HelloWorld
string1 += string2    # __iadd__ is called
print(string1.string) # HelloWorld
print(string2.string) # World

(5)描述符


描述符类用于描述一个类的属性,它至少需要实现以下三个方法之一:

  • __get__(self, instance, owner):用于访问属性,返回属性的值
  • __set__(self, instance, value):用于修改属性,不返回任何内容
  • __delete__(self, instance): 用于删除属性,不返回任何内容
class Property():
    def __init__(self, fget = None, fset = None, fdel = None):
        self.fget = fget
        self.fset = fset
        self.fdel = fdel
    def __get__(self, instance, onwer):
        print('__get__ is called')
        assert callable(self.fget)
        return self.fget(instance)
    def __set__(self, instance, value):
        print('__set__ is called')
        assert callable(self.fset)
        self.fset(instance, value)
    def __delete__(self, instance):
        print('__delete__ is called')
        assert callable(self.fdel)
        self.fdel(instance)
class Person():
    def __init__(self, name):
        self._name = name
    def _getName(self):
        print('_getName is called')
        return self._name
    def _setName(self, value):
        print('_setName is called')
        self._name = value
    name = Property(_getName, _setName)
person = Person('Jack')
person.name = 'Dango' #   __set__ is called      _setName is called
person.name           #   __get__ is called      _getName is called
del person.name       #   __delete__ is called   AssertionError

如上所示,我们可以为每一个属性创建一个描述符类,然而这是一件很麻烦的事情

幸好 Python 自带有描述符类 property(fget = None, fset = None, fdel = None, doc = None)

  • fget:指定读方法,若只指定一个方法 ( fget ), 则创建的属性是只读的
  • fset:指定写方法,若有指定两个方法 ( fgetfset ),则创建的属性是可读可写的
  • fdel:可选,指定删除属性的方法, 如果没有指定这个方法, 那么这个属性不可删除
  • doc  :可选,指定一个文档字符串
# 以函数方式使用
class Person():
    def __init__(self, name):
        self._name = name
    def _getName(self):
        print('_getName is called')
        return self._name
    def _setName(self, value):
        print('_setName is called')
        self._name = value
    def _delName(self):
        print('_delName is called')
        del self._name
    name = property(_getName, _setName, _delName)
person = Person('Jack')
person.name = 'Dango' # _setName is called
person.name           # _getName is called
del person.name       # _delName is called

# 以装饰器方式使用
class Person():
    def __init__(self, name):
        self._name = name
    @property
    def name(self):
        print('name getter')
        return self._name
    @name.setter
    def name(self, value):
        print('name setter')
        self._name = value
    @name.deleter
    def name(self):
        print('name deleter')
        del self._name
person = Person('Jack')
person.name = 'Dango' # name setter
person.name           # name getter
del person.name       # name deleter


6)输出函数

  • __str__(self)  :当使用函数 str   或函数 print 时被调用
  • __repr__(self):当使用函数 repr 或直接输出对象时被调用
class String():
    def __init__(self, string):
        self.string = string
    def __str__(self):
        return '__str__ is called'
    def __repr__(self):
        return '__repr__ is called'
>>> string = String('Hello')
>>> str(string)   # '__str__ is called'
>>> repr(string)  # '__repr__ is called'
>>> print(string) # __str__ is called
>>> string        # __repr__ is called


4、属性隐藏


默认情况下,Python 允许在外部直接访问对象的属性

class Object():
    value = 0
    def setNum(self, value): # 修改器
        self.value = value
    def getNum(self):        # 访问器
        return self.value
obj = Object()
obj.value = 100
print(obj.value) # 100

在 Python 中没有为私有属性提供直接的支持,这似乎违反了属性隐藏的原则

但 Python 用一种特殊的方式实现类私有属性的效果,只需要让名称以两个下划线开头即可

class Object():
    __value = 0
    def setNum(self, value): # 修改器
        self.__value = value
    def getNum(self):        # 访问器
        return self.__value
obj = Object()
# 这时不能直接访问属性
print(obj.value)   # AttributeError
print(obj.__value) # AttributeError
# 但还能通过修改器和访问器进行操作
obj.setNum(100)
print(obj.getNum()) # 100

但实际上这只是 Python 玩的一点小把戏,Python 会把类定义中所有以两个下划线开头的名称进行转换

即在原来的名称前加下划线和类名,所以对于上面的例子我们依然可以直接访问对象的属性

print(obj._Object__value) # 100
obj._Object__value = 1000
print(obj._Object__value) # 1000

我们无法禁止别人访问私有成员,只是以双下划线开头向对方发出了强烈的信号,希望他们不要这样做

如果不希望名称被修改,又想让别人知道不应该从外部修改该属性或方法,可以尝试用一个下划线开头

虽然这只是一种约定,但还是有一些作用的,例如在导入模块时,将不会导入以一个下划线开头的名称


文章知识点与官方知识档案匹配,可进一步学习相关知识

目录
相关文章
|
1月前
|
网络协议 Java Linux
PyAV学习笔记(一):PyAV简介、安装、基础操作、python获取RTSP(海康)的各种时间戳(rtp、dts、pts)
本文介绍了PyAV库,它是FFmpeg的Python绑定,提供了底层库的全部功能和控制。文章详细讲解了PyAV的安装过程,包括在Windows、Linux和ARM平台上的安装步骤,以及安装中可能遇到的错误和解决方法。此外,还解释了时间戳的概念,包括RTP、NTP、PTS和DTS,并提供了Python代码示例,展示如何获取RTSP流中的各种时间戳。最后,文章还提供了一些附录,包括Python通过NTP同步获取时间的方法和使用PyAV访问网络视频流的技巧。
191 4
PyAV学习笔记(一):PyAV简介、安装、基础操作、python获取RTSP(海康)的各种时间戳(rtp、dts、pts)
|
1月前
|
Python
Socket学习笔记(二):python通过socket实现客户端到服务器端的图片传输
使用Python的socket库实现客户端到服务器端的图片传输,包括客户端和服务器端的代码实现,以及传输结果的展示。
134 3
Socket学习笔记(二):python通过socket实现客户端到服务器端的图片传输
|
1月前
|
JSON 数据格式 Python
Socket学习笔记(一):python通过socket实现客户端到服务器端的文件传输
本文介绍了如何使用Python的socket模块实现客户端到服务器端的文件传输,包括客户端发送文件信息和内容,服务器端接收并保存文件的完整过程。
144 1
Socket学习笔记(一):python通过socket实现客户端到服务器端的文件传输
|
1月前
|
索引 Python
python-类属性操作
【10月更文挑战第11天】 python类属性操作列举
17 1
|
1月前
|
Java C++ Python
Python基础---类
【10月更文挑战第10天】Python类的定义
21 2
|
1月前
|
关系型数据库 MySQL 数据库
Mysql学习笔记(四):Python与Mysql交互--实现增删改查
如何使用Python与MySQL数据库进行交互,实现增删改查等基本操作的教程。
61 1
|
1月前
|
Ubuntu Linux Python
Ubuntu学习笔记(六):ubuntu切换Anaconda和系统自带Python
本文介绍了在Ubuntu系统中切换Anaconda和系统自带Python的方法。方法1涉及编辑~/.bashrc和/etc/profile文件,更新Anaconda的路径。方法2提供了详细的步骤指导,帮助用户在Anaconda和系统自带Python之间进行切换。
80 1
WK
|
1月前
|
Python
Python类命名
在Python编程中,类命名至关重要,影响代码的可读性和维护性。建议使用大写驼峰命名法(如Employee),确保名称简洁且具描述性,避免使用内置类型名及单字母或数字开头,遵循PEP 8风格指南,保持项目内命名风格一致。
WK
13 0
|
存储 Linux 索引
python基础学习笔记
服务器 1.ftp服务器         FTP是FileTransferProtocol(文件传输协议)的英文简称,中文名称为“文传协议”。
1500 0
|
数据安全/隐私保护 Python