在python中,小到基础类型,大到内置方法函数、类等全都是对象。
class MyObj: ...
print(type(MyObj)) #<class 'type'>
print(type(MyObj())) #<class '__main__.MyObj'>
就连type的描述实际上也是一个类(builtins.pyi可查)
因此便诞生了许多魔术方法,能够全面的操纵该对象的使用方式:
class MyClass:
def __repr__(self):
return "<MyClass object>"
def __str__(self):
return "This is a class"
print(MyClass()) #"This is a class"
MyClass() #<MyClass Object>
上面一段代码更改了MyClass在转化为字符串时的操作。其中__str__方法是当直接调用str()进行字符串化时返回的字符串(大多为print等函数),__repr__方法则是在调试时返回的更详细的信息,如该对象在一个列表里:
mylist = [MyClass(), MyClass()]
print(mylist) #[<MyClass object>, <MyClass object>]
此时便会调用__repr__返回更详细的内容,当然这些内容在重写魔术方法时返回的数据依于你的需求。
此外,魔术方法还可以做到重写运算符等操作:
class MyClass:
def __repr__(self):
return "<MyClass object>"
def __str__(self):
return "This is a class"
def __eq__(self, a):
return True
def __getitem__(self, key):
return self.__dict__[key]
__eq__方法重写了当MyClass对象被拿去做相等运算时的操作,__getitem__重写了当对象被像字典一样通过“obj[index]”进行取值操作时返回的数据,类似于c++中的operator。
__dict__在对象中是一个比较特殊的魔法变量,它存储着当前对象所有成员变量。通常他是一个字典,在一些内置类中它则是一个mappingproxy (因为大多内置类由C实现)。
所有类的基类都有object类。
乃至函数也是一个对象。
def myFunc(): ...
myFunc.__qualname__ = "123"
print(myFunc) #<function 123 at ...>
python实际上是一个强类型的语言,你当然不可尝试字符串+数字这种运算(js却可以),因为基本类型的运算方法是写死的,你只能继承基本类型重写该魔术方法来实现跟基本类型几乎一样的一个类。
所有变量的类型在定义的那一刻就已经确定,且并没有声明的操作,你能做的顶多就是 myVar : str = None 如此声明,但并不会有任何的效果,只是IDE会将该变量当作str类型给你提供他的方法。
Python万物皆对象的概念加上交互式执行,能让python很好的debug,而无需像C++等需要编译的语言一样单独拉出一个文件来重新测试。但是解释器使得python的速度慢上一大截,CPython的GIL锁虽然优化了单核性能但是多线程是假多线程。
尽管python中没有interface这一关键字,但是仍可以通过abc模块定义一个类似于接口的类。
import abc
class Interface(metaclass=abc.ABCMeta):
@abc.abstractmethod
def test(self): ...
@abc.abstractmethod
def print(self): ...
而继承它的子类必须实现 test、print 这两个方法,否则无法实例化:
class myClass(Interface): ...
a = myClass()
# TypeError: Can't instantiate abstract class r with abstract methods print, test
定义接口时在类名声明部分有一个比较刺眼的地方: (metaclass=abc.ABCMeta)
它与另一个魔术方法相关联:__init_subclass__。在 PEP 487 中有详细介绍,下面举出其中一个示例:
>>> class QuestBase:
... # this is implicitly a @classmethod (see below for motivation)
... def __init_subclass__(cls, swallow, **kwargs):
... cls.swallow = swallow
... super().__init_subclass__(**kwargs)
>>> class Quest(QuestBase, swallow="african"):
... pass
>>> Quest.swallow
'african'
可见,在 QuestBase 类中定义了 __init_subclass__ 这一魔术方法,在 Quest 继承了类 QuestBase,再通过 swallow 传递了 “african” 这一参数。最后再在 QuestBase 中的 __init_subclass__ 定义给了 Quest 类一个 swallow 属性。
因为定义给了 Quest 类一个 swallow ,该成员变量会在任何 Quest 的实例化中出现:
class a: ...
a.test = 1
t1, t2, t3 = a(), a(), a()
print(t1.test, t2.test, t3.test) # 1 1 1
关于abc模块与元类这方面可以搜索元编程。
谈到子类父类,不得不提及 public、protected、private 这三个家伙。
Python中并没有这三个关键字,通常使用约定俗成的 “_” 下划线定义变量名来说明这是什么类型的变量,但这也仅限于约定俗成。
class a:
def __init__(self):
self.__p = 1
self._p = 2
class b(a):
def __init__(self):
super().__init__()
t = b()
print(t._a__p, t._p) # 1 2
可见,通常使用双下划线+变量名的方法说明这是 private 变量,使用单下划线+变量名说明这是 protected 变量,其余定为 public 变量。
但仍可在最后看出,在外部可以使用 _定义该变量的类名__变量名 这个方法来在外部访问双下划线开头的变量(父类、子类内部则直接使用双下划线+变量名即可),而单下划线可随意在外部、内部访问。