我们先来补充一个前置知识(@金蛋(jindan-uwtta) 一直想让我在前头就说这个知识,我怕大家混淆类和对象的概念、或产生类不需要实例化即可使用的误解,坚持删掉了)。请看这段代码:
class Abc:
a = 1
b = 2
c = 3
def say_hello(self):
print('Hello!')
print(Abc.a, Abc.b, Abc.c) # 1 2 3
a = Abc()
print(a.a, a.b, a.c) # 1 2 3
Abc.say_hello() # 报错:TypeError: Abc.say_hello() missing 1 required positional argument: 'self'
a.say_hello() # Hello!
Abc.say_hello(a) # Hello!
可以看到,我们不初始化类时,也可以使用类名.属性名的格式访问类中的变量。
而对于方法,如果我们直接使用类名.方法名()的方式访问会报错。原因很简单——此时self关键字没有接收到合法的值。解决方法也十分简单粗暴,我们将一个实例化后的Abc类的对象a传入,使用类名.方法名(已实例化的对象)的格式访问即可。
魔术方法通常用于对一个自定义的对象做 Python 中基础的类型转换、数值和逻辑运算。
类型转换的魔术方法只接受self一个参数。
顾名思义,将类转换为字符串时使用的方法。
class User:
name = ''
def __str__(self):
return '名为 ' + self.name + ' 的用户'
a = User()
a.name = '小明'
print(str(a)) # 输出:名为 小明 的用户
print(a) # 事实上 print 本身就输出的是传递的参数转为 str 后的结果,输出:名为 小明 的用户
print(super(User, a).__str__()) # 即不自定义时默认的 __str__ 处理,输出:<__main__.User object at 0x00...>
与__str__将对象转为供人阅读的格式不同,这个是转换成供 Python 解释器阅读的。
额……说实话我没有想到很巧妙的例子,就先随便放一个吧。你需要知道的是,当你定义了__repr__而没有定义__str__时,将对象转为字符串也会使用__repr__的结果,因此一些人喜欢直接使用__repr__。
# 一个并不恰当的例子
class User:
name = ''
def __repr__(self):
return '123'
a = User()
print(a) # 123
class User:
name = ''
def __bool__(self):
return self.name != ''
a = User()
print(bool(a)) # False
a.name = '小明'
print(bool(a)) # True
指定len函数如何计算对象的长度
class User:
name = ''
def __len__(self):
return len(self.name)
a = User()
print(len(a)) # 0
a.name = 'abc'
print(len(a)) # 3
当使用小于号时使用:x < y语句会调用x.__lt__(y),并将其返回值作为答案。
class User:
name = ''
def __lt__(self, other):
return len(self.name) < len(other.name)
a, b = User(), User()
a.name, b.name = 'abc', 'def'
print(a < b) # False
b.name += 'ghi'
print(a < b) # True
同理可处理__le__ 小于等于``__gt__ 大于``__ge__ 大于等于``__eq__ 等于``__ne__ 不等于
当使用加法时使用:x + y语句会调用x.__add__(y),并将其返回值作为答案。
class User:
name = ''
def __add__(self, other):
return self.name + other.name
a, b = User(), User()
a.name, b.name = 'abc', 'def'
print(a + b) # abcdef
同理有__sub__ 减法``__mul__ 乘法``__truediv__ 除法以及次方、位运算等运算操作。
将对象当作一个函数调用。
class User:
def __call__(self):
print("I'm a user, why do you want to call me?")
a = User()
a() # I'm a user, why do you want to call me?
更多的魔术方法可以参考这篇文章:Python常用魔术方法 - 知乎
staticmethod和classmethod是 Python 内建的装饰器。
菜鸟教程对装饰器的解释如下:装饰器本质上是一个 Python 函数或类,它可以让其它函数或类在不需要做任何代码修改的前提下增加额外功能。
现在您无需了解什么是装饰器,我们会在以后的教程中提到(吗?如果没有的话请自行 Bing 搜索吧……)
def check_password(username, password):
...
class User:
def __init__(self, username):
self.username = username
@staticmethod
def login(username, password):
if check_password(username, password):
return User(username)
return
print(User.login('admin', 'right_password')) # <__main__.User object at 0x00...>
print(User.login('admin', 'wrong_password')) # None
staticmethod被绑定在了一个类中,它的特点是完全可以作为一个函数独立出来,放到类中只是为了代码组织的逻辑,并没有任何实际意义。
如果不用staticmethod,刚才这个例子也可以这样写:
def check_password(username, password):
...
class User:
def __init__(self, username):
self.username = username
def login(username, password):
if check_password(username, password):
return User(username)
return
print(login('admin', 'right_password')) # <__main__.User object at 0x00...>
print(login('admin', 'wrong_password')) # None
classmethod同理,classmethod的第一个参数必须是cls。cls代表的是类,就像是self代表对象一个道理。
class Student:
@classmethod
def new_student(cls):
return cls() # 此处的 class 是类 Student 本身,因此 cls() 等同于 Student()
def test(self):
print('ok')
a = Student.new_student() # 在此例中等同于 Student()
a.test() # 输出:ok
实际上,由于将第五行替换为return Student()也是合法的,并且具有与当前写法具有相同表现,因此classmethod通常只在存在复杂继承关系的情况下被使用。
然而,在复杂的继承关系下使用这样子跨类的语句实现十分容易出错,所以classmethod使用频率极低,我们通常使用staticmethod替代之。
在上例末尾加上一句b = a.new_student(),程序不会报错。因为 new_student() 是类方法,类方法可以被类对象调用。
当然,由于此处实例化后的a没什么意义,所以这句代码的运行等价于b = Student.new_student()。
property可以将一个函数的返回值映射为属性。每次访问该属性,property会运行被其修饰的函数并将返回值作为属性值。
property修饰的函数能且只能接受一个参数self。通过property映射的属性不能被直接设置。
current_year = 2022
class People:
def __init__(self, birth_year):
self.birth_year = birth_year
@property
def age(self):
return current_year - self.birth_year
a = People(2000)
print(a.age) # 22
current_year = 2035
print(a.age) # 35
a.age = 36 # 报错:AttributeError: can't set attribute 'age'
property通常用于数据缓存。例如:我要通过a.followers的格式访问User类的某个对象a的粉丝信息,我们可以设置函数检测a的粉丝这一数据是否在本地有缓存,若有直接返回,若没有则联网获取。