这篇文章介绍有关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__
正常情况下极少重写该方法,当继承一个不可变的类 (例如 int
、str
) 又需要对其进行修改时才会重写
# 只有大写的字符串 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
:指定写方法,若有指定两个方法 (fget
和fset
),则创建的属性是可读可写的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
我们无法禁止别人访问私有成员,只是以双下划线开头向对方发出了强烈的信号,希望他们不要这样做
如果不希望名称被修改,又想让别人知道不应该从外部修改该属性或方法,可以尝试用一个下划线开头
虽然这只是一种约定,但还是有一些作用的,例如在导入模块时,将不会导入以一个下划线开头的名称
文章知识点与官方知识档案匹配,可进一步学习相关知识