程序员书籍笔记 程序员书籍笔记
  • JavaScript
  • HTML/CSS
  • PHP
  • Python
  • Go
  • 数据库
  • 容器
  • 微服务
  • 消息队列
  • 搜索引擎
  • 大数据
  • 鸟哥Linux私房菜
其他
  • 大数据
  • 深度学习
APP下载 (opens new window)
GitHub (opens new window)
  • JavaScript
  • HTML/CSS
  • PHP
  • Python
  • Go
  • 数据库
  • 容器
  • 微服务
  • 消息队列
  • 搜索引擎
  • 大数据
  • 鸟哥Linux私房菜
其他
  • 大数据
  • 深度学习
APP下载 (opens new window)
GitHub (opens new window)
  • PHP

  • Python

    • python语言及其运用

      • 基本数据类型和容器
      • 代码结构、模块、包和程序
      • 对象和类
        • 什么是对象
        • 使用class定义类
        • 继承
        • 覆盖方法
        • 添加新方法
        • 使用super来使用父方法
        • 使用属性对特性进行设置和访问
        • 使用名称保护代码私有性
        • 方法类型
        • 鸭子类型
        • 特殊方法
        • 组合
        • 何时使用类和对象而不是模块
      • 像高手一样玩转数据
  • Go

  • 后端
  • Python
  • python语言及其运用
小游
2021-05-13

对象和类

# 什么是对象

Python 里的所有数据都是以对象形式存在的,无论是简单的数 字类型还是复杂的代码模块。然而,Python 特殊的语法形式巧妙地将实现对象机制的大量 细节隐藏了起来。输入 num = 7 就可以创建一个值为 7 的整数对象,并且将这个对象赋值 给变量 num。事实上,在 Python 中,只有当你想要创建属于自己的对象或者需要修改已有 对象的行为时,才需要关注对象的内部实现细节。

# 使用class定义类

下面我们简单定义一个类,并使用这个类

class Person():
    pass

one = Person()
1
2
3
4

下面我们来使用构造函数,构造函数这里初始化了一个类,然后设置了值

class Person():
    def __init__(self,name):
        self.name = name

one = Person("小游")
print(one.name)
--------
小游
1
2
3
4
5
6
7
8

# 继承

只需要在类的括号里面加上父类的对象就可以实现继承了

class Father():
    name = "父类"
    def say(self):
        print(self.name)

class Person(Father):
    pass

one = Person()
one.say()
--------
父类
1
2
3
4
5
6
7
8
9
10
11
12

# 覆盖方法

在子类中,可以覆盖任何父类的方 法,包括 init()

class Father():
    name = "父类"
    def say(self):
        print(self.name)

class Person(Father):
    name = '子类'
    def say(self):
        print(self.name)

one = Person()
one.say()
----------
子类
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 添加新方法

子类还可以添加父类中没有的方法。

class Father():
    name = "父类"
    def say(self):
        print(self.name)

class Person(Father):
    name = '子类'
    def say(self):
        print(self.name)
    def hello(self):
        print("hello")

one = Person()
one.hello()
------
hello
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# 使用super来使用父方法

class Father():
    name = "父类"
    def say(self):
        print(self.name)

class Person(Father):
    name = '子类'
    def say(self):
        super().say()
    def hello(self):
        print("hello")

one = Person()
one.say()
---------
子类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# 使用属性对特性进行设置和访问

有一些面向对象的语言支持私有特性。这些特性无法从对象外部直接访问,我们需要编写 getter 和 setter 方法对这些私有特性进行读写操作。

Python 不需要 getter 和 setter 方法,因为 Python 里所有特性都是公开的,使用时全凭自觉。 如果你不放心直接访问对象的特性,可以为对象编写 setter 和 getter 方法。但更具 Python 风格的解决方案是使用属性(property) 。

当我们尝试访问name时会自动调用get_name方法

class Duck():
    def __init__(self, input_name):
        self.hidden_name = input_name
    def get_name(self):
        print('inside the getter')
        return self.hidden_name
    def set_name(self, input_name):
        print('inside the setter')
        self.hidden_name = input_name
    name = property(get_name, set_name)

one = Duck("小鸭子")
one.name
--------
inside the getter
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

另外一个方式就是使用修饰符,下面这个方式和上面是一样的

class Duck():
    def __init__(self, input_name):
        self.hidden_name = input_name
    @property
    def name(self):
        print('inside the getter')
        return self.hidden_name
    @name.setter
    def name(self, input_name):
        print('inside the setter')
        self.hidden_name = input_name

one = Duck("小鸭子")
one.name
1
2
3
4
5
6
7
8
9
10
11
12
13
14

