前置知识:类未实例化时使用属性和方法
我们先来补充一个前置知识(@金蛋(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
一个参数。
str
顾名思义,将类转换为字符串时使用的方法。
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...>
repr
与__str__
将对象转为供人阅读的格式不同,这个是转换成供 Python 解释器阅读的。
额……说实话我没有想到很巧妙的例子,就先随便放一个吧。你需要知道的是,当你定义了__repr__
而没有定义__str__
时,将对象转为字符串也会使用__repr__
的结果,因此一些人喜欢直接使用__repr__
。
# 一个并不恰当的例子
class User:
name = ''
def __repr__(self):
return '123'
a = User()
print(a) # 123
bool
class User:
name = ''
def __bool__(self):
return self.name != ''
a = User()
print(bool(a)) # False
a.name = '小明'
print(bool(a)) # True
len
指定len
函数如何计算对象的长度
class User:
name = ''
def __len__(self):
return len(self.name)
a = User()
print(len(a)) # 0
a.name = 'abc'
print(len(a)) # 3
lt
当使用小于号时使用: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__ 不等于
add
当使用加法时使用: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__ 除法
以及次方、位运算等运算操作。
call
将对象当作一个函数调用。
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
staticmethod
和classmethod
是 Python 内建的装饰器。
菜鸟教程对装饰器的解释如下:装饰器本质上是一个 Python 函数或类,它可以让其它函数或类在不需要做任何代码修改的前提下增加额外功能。
现在您无需了解什么是装饰器,我们会在以后的教程中提到(吗?如果没有的话请自行 Bing 搜索吧……)
staticmethod
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
同理,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
会运行被其修饰的函数并将返回值作为属性值。
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
的粉丝这一数据是否在本地有缓存,若有直接返回,若没有则联网获取。