Python 3 入门 - 6. 类、标准库及奶酪店

这是本系列最后一篇,介绍类与内置函数,以及简单提及标准库与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++中,有三个概念:OverloadOverrideOverwrite

  • 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

除了前面已经讲到的内容,标准库中还提供了许多模块。如:datetimemathrandomhashlibuuidjsonbase64httpurllib 等等。


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 包。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值