与直接访问特性相比,使用 property 还有一个巨大的优势:如果你改变了某个特性的定义, 只需要在类定义里修改相关代码即可,不需要在每一处调用修改。

# 使用名称保护代码私有性

前面的 Duck 例子中,为了隐藏内部特性,我们曾将其命名为 hidden_name。其实,Python 对那些需要刻意隐藏在类内部的特性有自己的命名规范:由连续的两个下划线开头(__)

class Duck():
    def __init__(self, input_name):
        self.__name = input_name
    @property
    def name(self):
        print('inside the getter')
        return self.__name
    @name.setter
    def name(self, input_name):
        print('inside the setter')
        self.__name = input_name

one = Duck("小鸭子")
one.name
1
2
3
4
5
6
7
8
9
10
11
12
13
14

如果我们想访问私有变量,则会报错

print(one.__name)
------
Traceback (most recent call last):
  File "E:/CODE/Python/入门/main.py", line 15, in <module>
    print(one.__name)
AttributeError: 'Duck' object has no attribute '__name'
1
2
3
4
5
6

# 方法类型

在类的定义中, self 作为第一个参数的方法都是实例方法(instance method)。它们在 创建自定义类时最常用。实例方法的首个参数是 self,当它被调用时,Python 会把调用该 方法的对象作为 self 参数传入

与之相对,类方法(class method)会作用于整个类,对类作出的任何改变会对它的所有实 例对象产生影响。在类定义内部,用前缀修饰符 @classmethod 指定的方法都是类方法。与 实例方法类似,类方法的第一个参数是类本身。在 Python 中,这个参数常被写作 cls,因 为全称 class 是保留字,在这里我们无法使用。下面的例子中,我们为类 A 定义一个类方 法来记录一共有多少个类 A 的对象被创建:

class A():
    count = 0
    def __init__(self):
        A.count += 1
    def exclaim(self):
        print("I'm an A!")
    @classmethod
    def kids(cls):
        print("A has", cls.count, "little objects.")

easy_a = A()
breezy_a = A()
wheezy_a = A()
A.kids()
-------------
A has 3 little objects.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

上面的代码中,我们使用的是 A.count(类特性),而不是 self.count(可能是对象 的特性)。在 kids() 方法中,我们使用的是 cls.count,它与 A.count 的作用一样。

静态方法如下,我们可以直接调用,不需要初始化

class A():
    @staticmethod
    def kids():
        print("这个是静态方法")

A.kids()
-----
这个是静态方法
1
2
3
4
5
6
7
8

# 鸭子类型

Python 对实现多态(polymorphism)要求得十分宽松,这意味着我们可以对不同对象调用 同名的操作,甚至不用管这些对象的类型是什么。

class Quote():
    def __init__(self, person, words):
        self.person = person
        self.words = words
    def who(self):
        return self.person
    def says(self):
        return self.words + '.'

class QuestionQuote(Quote):
    def says(self):
        return self.words + '?'

class ExclamationQuote(Quote):
    def says(self):
        return self.words + '!'

hunter = Quote('小游', "我敲代码")
print(hunter.who(), '说:', hunter.says())
hunter = QuestionQuote('小游', "我敲代码")
print(hunter.who(), '问:', hunter.says())
hunter = ExclamationQuote('小游', "我敲代码")
print(hunter.who(), '说:', hunter.says())
----------
小游 说: 我敲代码.
小游 问: 我敲代码?
小游 说: 我敲代码!
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

# 特殊方法

class Word():
    def __init__(self, text):
        self.text = text
    def equals(self, word2):
        return self.text.lower() == word2.text.lower()

first = Word('ha')
second = Word('HA')
third = Word('eh')

print(first.equals(second))
---------
True
1
2
3
4
5
6
7
8
9
10
11
12
13

我们成功定义了 equals() 方法来进行小写转换并比较。但试想一下,如果能通过 if first == second 进行比较的话岂不更妙?这样类会更自然,表现得更像一个 Python 内置的类。

class Word():
    def __init__(self, text):
        self.text = text
    def __eq__(self, other):
        return self.text.lower() == other.text.lower()

first = Word('ha')
second = Word('HA')
third = Word('eh')

print(first==second)
----------
True
1
2
3
4
5
6
7
8
9
10
11
12
13

太神奇了!是不是如同魔术一般?仅需将方法名改为 Python 里进行相等比较的特殊方法名 eq() 即可。表 6-1 和表 6-2 列出了最常用的一些魔术方法

__eq__(self, other) self == other
__ne__(self, other) self != other
__lt__(self, other) self < other
__gt__(self, other) self > other
__le__(self, other) self <= other
__ge__(self, other) self >= other
# 和数学有关的魔术方法
__add__(self, other) self + other
__sub__(self, other) self - other
__mul__(self, other) self * other
__floordiv__(self, other) self // other
__truediv__(self, other) self / other
__mod__(self, other) self % other
__pow__(self, other) self ** other
# 其他种类的魔术方法
__str__(self) str(self)
__repr__(self) repr(self)
__len__(self) len(self)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

