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

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

1、内置方法

(1)issubclass(class, classinfo)

检查 class 是否为 classinfo 的子类,classinfo 可以是一个类也可以是由多个类组成元组

注意 class 被认为是 class 的子类,也被认为是 object 的子类,若传入的类型与期望不符则抛出 TypeError 异常

(2)isinstance(object, classinfo)

检查 object 是否为 classinfo 的实例化对象,classinfo 可以是一个类也可以是由多个类组成元组

注意若 object 不是对象则返回 False,若 classinfo 既不是类也不是由多个类组成元组则抛出 TypeError 异常

(3)==is

  • == 比较两对象是否 相等

  • is 比较两对象是否 相同

>>> a = [1,2,3]
>>> b = [1,2,3]
>>> a == b # 比较两个对象的值是否相等
True
>>> a is b # 比较两个对象的 id 是否相等
False

(4)hasattrgetattrsetattrdelattr

  • hasattr(object, name):检查 object 中是否有特定属性 name,注意 name 需要用双引号包围

  • getattr(object, name [, default]):获取 object 中特定属性 name 的值

    若属性不存在,则抛出异常(没设置 default)或打印提示信息(有设置 default),注意 name 需要用双引号包围

  • setattr(object, name, value):设置 object 中特定属性 name 的值

    若属性不存在,则新建一个属性并赋值,注意 name 需要用双引号包围

  • delattr(object, name):删除 object 中特定属性 name 的值

    若属性不存在则抛出 AttributeError 异常,注意 name 需要用双引号包围

>>> class Person:
		name = 'test'
		def getName(self):
			print(self.name)
        
>>> student = Person()
>>> hasattr(student,'name')
# True
>>> getattr(student,'age','none')
# 'none'
>>> setattr(student,'age',18)
>>> student.age
# 18
>>> delattr(student,'age')
>>> student.age
# AttributeError: 'Person' object has no attribute 'age'

2、属性隐藏

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

>>> class Test:
    	num = 0
        def setNum(self,num): # 修改器
            self.num = num
        def getNum(self): # 访问器
            return self.num
        
>>> obj = Test()
>>> # 我们可以直接访问对象的数据成员,而不需要通过访问器和修改器
>>> obj.num = 100
>>> obj.num
# 100 

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

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

>>> class Test:
    	__num = 0
        def setNum(self,num):
            self.num = num
        def getNum(self):
            return self.num
        
>>> obj = Test()
>>> # 此时,我们将不能直接访问对象私有的属性和方法
>>> obj.num
# AttributeError
>>> # 但是,我们依然可以使用访问器和修改器访问和修改数据
>>> obj.setNum(100)
>>> obj.getNum()
# 100

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

即在开头加上下划线和类名,所以对于上面的例子我们依然可以直接访问对象的私有属性

>>> obj._Test__num = 100
>>> obj._Test__num
# 100

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

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

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

3、参数 self

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

学过 C++ 的朋友或许很容易理解,Python 中的 self 其实就相当于 C++ 中的 this,指向调用该方法的实例化对象

在刚刚接触时,常常会出现下面的错误

>>> # 常见错误:忘记在一般方法中加上 self 参数
>>> class Test:
        def __init__(self):
            self.value = 0
        def getValue(): # 错误定义,没有加上参数 self
            return value

>>> test = Test()
>>> test.getValue()
# TypeError: getValue() takes 0 positional arguments but 1 was given
>>> # 常见错误:忘记在方法中调用参数时加上 self,此时会默认该参数为局部变量
>>> class Test:
        def __init__(self):
            self.value = 0
        def getValue(self): # 正确定义
            return value # 错误使用,这里返回的是局部变量

>>> test = Test()
>>> test.getValue()
# NameError: name 'value' is not defined

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

在类方法中没有加上 self 参数的方法称为静态方法,它可以直接通过类来调用,而不可以通过对象调用

>>> class Test:
        def show():
            print('static function')
            
>>> test = Test()
>>> test.show() # 不可以通过对象调用
# TypeError: show() takes 0 positional arguments but 1 was given
>>> Test.show() # 但可以通过类直接调用
# static function

另外一个不需要加上 self 参数的情况是类方法

该方法传入的第一个参数是类似于 self 的参数 cls,该方法可通过对象直接调用,但参数 cls 将自动关联到类

>>> class Test:
        def show(cls):
            print('static function')
            
>>> test = Test()
>>> test.show() # 可以通过对象直接调用
# static function

4、魔法方法

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

(1)构造函数

__init__(self) 也被称为构造函数,该方法在创建对象时自动调用,返回值必须为 None,用于初始化对象

>>> class Test:
        def __init__(self,value):
            print('__init__ is called.')
            self.value = value
        def getValue(self):
            return self.value
            
>>> obj = Test(16)
# __init__ is called.
>>> obj.getValue()
# 16

在继承机制中,方法重写 对构造函数尤为重要

对于一般的子类而言,在构造函数中不仅需要父类的初始化代码,还需要自己的初始化代码

要注意,在重写构造函数时,必须调用父类的构造函数,否则可能无法正确初始化对象

