这篇文章介绍有关 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)
:定义取模运算行为%
__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
我们无法禁止别人访问私有成员,只是以双下划线开头向对方发出了强烈的信号,希望他们不要这样做
如果不希望名称被修改,又想让别人知道不应该从外部修改该属性或方法,可以尝试用一个下划线开头
虽然这只是一种约定,但还是有一些作用的,例如在导入模块时,将不会导入以一个下划线开头的名称