# 组合

如果你想要创建的子类在大多数情况下的行为都和父类相似的话(子类是父类的一种特 殊情况,它们之间是 is-a 的关系),使用继承是非常不错的选择。建立复杂的继承关系确 实很吸引人,但有些时候使用组合(composition)或聚合(aggregation)更加符合现实的 逻辑(x 含有 y,它们之间是 has-a 的关系)。

class Bill():
    def __init__(self, description):
        self.description = description

class Tail():
    def __init__(self, length):
        self.length = length

class Duck():
    def __init__(self, bill, tail):
        self.bill = bill
        self.tail = tail
    def about(self):
        print('描述:', bill.description, ' 长度:',
              tail.length)

tail = Tail('描述信息')
bill = Bill('长度信息')
duck = Duck(bill, tail)
duck.about()
------
描述: 长度信息  长度: 描述信息
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

上面这个代码其实就是把两个类组合到一个Duck这个大类里去了

# 何时使用类和对象而不是模块

有一些方法可以帮助你决定是把你的代码封装到类里还是模块里。

  • 当你需要许多具有相似行为(方法)但不同状态(特性)的实例时,使用对象是最好的 选择。
  • 类支持继承,但模块不支持。
  • 如果你想要保证实例的唯一性,使用模块是最好的选择。不管模块在程序中被引用多少 次,始终只有一个实例被加载。(对 Java 和 C++ 程序员来说,如果读过 Erich Gamma 的《设 计模式:可复用面向对象软件的基础》,可以把 Python 模块理解为单例。)
  • 如果你有一系列包含多个值的变量,并且它们能作为参数传入不同的函数,那么最好 将它们封装到类里面。举个例子,你可能会使用以 size 和 color 为键的字典代表一张 彩色图片。你可以在程序中为每张图片创建不同的字典,并把它们作为参数传递给像 scale() 或者 transform() 之类的函数。但这么做的话,一旦你想要添加其他的键或者 函数会变得非常麻烦。为了保证统一性,应该定义一个 Image 类,把 size 和 color 作 为特性,把 scale() 和 transform() 定义为方法。这么一来,关于一张图片的所有数据 和可执行的操作都存储在了统一的位置。
  • 用最简单的方式解决问题。使用字典、列表和元组往往要比使用模块更加简单、简洁且 快速。而使用类则更为复杂。

命名元组

命名元组是元组的子类,你既可以通过名称(使用 .name)来访问其中的值,也可以通过 位置进行访问(使用 [offset]),我们使用命名元组来实现DUCK类

from collections import namedtuple

Duck = namedtuple('Duck', 'bill tail')
duck = Duck('wide orange', 'long')
print(duck)

Duck(bill='wide orange', tail='long')
print(duck.bill)
print(duck.tail)
-----------
Duck(bill='wide orange', tail='long')
wide orange
long
1
2
3
4
5
6
7
8
9
10
11
12
13

我们甚至可以使用字典来构造命名元祖

from collections import namedtuple

Duck = namedtuple('Duck', 'bill tail')
parts = {'bill': 'wide orange', 'tail': 'long'}
duck2 = Duck(**parts)
print(duck2)
---------
Duck(bill='wide orange', tail='long')
1
2
3
4
5
6
7
8

命名元组是不可变的,但你可以替换其中某些域的值并返回一个新的命名元组:

from collections import namedtuple

Duck = namedtuple('Duck', 'bill tail')
parts = {'bill': 'wide orange', 'tail': 'long'}
duck2 = Duck(**parts)
duck3 = duck2._replace(bill="参数1",tail="参数2")
print(duck2)
print(duck3)
-------
Duck(bill='wide orange', tail='long')
Duck(bill='参数1', tail='参数2')
1
2
3
4
5
6
7
8
9
10
11

我们可以向字典中添加新的值,但是无法对命名元祖进行操作。

使用命名元组的好处:

  • 它无论看起来还是使用起来都和不可变对象非常相似;
  • 与使用对象相比,使用命名元组在时间和空间上效率更高;
  • 可以使用点号(.)对特性进行访问,而不需要使用字典风格的方括号;
  • 可以把它作为字典的键
编辑 (opens new window)
上次更新: 2021/05/14, 21:28:33
代码结构、模块、包和程序
像高手一样玩转数据

← 代码结构、模块、包和程序 像高手一样玩转数据→

Theme by Vdoing | Copyright © 2021-2021 小游
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式