“欸?为什么我的编辑器给我的class
标红了啊???”
“你不知道吗?Python的新版本移除了class
关键字啊?”
“啊???”
一个早晨
由于不明原因,我突然没法在Python里使用class
关键字了!!!这怎么能够接受呢?我的面向对象编程啊!不行不行,没有class
我还怎么写代码!突然,一个声音出现在这小小的房间里。
“哦哈哈哈哈,还记得我吗?”
“你是谁??”
“闭包——包——嗷——”神秘人悄然离去,只剩下长长的尾音。
不行!你还我class
!
第一步!模拟类的行为
闭包这位同学有一个神奇的性质,就是它会保存自己所存在的上下文环境。在某种意义上,其可以很大程度模拟面向对象的行为。下面就是一个简单的闭包的例子。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| def f(): a = 0 def g(): nonlocal a a += 1 print(a)
return g
g1 = f() g2 = f()
g1() g1() g1() g2() g2() g2()
|
上述代码的输出如下:
可以看到,g1
和g2
各自维护住了自己所在上下文中的a
的值,并且没有产生干扰。基于闭包的这个特点,我们可以实现一个简单的类。
第二步!造一个简单的类
通过上面的例子可以看到,通过一个函数返回其内部的函数对象就可以简单地构造出一个闭包。因此,我们先搭好框架。比如我们模拟一个Dog
类:
1 2 3 4 5
| def Dog(name: str, age: int): def this(): pass
return this
|
上述就是一个极其简单的闭包。通过进行函数调用:dog = Dog("Doge", 5)
,我们拿到了Dog()
返回的一个函数对象;又由于闭包的性质,该函数对象外层环境中的局部变量name
和age
又会被维持住,这就构成了一个简单的闭包。
类一般都具有一部分成员函数,或者称为对象的方法。在这里我们也可以这样做。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| def Person(name: str, age: int): def self(): pass def to_string(): return f"Person(name = {name}, age = {age})" def get_name(): return name def get_age(): return age def set_age(new_age: int): nonlocal age age = new_age
def set_name(new_name: str): nonlocal name name = new_name
self.to_string = to_string self.get_name = get_name self.get_age = get_age self.set_name = set_name self.set_age = set_age
return self
|
首先,我们不使用this
而改为self
,这符合我们在Python中一贯的使用习惯。同时,我们声明一系列的方法,包括set_name
、get_name
等。为了便于调用,我们将这些方法的函数对象绑定到self
上,这样,我们就可以使用person.get_name()
来进行调用了。简单使用一下:
1 2 3 4 5 6 7
| p1 = Person("John", 30) print(p1.to_string()) print(p1.get_name()) print(p1.get_age()) p1.set_name("Alice") p1.set_age(25) print(p1.to_string())
|
其结果如下:
1 2 3 4
| Person(name = John, age = 30) John 30 Person(name = Alice, age = 25)
|
哦吼!初具雏形。这还需要class
关键字干嘛?想到这里,我端起电脑旁的盐汽水,微微抿了一口。 (至于为什么只是抿了一口,那当然是因为优雅)这时,一个声音在我耳畔响起:
“你这算什么?不能继承的类也能叫类?”
“你再骂!”
话虽这么说,但是没有继承的类就像没有醋的饺子,总是缺少了一点灵魂的。
第三步!灵魂继承,浇给
我们先拿到两个类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| def Animal(self, *args, **kwargs): self.name = kwargs.get("name", "Unknown")
def to_string(): return f"Animal(name = {self.name})" self.to_string = to_string
return self
def Dog(self, *args, **kwargs): self.age = kwargs.get("age", 10)
def to_string(): return f"Dog(name = {self.name}, age = {self.age})" self.to_string = to_string
return self
|
这两个类都符合我们定义的最基础的类,这里就不多解释了。至于我们为何需要格外加一个self
的形参,其在继承中将会起到一个重要的作用。这个例子中,Dog
类需要继承Animal
类,包括父类的成员变量和成员函数。因此,我们需要对Dog
类进行改写。
一种显然的方法是修改Dog
类的内部逻辑,使之接受到self
形参对应的Animal
实例后,依次继承其中的成员变量和成员函数。但这种做法太过臃肿了:如果我们还需要定义一个Cat
类,我们是不是也要这要修改Cat
类内部呢?这两部分代码是不是几乎一模一样呢?如果我们修改了基类Animal
的成员变量,那么我们是不是要修改所有继承自Animal
类的类定义呢?
好在,Python中的装饰器能够帮我们进行这部分的代码复用。我们使用@extend()
装饰器来表达继承这类关系,因此上述类定义可以改写成:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| @extend(None) def Animal(self, *args, **kwargs): self.name = kwargs.get("name", "Unknown")
def to_string(): return f"Animal(name = {self.name})" self.to_string = to_string
return self
@extend(Animal) def Dog(self, *args, **kwargs): self.age = kwargs.get("age", 10)
def to_string(): return f"Dog(name = {self.name}, age = {self.age})" self.to_string = to_string
return self
|
其中,@extend(None)
表示其为基类,不要继承任何类。因此剩下来的工作就是完成@extend
的逻辑。
完成装饰器生成器extend
可以看到,@extend(None)
和@extend(Animal)
才是装饰器,因此extend
只是一个装饰器生成器而已,其应该返回一个装饰器。我们先搭建出大致框架:
1 2 3 4 5
| def extend(super_cls): def decorator(cls): pass
return decorator
|
装饰器decorator
的参数是子类cls
,返回值应该是被装饰过后的子类,其也是一个类,因此我们继续完善上述代码:
1 2 3 4 5 6 7 8 9
| def extend(super_cls): def decorator(cls): def decorated_cls(): pass
return decorated_cls
return decorator
|
首先,我们需要确定被装饰子类decorated_cls
的形参列表。在面向对象编程中,我们初始化子类并不需要传入父类实例,因此,self
肯定是多余的形参。因此我们能够确定,返回的被装饰子类形参列表必然不存在self
,即decorated_cls(*args, **kwargs)
。
那么接下来的逻辑是,我们如何处理原本定义中的self
形参?显然,这依赖于该类所继承的父类。如果该类本身没有父类(即基类),那么self
显然是空对象;反之,self
对应的就是使用参数列表所初始化的父类对象。完成上述逻辑即:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| def extend(super_cls): def decorator(cls): def decorated_cls(*args, **kwargs): if super_cls is None: return cls(lambda: ..., *args, **kwargs) else: super_obj = super_cls(*args, **kwargs) return cls(super_obj, *args, **kwargs) return decorated_cls return decorator
|
因此,完整的定义即:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| def extend(super_cls): def decorator(cls): def decorated_cls(*args, **kwargs): if super_cls is None: return cls(lambda: ..., *args, **kwargs) else: super_obj = super_cls(*args, **kwargs) return cls(super_obj, *args, **kwargs) return decorated_cls return decorator
@extend(None) def Animal(self, *args, **kwargs): self.name = kwargs.get("name", "Unknown")
def to_string(): return f"Animal(name = {self.name})" self.to_string = to_string
return self
@extend(Animal) def Dog(self, *args, **kwargs): self.age = kwargs.get("age", 10)
def to_string(): return f"Dog(name = {self.name}, age = {self.age})" self.to_string = to_string
return self
|
我们测试一下:
1 2
| dog = Dog(name="Buddy", age=5) print(dog.to_string())
|
运行结果:
1
| Dog(name = Buddy, age = 5)
|
“嗨,不过如此嘛。”又喝了一口剩下的盐汽水,我长舒一口气。
“哼哼,你以为就结束了?”
“不然呢?这个闭包模拟类行为简直是天才想法!”
“真的吗?”突然,我的眼前出现了这么一行代码:
1 2 3
| @extend(Animal, Runner) def Dog(self, *args, **kwargs): pass
|
“我要你做出来多继承!!!”
“啊??”
第四步!多继承,启动!
理解上述单继承后,多继承的逻辑只需要在extend
中修改即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| def extend(*super_cls_list): def decorator(cls): def decorated_cls(*args, **kwargs): if len(super_cls_list) == 0: return cls(lambda: ..., *args, **kwargs) else: super_obj = lambda: ... for super_cls in super_cls_list: cur_obj = super_cls(*args, **kwargs) for k,v in cur_obj.__dict__.items(): if not k.startswith("__"): setattr(super_obj, k, v) return cls(super_obj, *args, **kwargs) return decorated_cls return decorator
|
注意
此时需要对extend
用法稍作修改:对于基类,不再需要传入None
;对于子类,需要传入父类列表而不是以逗号隔开的多个参数
完整代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
| def extend(*super_cls_list): def decorator(cls): def decorated_cls(*args, **kwargs): if len(super_cls_list) == 0: return cls(lambda: ..., *args, **kwargs) else: super_obj = lambda: ... for super_cls in super_cls_list: temp_obj = super_cls(*args, **kwargs) for k,v in temp_obj.__dict__.items(): if not k.startswith("__"): setattr(super_obj, k, v)
return cls(super_obj, *args, **kwargs) return decorated_cls return decorator
@extend() def Animal(self, *args, **kwargs): self.name = kwargs.get("name", "Unknown")
def to_string(): return f"Animal(name = {self.name})" self.to_string = to_string
return self
@extend() def Runner(self, *args, **kwargs): self.speed = kwargs.get("speed", 10)
def to_string(): print(f"{self.name} is running with speed {self.speed}") self.to_string = to_string
return self
@extend(*[Animal, Runner]) def Dog(self, *args, **kwargs): self.age = kwargs.get("age", 10)
def to_string(): return f"Dog(name = {self.name}, age = {self.age}, speed = {self.speed})" self.to_string = to_string
return self
|
试验一下:
1 2
| dog = Dog(name="Buddy", age=5, speed=20) print(dog.to_string())
|
结果:
1
| Dog(name = Buddy, age = 5, speed = 20)
|
后记
“小小的class
关键字,居然要考虑这么多。”忙活了这么久,终于可以喝完这瓶盐汽水了。
“不对啊,这盐汽水喝不完啊!”
突然,梦醒了。一如既往地打开电脑和VSCode,输入class
关键字。
“好像?都是一场梦?”
“哈哈哈哈,”古怪的声音再次响起,“但你学会了不少新东西,不是吗?”这声音又一次离去了,只不过这次再未出现过。