目录
这是本系列最后一篇,介绍类与内置函数,以及简单提及标准库与PyPI。到此,作为一门语言,Python 的主要内容都基本介绍完了,但是要真正用 Python 干一些奇妙的事,还需要继续学习。
类(Class)
关于 类 的详细教程请参考:https://docs.python.org/zh-cn/3/tutorial/classes.html 。另外,在之前的文章里提到过 python 的类没有真正的 私有属性。
类定义与实例化
与函数定义 (def
语句) 一样,类定义必须先执行才能生效。
当一个类定义了 __init__()
方法时,类的实例化操作会自动为新创建的类实例发起调用 __init__()
。
>>> class Complex:
... def __init__(self, realpart, imagpart):
... self.r = realpart
... self.i = imagpart
...
>>> x = Complex(3.0, -4.5)
>>> x.r, x.i
(3.0, -4.5)
数据属性
数据属性(Data Attribute)即实例变量,数据属性不需要声明,像局部变量一样,它们将在第一次被赋值时产生。
>>> class MyClass:
... pass
...
>>> x = MyClass()
>>> x.counter = 1
>>> while x.counter < 10:
... x.counter = x.counter * 2
...
>>> print(x.counter)
16
方法对象与函数
下面代码中,MyClass.foo
是一个函数,而 x.foo
是一个方法对象。方法是从属于“对象”的函数。方法的特殊之处在于实例对象会作为函数的第一个参数被传入。 在下面的示例中,调用 x.foo()
其实就相当于 MyClass.foo(x)
。
调用一个具有 n 个参数的方法就相当于调用再多一个参数的对应函数,这个参数值为方法所属实例对象,位置在其他参数之前。
方法的第一个参数常常被命名为
self
。 这也不过就是一个约定:self
这一名称在 Python 中绝对没有特殊含义。 但是要注意,不遵循此约定会使得你的代码对其他 Python 程序员来说缺乏可读性 …
>>> class MyClass:
... def foo(self):
... print('Hello world.')
...
>>> x = MyClass()
>>> x.foo()
Hello world.
>>> MyClass.foo(x)
Hello world.
实例方法对象也具有属性: foo.__self__
就是带有 foo()
方法的实例对象,而 foo.__func__
则是该方法所对应的函数对象。
>>> x = MyClass()
>>> x.foo.__self__
<__main__.MyClass object at 0x000001DBBA66F3A0>
>>> x.foo.__self__ is x
True
>>> x.foo.__func__ is MyClass.foo
True
类变量与实例变量
一般来说,实例变量用于每个实例的唯一数据,而类变量用于类的所有实例共享的属性和方法。
class Dog:
kind = 'canine' # 类变量
def __init__(self, name):
self.name = name # 实例变量
>>> d = Dog('Fido')
>>> e = Dog('Buddy')
>>> d.kind # shared by all dogs
'canine'
>>> e.kind # shared by all dogs
'canine'
>>> d.name # unique to d
'Fido'
>>> e.name # unique to e
'Buddy'
共享数据可能在涉及 mutable 对象例如列表和字典的时候导致令人惊讶的结果。例如以下代码中的 tricks 列表不应该被用作类变量,因为所有的 Dog 实例将只共享一个单独的列表:
class Dog:
tricks = [] # mistaken use of a class variable
def __init__(self, name):
self.name = name
def add_trick(self, trick):
self.tricks.append(trick)
>>> d = Dog('Fido')
>>> e = Dog('Buddy')
>>> d.add_trick('roll over')
>>> e.add_trick('play dead')
>>> d.tricks # unexpectedly shared by all dogs
['roll over', 'play dead']
正确的类设计应该使用实例变量:
class Dog:
def __init__(self, name):
self.name = name
self.tricks = [] # creates a new empty list for each dog
def add_trick(self, trick):
self.tricks.append(trick)
如果同样的属性名称同时出现在实例和类中,则属性查找会优先选择实例:
>>> class Person:
... name = 'John'
...
>>> a = Person()
>>> a.name = 'Tom'
>>> b = Person()
>>> print(a.name, b.name)
Tom John
每个值都是一个对象,因此具有 类 (也称为 类型),并存储为 object.__class__
。
>>> a.__class__
<class '__main__.Person'>
>>> 'Hello'.__class__
<class 'str'>
>>> (1, 2, 3).__class__
<class 'tuple'>
>>> int('1').__class__
<class 'int'>
继承
作为面向对象的语言,Python当然支持继承。
class SubClass(ClassA): # 继承自 ClassA
...
在C++中,有三个概念:Overload
、Override
、Overwrite
:
Overload
是指同一个类中多个同名函数拥有不同参数,叫做重载
;Override
是指派生类中定义了与基类 名称相同且参数相同 的virtual
函数,叫做覆盖
;Overwrite
是指派生类中的函数在 同名不同参 等情况下隐藏了基类同名函数的情况,叫做改写
。
具体可参考:C++中的Overload、Override和Overwrite
Python类中,所有的方法都是 virtual
方法,即可以被 “覆盖”。基类方法如果调用了同一基类中定义的另一方法,最终可能会调用覆盖它的派生类的方法。例如:
class ClassA:
def foo(self):
print("foo in class A.")
def bar(self):
print("bar in class A.")
self.foo() # 调用实例方法 foo()
class SubClass(ClassA):
def foo(self):
print("foo in sub-class.")
>>> x = SubClass()
>>> x.foo()
foo in sub-class.
>>> x.bar()
bar in class A.
foo in sub-class.
Python解析对象的属性引用时遵循一定的顺序,首先在当前类中查找,如果找不到再递归地在基类中查找。以下代码的表现类似于C++中的 Overwrite
。
class ClassA:
def foo(self):
print("foo in class A.")
def bar(self):
print("bar in class A.")
self.foo()
class SubClass(ClassA):
def foo(self):
print("foo in sub-class.")
def bar(self, message): # 改写了 bar(),增加了 message 参数
super().bar()
print("bar in sub-class:", message)
>>> x = SubClass()
>>> x.bar('Hello') # 调用子类方法成功
bar in class A.
foo in sub-class.
bar in sub-class: Hello
>>> x.bar() # 调用基类方法失败
TypeError("bar() missing 1 required positional argument: 'message'")
>>> ClassA.bar(x) # 调用基类方法成功
bar in class A.
foo in sub-class.
isinstance()
与 issubclass()
函数:
>>> print(isinstance(x, SubClass))
True
>>> print(isinstance(x, ClassA))
True
>>> print(issubclass(ClassA, ClassA))
True
>>> print(issubclass(SubClass, ClassA))
True
多重继承
Python 支持多重继承。多重继承的语法很简单,例如:
class SubClass(ClassA, ClassB) # 继承自 ClassA 与 ClassB
...
多重继承的一个关键问题是:当调用 super().foo()
时,到底调用的是哪个基类的方法?
对于多数应用来说,在最简单的情况下,你可以认为搜索从父类所继承属性的操作是 深度优先、从左至右 的,当层次结构中存在重叠时不会在同一个类中搜索两次。 因此,如果某一属性在 DerivedClassName 中未找到,则会到 Base1 中搜索它,然后(递归地)到 Base1 的基类中搜索,如果在那里未找到,再到 Base2 中搜索,依此类推。
真实情况比这个更复杂一些;方法解析顺序会动态改变以支持对 super() 的协同调用。 这种方式在某些其他多重继承型语言中被称为后续方法调用,它比单继承型语言中的 super 调用更强大。
可以使用 mro()
方法来查看方法解析顺序。
class ClassA:
def foo(self):
print("foo in class A.")
class ClassB:
def foo(self):
print("foo in class B.")
class SubClass(ClassA, ClassB):
def foo(self):
super().foo()
print("foo in sub-class.")
>>> x = SubClass()
>>> x.foo()
foo in class A.
foo in sub-class.
>>> print(SubClass.mro())
[<class '__main__.SubClass'>, <class '__main__.ClassA'>, <class '__main__.ClassB'>, <class 'object'>]
>>> print(issubclass(SubClass, ClassB))
True
类方法与静态方法
@classmethod
:把方法封装成类方法。类方法隐含的第一个参数就是类,就像实例方法接收实例作为参数一样。
@staticmethod
:将方法转换为静态方法。静态方法不会接收隐式的第一个参数。类似于 Java 和 C++ 中的静态方法。
class MyClass:
name = 'A Demo Class'
@classmethod
def foo(cls, message):
print('foo:', cls.name, message) # 类方法可以通过 cls 访问类变量
@staticmethod
def bar(message):
print('bar:', message) # 静态方法如果要访问类变量,需要显式地使用 MyClass.name
>>> MyClass.foo('hello')
foo: A Demo Class hello
>>> MyClass.bar('hello')
bar: hello
>>> x = MyClass()
>>> x.foo('world')
foo: A Demo Class world
>>> x.bar('world')
bar: world
常用内置函数
完整的内置函数参考:https://docs.python.org/zh-cn/3/library/functions.html
其中包括了 bytes()
、list()
、range()
、str()
等构造函数,另外之前也已经见到过 help()
、id()
、isinstance()
、issubclass()
、len()
、print()
、repr()
、reversed()
、round()
、sorted()
、super()
等函数。
下面再列出几个常用的内置函数:
abs()、hex()、divmod()、max()、min()
abs(x)
:返回一个数的绝对值。
hex(x)
:将整数转换为以“0x”为前缀的小写十六进制字符串。(注意区别于 bytes.hex()
和 bytearray.hex()
)
divmod(a,b)
:返回两个数做整数除法的商和余数。
max(iterable, *[, key, default])
:返回可迭代对象中最大的元素。
max(arg1, arg2, *args[, key])
:返回两个及以上实参中最大的。
min(iterable, *[, key, default])
:返回可迭代对象中最小的元素。
min(arg1, arg2, *args[, key])
:返回两个及以上实参中最小的。
getattr()、hasattr()、setattr()
getattr(object, name[, default])
:返回对象命名属性的值。
hasattr(object, name)
:如果字符串 name 是对象的属性之一的名称,则返回 True,否则返回 False。
setattr(object, name, value)
:给对象属性赋值。如 setattr(x, 'foobar', 123)
等价于 x.foobar = 123
。
chr()、ord()
chr(i)
:返回整数 i 对应的 Unicode 字符的字符串格式。
ord(c)
:返回 Unicode 字符 c 的整数码值。
>>> hex(ord('中'))
'0x4e2d'
>>> chr(0x4e2d)
'中'
>>> '\u4e2d'
'中'
zip()
zip(*iterables, strict=False)
:在多个迭代器上并行迭代,从每个迭代器返回一个数据项组成元组。
>>> for item in zip([1, 2, 3], ['sugar', 'spice', 'everything nice']):
... print(item)
...
(1, 'sugar')
(2, 'spice')
(3, 'everything nice')
>>> for a,b in zip([1, 2, 3], ['sugar', 'spice', 'everything nice']):
... print(a,b)
...
1 sugar
2 spice
3 everything nice
标准库
Python 3 标准库参考:https://docs.python.org/zh-cn/3/library/index.html
除了前面已经讲到的内容,标准库中还提供了许多模块。如:datetime
、math
、random
、hashlib
、uuid
、json
、base64
、http
、urllib
等等。
PyPI(cheeseshop)
https://github.com/catherinedevlin/python_learners_glossary
PyPI, pronounced “Pie-Pee-Eye” and also known as The Cheeseshop, is the “Python Packaging Index”. It is where you can publish and download open source Python packages.
PyPI 是 Python Package Index 的缩写,以前被叫做 cheeseshop,是 Python 官方的开源包索引,可以理解为 Java 的 Maven 库。使用 pip install
命令就可以安装上面的 python 包。