>>> class Person:
        def __init__(self,name):
            self.name = name
        def show(self):
            print('Name:', self.name)
            
>>> class Student(Person):
        def __init__(self,stuID): # 构造函数重写,没有调用父类的初始化方法
            self.stuID = stuID
        def show(self): # 普通方法重写
            print('Name:', self.name)
            print('stuID:', self.stuID)
            
>>> student = Student(1234)
>>> student.show() 
# AttributeError: 'Student' object has no attribute 'name'

我们可以看到上述的使用方法是错误的,因为在子类中没有调用父类的初始化方法,父类属性不能被正确初始化

为此,有两种解决方法:一是调用未关联的父类构造函数,二是使用函数 super

>>> # 调用未关联的父类构造函数,旧式用法
>>> class Student(Person):
        def __init__(self,name,stuID): # 构造函数重写,调用父类的初始化方法
            Person.__init__(self,name)
            self.stuID = stuID
        def show(self): # 普通方法重写
            Person.show(self)
            print('stuID:', self.stuID)
            
>>> student = Student('Peter',1234)
>>> student.show()
# Name: Peter
# stuID: 1234
>>> # 使用函数 super,新式用法
>>> class Student(Person):
        def __init__(self,name,stuID): # 构造函数重写,调用父类的初始化方法
            super().__init__(name) # 区别仅仅在于这一命令
            self.stuID = stuID
        def show(self): # 普通方法重写
            super().show()
            print('stuID:', self.stuID)
            
>>> student = Student('Peter',1234)
>>> student.show()
# Name: Peter
# stuID: 1234

在上面的例子中,可以看到两种处理方法的效果是一致的

但是调用未关联的父类构造函数难以处理多继承的问题,而使用函数 super 可以以同样的语法处理多继承问题

所以在新版的 Python 中,建议使用 super 来代替未关联的父类构造函数


事实上,__init__(self) 并不是实例化对象时第一个被调用的方法,第一个被调用的方法是 __new__(cls)

该方法的第一个参数是 cls,若还有其它参数则原封不动地传递给 __init__

__new__ 方法一般返回一个实例化对象,正常情况下极少重写 __new__ 方法

但当需要继承一个不可变方法,又需要对其进行修改时,可以重写该方法

>>> class MyString(str):
        def __new__(cls,string):
            string = string.upper()
            return str.__new__(cls,string)
    
>>> obj = MyString('abcdefg')
>>> obj
# 'ABCDEFG'

(2)析构函数

__del__(self) 也被称为析构函数,该方法在对象销毁前自动调用

但事实上,在销毁对象时并不会立即调用 __del__(self) 方法,而是当所有指向该对象的标签被销毁时才会调用

>>> class Test:
        def __del__(self):
            print('__del__ is called')
            
>>> obj = Test()
>>> temp = obj
>>> del obj # 删除指向对象的标签时,并不直接调用 __del__
>>> del temp # 删除指向该对象的所有标签时,才会调用 __del__
# __del__ is called

(3)元素访问

使用本小节中介绍的魔法方法,可以帮助我们更好的创建序列和映射类型(元素集合),要实现它们的基本行为

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

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

  • __len__(self):返回集合包含的项数,对序列而言是元素个数,对映射而言是键值对个数

    当对象使用内置函数 len 时被调用 len(self)

  • __getitem__(self,key):返回与指定键相对应的值,对序列而言键是整数,对映射而言是键是任何类型

    当对象被访问时调用 self[key]

  • __setitem__(self,key,value):以与键相关联的方式储存值,当对象被修改时调用 self[key] = value

  • __delitem__(self,key):删除与键相关联的值,当对象使用内置函数 del 时被调用 del self[key]

>>> class MyDic:
        def __init__(self):
            self.dic = dict()
        def __getitem__(self,key):
            print('__getitem__ is called')
            return self.dic[key]
        def __setitem__(self,key,value):
            print('__setitem__ is called')
            self.dic[key] = value
        # 这里为了演示效果,没有实现 __del__ 方法,因此不能使用 del 删除其中的元素

>>> dic = MyDic()
>>> dic['A'] = 1 # 修改器
# __setitem__ is called
>>> dic['A'] # 访问器
# __getitem__ is called
# 1
>>> del dic['A']
# AttributeError: __delitem__

除了以上最基本的魔法方法可以帮助我们创建序列和映射类型外,还有其它一些有用的魔法方法

例如,__contains__(self,item) 定义使用成员运算符 in 或者 not in 时的行为

(4)属性访问

属性访问魔法方法可以拦截对对象属性的所有访问企图,一般可以用于执行权限检查,日志记录等操作

  • __getattribute__(self,name):在属性被访问时自动调用
  • __getattr__(self,name):在属性被访问且对象没有该属性时自动调用
  • __setattr__(self,name,value):在试图给属性赋值时自动调用
  • __delattr__(self,name):在试图删除属性时自动调用
