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

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

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

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


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

目录
相关文章
|
5天前
|
存储 数据处理 Python
Python如何显示对象的某个属性的所有值
本文介绍了如何在Python中使用`getattr`和`hasattr`函数来访问和检查对象的属性。通过这些工具,可以轻松遍历对象列表并提取特定属性的所有值,适用于数据处理和分析任务。示例包括获取对象列表中所有书籍的作者和检查动物对象的名称属性。
16 2
|
19天前
|
缓存 监控 算法
Python内存管理:掌握对象的生命周期与垃圾回收机制####
本文深入探讨了Python中的内存管理机制,特别是对象的生命周期和垃圾回收过程。通过理解引用计数、标记-清除及分代收集等核心概念,帮助开发者优化程序性能,避免内存泄漏。 ####
31 3
|
2月前
|
网络协议 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访问网络视频流的技巧。
434 4
PyAV学习笔记(一):PyAV简介、安装、基础操作、python获取RTSP(海康)的各种时间戳(rtp、dts、pts)
|
2月前
|
Python
Socket学习笔记(二):python通过socket实现客户端到服务器端的图片传输
使用Python的socket库实现客户端到服务器端的图片传输,包括客户端和服务器端的代码实现,以及传输结果的展示。
166 3
Socket学习笔记(二):python通过socket实现客户端到服务器端的图片传输
|
2月前
|
索引 Python
python-类属性操作
【10月更文挑战第11天】 python类属性操作列举
26 1
|
2月前
|
Java C++ Python
Python基础---类
【10月更文挑战第10天】Python类的定义
27 2
|
2月前
|
关系型数据库 MySQL 数据库
Mysql学习笔记(四):Python与Mysql交互--实现增删改查
如何使用Python与MySQL数据库进行交互,实现增删改查等基本操作的教程。
73 1
|
2月前
|
Ubuntu Linux Python
Ubuntu学习笔记(六):ubuntu切换Anaconda和系统自带Python
本文介绍了在Ubuntu系统中切换Anaconda和系统自带Python的方法。方法1涉及编辑~/.bashrc和/etc/profile文件,更新Anaconda的路径。方法2提供了详细的步骤指导,帮助用户在Anaconda和系统自带Python之间进行切换。
117 1
WK
|
2月前
|
Python
Python类命名
在Python编程中,类命名至关重要,影响代码的可读性和维护性。建议使用大写驼峰命名法(如Employee),确保名称简洁且具描述性,避免使用内置类型名及单字母或数字开头,遵循PEP 8风格指南,保持项目内命名风格一致。
WK
21 0
|
7月前
|
开发者 Python
Python对象和类
Python对象和类
30 0

热门文章

最新文章