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

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

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

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


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

目录
相关文章
|
15天前
|
安全 JavaScript Java
Python中None与NoneType的真相:从单例对象到类型系统的深度解析
本文通过10个真实场景,深入解析Python中表示“空值”的None与NoneType。从单例模式、函数返回值,到类型注解、性能优化,全面揭示None在语言设计与实际编程中的核心作用,帮助开发者正确高效地处理“无值”状态,写出更健壮、清晰的Python代码。
92 3
|
30天前
|
Python
解决Python中AttributeError:'image'对象缺少属性'read_file'的问题策略。
通过上述策略综合考虑,您将能够定位问题并确定如何解决它。记住,Python社区很庞大,也很乐于帮助解决问题,因此不要害怕在求助时提供尽可能多的上下文和您已经尝试过的解决方案。
58 0
|
5月前
|
Python
解决Python报错:DataFrame对象没有concat属性的多种方法(解决方案汇总)
总的来说,解决“DataFrame对象没有concat属性”的错误的关键是理解concat函数应该如何正确使用,以及Pandas库提供了哪些其他的数据连接方法。希望这些方法能帮助你解决问题。记住,编程就像是解谜游戏,每一个错误都是一个谜题,解决它们需要耐心和细心。
234 15
|
5月前
|
安全 测试技术 开发者
Python中的“空”:对象的判断与比较
在Python开发中,判断对象是否为“空”是常见操作,但其中暗藏诸多细节与误区。本文系统梳理了Python中“空”的判定逻辑,涵盖None类型、空容器、零值及自定义对象的“假值”状态,并对比不同判定方法的适用场景与性能。通过解析常见误区(如混用`==`和`is`、误判合法值等)及进阶技巧(类型安全检查、自定义对象逻辑、抽象基类兼容性等),帮助开发者准确区分各类“空”值,避免逻辑错误,同时优化代码性能与健壮性。掌握这些内容,能让开发者更深刻理解Python的对象模型与业务语义交集,从而选择最适合的判定策略。
167 5
|
5月前
|
人工智能 Python
[oeasy]python083_类_对象_成员方法_method_函数_function_isinstance
本文介绍了Python中类、对象、成员方法及函数的概念。通过超市商品分类的例子,形象地解释了“类型”的概念,如整型(int)和字符串(str)是两种不同的数据类型。整型对象支持数字求和,字符串对象支持拼接。使用`isinstance`函数可以判断对象是否属于特定类型,例如判断变量是否为整型。此外,还探讨了面向对象编程(OOP)与面向过程编程的区别,并简要介绍了`type`和`help`函数的用法。最后总结指出,不同类型的对象有不同的运算和方法,如字符串有`find`和`index`方法,而整型没有。更多内容可参考文末提供的蓝桥、GitHub和Gitee链接。
110 11
|
8月前
|
测试技术 Python
【03】做一个精美的打飞机小游戏,规划游戏项目目录-分门别类所有的资源-库-类-逻辑-打包为可玩的exe-练习python打包为可执行exe-优雅草卓伊凡-持续更新-分享源代码和游戏包供游玩-1.0.2版本
【03】做一个精美的打飞机小游戏,规划游戏项目目录-分门别类所有的资源-库-类-逻辑-打包为可玩的exe-练习python打包为可执行exe-优雅草卓伊凡-持续更新-分享源代码和游戏包供游玩-1.0.2版本
309 31
【03】做一个精美的打飞机小游戏,规划游戏项目目录-分门别类所有的资源-库-类-逻辑-打包为可玩的exe-练习python打包为可执行exe-优雅草卓伊凡-持续更新-分享源代码和游戏包供游玩-1.0.2版本
|
6月前
|
存储 C语言 Python
[oeasy]python077_int类型怎么用_整数运算_integer_进制转化_int类
本文主要讲解了Python中`int`类型的应用与特性。首先回顾了`int`词根的溯源,探讨了整型变量的概念及命名规则(如匈牙利命名法)。接着分析了整型变量在内存中的存储位置和地址,并通过`type()`和`id()`函数验证其类型和地址。还介绍了整型变量的运算功能,以及如何通过`int()`函数将字符串转化为整数,支持不同进制间的转换(如二进制转十进制)。此外,文章提及了关键字`del`的使用场景,对比了Python与C语言中`int`的区别,并总结了整型与字符串类型的差异,为后续深入学习奠定基础。
96 1
|
9月前
|
存储 数据处理 Python
Python如何显示对象的某个属性的所有值
本文介绍了如何在Python中使用`getattr`和`hasattr`函数来访问和检查对象的属性。通过这些工具,可以轻松遍历对象列表并提取特定属性的所有值,适用于数据处理和分析任务。示例包括获取对象列表中所有书籍的作者和检查动物对象的名称属性。
179 2
|
9月前
|
缓存 监控 算法
Python内存管理:掌握对象的生命周期与垃圾回收机制####
本文深入探讨了Python中的内存管理机制,特别是对象的生命周期和垃圾回收过程。通过理解引用计数、标记-清除及分代收集等核心概念,帮助开发者优化程序性能,避免内存泄漏。 ####
192 3
|
9月前
|
数据采集 存储 XML
python实战——使用代理IP批量获取手机类电商数据
本文介绍了如何使用代理IP批量获取华为荣耀Magic7 Pro手机在电商网站的商品数据,包括名称、价格、销量和用户评价等。通过Python实现自动化采集,并存储到本地文件中。使用青果网络的代理IP服务,可以提高数据采集的安全性和效率,确保数据的多样性和准确性。文中详细描述了准备工作、API鉴权、代理授权及获取接口的过程,并提供了代码示例,帮助读者快速上手。手机数据来源为京东(item.jd.com),代理IP资源来自青果网络(qg.net)。

热门文章

最新文章

推荐镜像

更多