>>> class Test:
        def __init__(self):
            self.num = 0
        def __setattr__(self,name,value):
            print('__setattr__ is called')
            self.__dict__[name] = value
	        # 注意,在这里使用 self.name = value 这样的常规赋值语句是错误的,
            # 因为在语句中会再次调用 __setattr__,导致无限循环的错误
            # 因此,可以使用对象内置属性 __dict__ 进行赋值
        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

            
>>> test = Test() # __init__ 被调用,执行属性 num 赋值
# __setattr__ is called
# __getattribute__ is called
>>> test.num # 执行属性 num 访问(num 属性存在)
# __getattribute__ is called
# 0
>>> test.void # 执行属性 void 访问(void 属性不存在)
# __getattribute__ is called
# __getattr__ is called
# AttributeError

(5)运算符

① 算术运算

二元运算符:

  • __add__(self, other):定义加法行为:+
  • __sub__(self, other):定义减法行为:-
  • __mul__(self, other):定义乘法行为:*
  • __truediv__(self, other):定义真正除法行为:/
  • __floordiv__(self, other):定义整数除法行为://
  • __mod__(self, other):定义取模运算行为:%
  • __pow__(self, other [, modulo]):定义取幂行为:**
  • __lshift__(self, other):定义按位左移行为:<<
  • __rshift__(self, other):定义按位右移行为:>>
  • __and__(self, other):定义按位与行为:&
  • __or__(self, other):定义按位或行为:|
  • __xor__(self, other):定义按位异或行为:^

注意:

  • 反运算:在函数名前加一个字符 r,当左操作数不支持该算术运算时,则会利用右操作数调用该算术运算方法
  • 增量赋值:在函数名前加一个字符 i,支持类似于 x *= y 的操作

一元操作符:

  • __pos__(self):定义正号行为:+
  • __neg__(self):定义减号行为:-
  • __abs__(self):定义绝对值行为:abs()
  • __invert__(self):定义按位取反行为:~
② 比较运算
  • __lt__(self, other):定义小于行为:<
  • __le__(self, other):定义小于等于行为:<=
  • __eq__(self, other):定义等于行为:=
  • __ne__(self, other):定义不等于行为:!=
  • __gt__(self, other):定义大于行为:>
  • __ge__(self, other):定义大于等于行为:>=
>>> class Test:
        def __init__(self,value):
            self.value = value
        def __add__(self,other):
            return Test(self.value + other.value)
        def __neg__(self):
            return Test(-self.value)
        def __gt__(self,other):
            return (self.value > other.value)
        
>>> test1 = Test(100)
>>> test2 = Test(1)
>>> test1 > test2
# True
>>> test3 = test1 + (-test2)
>>> test3.value
# 99

(6)输出函数

  • __str__(self):当使用函数 str 或使用函数 print 时被调用
  • __repr__(self):当使用函数 repr 或直接输出对象时被调用
>>> class Test:
	def __init__(self,value):
		self.value = value
	def __str__(self):
		return str(self.value)
	def __repr__(self):
		return 'This is the class, Test'
    
>>> test = Test(100)
>>> str(test)
# '100'
>>> print(test)
# 100
>>> repr(test)
# 'This is the class, Test'
>>> test
# This is the class, Test

(7)描述符类

描述符类用于描述另一个类的属性,其将某种特殊类型的类实例指派给另一个类的属性

特殊类型的类需至少实现以下三个内置方法之一:

  • __get__(self, instance, owner):用于访问属性,返回属性的值

    self 为特殊类型的实例,instance 为另一个类的实例,owner 为另一个类的对象

  • __set__(self, instance, value):用于设置属性,不返回任何内容

    self为特殊类型的实例,instance为另一个类的实例,value为传入的值

  • __delete__(self, instance):用于删除属性,不返回任何内容

    self为特殊类型的实例,instance为另一个类的实例

在 Python 中有一个自带的描述符类 property,property(fget=None, fset=None, fdel=None,doc=None)

当指定 property 时,

  • 若没有指定任何参数,则创建的特性既不可读也不可写
  • 若指定一个参数( fget ),则创建的特性将是只读的
  • 若指定两个参数( fget 和 fset ),则创建的特性将是可读可写的
  • 第三个参数是可选的,指定用于删除属性的方法,这个方法不接受任何参数
  • 第四个参数也是可选的,指定一个文档字符串
>>> class MyProperty: # 自己创建一个描述符类
        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')
            return self.fget(instance)
        def __set__(self,instance,value):
            print('__set__ is called')
            self.fset(instance,value)
        def __delete__(self,instance):
            print('__delete__ is called')
            self.fdel(instance)
            
>>> class Test:
        def __init__(self):
            self._value = 0
        def _getValue(self):
            return self._value
        def _setValue(self,value):
            self._value = value
        value = MyProperty(_getValue,_setValue)
        
>>> test = Test()
>>> test.value = 1
# __set__ is called
>>> test.value
# __get__ is called
# 1

【 阅读更多 Python 系列文章,请看 Python学习笔记

版权声明:本博客属于个人维护博客,未经博主允许不得转载其中文章。
原文地址:https://www.cnblogs.com/wsmrzx/p/10301566.html