内容简介:到目前为止,我们一直在使用函数和函数包,以及定义我们自己的函数。 但事实证明,我们一直在使用对象,我们只是没有认识到它们。 例如,字符串甚至整数都是对象:
大揭秘
到目前为止,我们一直在使用函数和函数包,以及定义我们自己的函数。 但事实证明,我们一直在使用对象,我们只是没有认识到它们。 例如,
x = 'Hi' x.lower() # 'hi'
字符串 x
是我们可以发送消息的对象。
print( type(x) ) # <class 'str'>
甚至整数都是对象:
print(dir(99)) # ['__abs__', '__add__', '__and__', '__bool__', '__ceil__', '__class__', '__delattr__', '__dir__', '__divmod__', '__doc__', '__eq__', '__float__', '__floor__', '__floordiv__', '__format__', '__ge__', '__getattribute__', '__getnewargs__', '__gt__', '__hash__', '__index__', '__init__', '__init_subclass__', '__int__', '__invert__', '__le__', '__lshift__', '__lt__', '__mod__', '__mul__', '__ne__', '__neg__', '__new__', '__or__', '__pos__', '__pow__', '__radd__', '__rand__', '__rdivmod__', '__reduce__', '__reduce_ex__', '__repr__', '__rfloordiv__', '__rlshift__', '__rmod__', '__rmul__', '__ror__', '__round__', '__rpow__', '__rrshift__', '__rshift__', '__rsub__', '__rtruediv__', '__rxor__', '__setattr__', '__sizeof__', '__str__', '__sub__', '__subclasshook__', '__truediv__', '__trunc__', '__xor__', 'bit_length', 'conjugate', 'denominator', 'from_bytes', 'imag', 'numerator', 'real', 'to_bytes']
类 是对象的蓝图,基本上是类型的名称,在这种情况下是 str
。 对象称为类的 实例 。
在 x.lower()
中,我们将 lower
消息发送到 x
字符串对象。 消息实际上只是与类/对象相关的函数。
x.lower # <function str.lower>
在不支持对象学习编程的语言中,我们会做类似的事情:
lower(x)
Python 有函数和对象重编程,这就是为什么有 x.lower()
和:
len(x) # 2
函数或“消息”的选择取决于库设计者,但是 lower
仅对字符串有意义,因此将它与 str
的定义组合起来是有意义的。
然而,在实现方面, x.lower()
实际上实现为 str.lower(x)
其中 str
是字符串的类定义。 电脑处理器了解函数调用; 他们不理解对象,所以我们在 Python 解释器本身中执行了这个翻译。
包 VS 对象成员
让我们直截了当。点 .
运算符在 Python 中重载,表示包成员和对象成员访问。你已经熟悉了这个:
import numpy as np np.array([1,2,3]) # array([1, 2, 3])
import math math.log(3000) # 8.006367567650246
阅读代码时,这是一个常见的混淆点。 当我们看到 a.f()
时,我们不知道函数 f
是由 a
标识的包的成员,还是由 a
引用的对象的成员。
在 wordsim
项目中,你定义了一个名为 wordsim.py
的文件,然后我的 test_wordsim.py
文件执行 from wordsim import *
来导入 wordsim.py
中的所有函数。
练习
在下文中,将 标识符 (单词)标识为包或函数或字段:
np.log(3) np.linalg.norm(v) from sklearn.ensemble import RandomForestRegressor pd.read_csv("foo.csv") pd.read_csv 'hi'.lower() 'hi'.lower df_train.columns np.pi img = img.convert("L")
现在,确定子表达式的数据类型,并将 标识符 (单词)标识为包或函数或字段:
df["saledate"].dt.year df_train.isnull().any().head(60)
字段 VS 方法
对象具有函数,我们将其称为 方法 ,以将它们与不与对象关联的函数区分开来。 对象也有变量,我们称之为 字段 或 实例变量 。
字段是对象的 状态 。 方法是对象的 行为 。
我们一直在使用字段,例如 df.columns
,它获取数据帧中的列名的列表。
import datetime now = datetime.date.today() print( type(now) ) print( now.year ) # access field year print( now.month ) ''' <class 'datetime.date'> 2018 8 '''
如果尝试在没有括号的情况下访问对象函数,则表达式将计算为函数对象本身而不是调用它:
s='hi' s.title # <function str.title>
简单的对象定义
类是多个对象的蓝图,通常称为 实例 。 类*封装了对象的状态和行为。
想象一下外星人在你家后院的土地上,并要求你描述一辆汽车。 您可能会描述其属性,例如轮子的数量及其功能,例如可以启动和停止。 这些是状态和行为。 通过定义它们,我们有效地定义了对象。 类名仅仅为实体命名。
按照惯例,类名称应该像“Point”一样大写。
作为对象替代品的元组
对象的字段是我们想要关联在一起的数据项。 例如,如果我想跟踪书名/作者,我可以使用元组列表:
from lolviz import * books = [ ('Gridlinked', 'Neal Asher'), ('Startide Rising', 'David Brin') ] objviz(books)
for b in books: print(f"{b[1]}: {b[0]}") ''' Neal Asher: Gridlinked David Brin: Startide Rising '''
# Or, more fancy for title, author in books: print(f"{author}: {title}") ''' Neal Asher: Gridlinked David Brin: Startide Rising '''
为了在两种情况下访问元组的元素,我们必须跟踪我们头脑中的顺序。 换句话说,我们必须访问元组元素,就像它们是列表元素一样。
形式对象
更好的方法是正式声明,作者和标题数据元素应该封装到称为书籍的单个实体中。我认为 Python 的规范非常古怪,但它非常灵活。 例如,我们可以定义一个没有方法没有字段的对象,但是可以使用赋值语句动态添加字段:
class Book: pass b = Book() print(b) b.title = 'Gridlinked' b.author = 'Neal Asher' print(b.title, b.author) objviz(b) ''' <__main__.Book object at 0x115c51fd0> Gridlinked Neal Asher '''
但这并不能让我们定义与该对象相关的方法(很容易)。让我们看看我们的第一个真正的类定义,它包含一个名为 构造器 的函数。
class Book: def __init__(self, title, author): self.title = title self.author = author self.chapters = []
构造器通常根据参数设置初始和默认字段值。
在对象中定义的所有方法,函数必须有一个名为 self
的显式第一个参数。 这是正在考虑的对象。
然后我们可以使用实例创建语法 Book(..., ...)
来创建一列 Book
类的书籍对象或实例:
books = [ Book('Gridlinked', 'Neal Asher'), Book(title='David Brin', author='Startide Rising') ]
objviz(books)
for b in books: print(f"{b.author}: {b.title}") # access fields ''' Neal Asher: Gridlinked Startide Rising: David Brin '''
请注意,我们不会将 self
参数传递给构造函数。 它在调用一侧隐藏,但在定义一侧出现!
顽皮的行为
还要注意我们一直在使用构造函数设置对象的字段,Python 以其无限的灵活性允许你做非常顽皮的事情,比如在任意对象上设置字段:
class Foo: pass # just says "empty" x = Foo() x.foo = 3
即使类本身没有定义 foo
,也不会出错!
您甚至可以 动态添加方法 。
定义方法
如果您尝试打印一本书,您将只看到类型信息和物理内存地址:
print(books[0]) # <__main__.Book object at 0x115c51eb8>
class Book: def __init__(self, title, author): self.title = title self.author = author def __str__(self): # called when conversion to string needed like print return f"Book({self.title}, {self.author})" def __repr__(self): # called in interactive mode return self.__str__() # call the string books = [ Book('Gridlinked', 'Neal Asher'), Book('Startide Rising', 'David Brin') ]
print(books[0]) # calls __str__() books[0] # calls __repr__() ''' Book(Gridlinked, Neal Asher) Book(Gridlinked, Neal Asher) '''
确保使用 self.x
来引用字段 x
,否则你在方法中创建一个局部变量:
class Foo: def __init__(self): self.x = 0 def foo(self): x = 3 # WARNING: does not alter the field! should be self.x
让我们创建另一种设置销售图书数量的方法。
class Book: def __init__(self, title, author): self.title = title self.author = author self.sold = 0 # set default def sell(self, n): self.sold += n def __str__(self): # called when conversion to string needed like print return f"Book({self.title}, {self.author}, sold={self.sold})" def __repr__(self): # called in interactive mode return self.__str__() # call the string
b = Book('Gridlinked', 'Neal Asher') print(b) b.sell(100) # Book.sell(b, 100) print(b) ''' Book(Gridlinked, Neal Asher, sold=0) Book(Gridlinked, Neal Asher, sold=100) '''
注意:在方法定义中,我们调用同一对象上的其他方法,使用 self.foo(...)
调用方法 foo
。
理解方法和函数的关键
b.sell(100)
方法调用 由 Python 解释器翻译并执行为 函数调用 Book.sell(b, 100)
。 b
变成参数 self
,所以 sell()
函数正在更新 book b
。
为什么我们更喜欢 b.sell(100)
而不是 Book.sell(b, 100)
:我们不仅仅在函数,并且在对象之间来回传递消息。 我们说 dog.bark()
,不是 bark(dog)
,或我们说 ball.inflate()
,而不是 inflate(ball)
。
练习
真实世界的对象包含......和......
软件对象的状态存储在......
软件对象的行为通过......公开
软件对象的蓝图称为...
练习
定义一个名为 Point
的类,它有一个构造函数,接受 x
, y
坐标并使它们成为类的字段。
定义方法 distance(q)
,它接受一个 Point
并返回从 self
到 q
的欧几里德距离。
使用这个来测试:
p = Point(3,4) q = Point(5,6) print(p.distance(q))
添加方法 __str__
,以便 print(q)
打印出类似 (3, 4)
的东西。
答案
import numpy as np class Point: def __init__(self, x, y): self.x = x self.y = y def distance(self, other): return np.sqrt( (self.x - other.x)**2 + (self.y - other.y)**2 ) def __str__(self): return f"({self.x},{self.y})"
p = Point(3,4) q = Point(5,6) print(p, q) print(p.distance(q)) ''' (3,4) (5,6) 2.8284271247461903 '''
继承
定义一些与我们已经理解的东西相关的新东西,通常要容易得多。 在编程中也是如此。 让我们从一个帐户对象开始:
class Account: def __init__(self, starting): self.balance = starting def add(self, value): self.balance += value def total(self): return self.balance
a = Account(100.0) a.add(15) a.total() # 115.0
objviz(a)
继承的行为类似于 import
,或从另一个类导入操作到新类。 ( 请注意,这不是真的,但我们可以将其视为包含,对于我们目的。 )
如果我们不指定超类,则类 object
是隐式超类。 该类称为类层次结构的根,并定义了许多标准方法:
x = object() # yes, we can make a generic object print(dir(x)) # ['__class__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__']
我们可以定义一个有息账户,因为它与普通账户不同:
class InterestingAccount(Account): # derive from super class to get subclass def __init__(self, starting, rate): self.balance = starting # super().__init__(starting) self.rate = rate def total(self): # OVERRIDE method return self.balance + self.balance * self.rate b = InterestingAccount(100.0, 0.15) b.add(15) b.total()
objviz(b)
要点是我们可以使用 add()
而不必在 InterestingAccount
中重新定义它,而 InterestingAccount
也可以覆盖帐户的 total()
。 我们已经 复用 和 覆盖 以前的功能。您可以将超类视为定义一些初始函数,我们可以在子类中重用或覆盖他们。
我们还可以通过添加不在超类中的方法来 扩展 功能。
class InterestingAccount(Account): # derive from super class to get subclass def __init__(self, starting, rate): super().__init__(starting) # does self.balance = starting above self.rate = rate def total(self): # OVERRIDE method return self.balance + self.balance * self.rate def profit(self): return self.balance * self.rate
b = InterestingAccount(100.0, 0.15) b.add(15) b.profit() # 17.25
a = Account(100.0) b = InterestingAccount(100.0, 0.15) print(type(a)) print(type(b)) ''' <class '__main__.Account'> <class '__main__.InterestingAccount'> '''
类的定义实际上本身是对象,您可以使用任何对象的秘密字段访问它们:
print(b.__class__) print(b.__class__.__base__) ''' <class '__main__.InterestingAccount'> <class '__main__.Account'> '''
练习
- 什么是类?
- 类和实例之间有什么区别?
- 使用不带参数的构造函数定义类
Foo
的新实例。 - 访问对象字段的语法是什么?
- 方法与函数有何不同?
-
__init__
方法有什么作用? - 给定类
Employee
和Manager
,哪个是子类或者超类? - 子类中的方法可以调用超类中定义的方法吗?
- 如何覆盖从超类继承的方法?
动态调度(高级)
当你调用 b.add(15)
时,Python 在 b
( InterestingAccount
)的对象定义中查找函数 add
。 因为我们从超类继承了该方法,所以子类知道它。 当我们调用 b.total()
时,Python再次查找 InterestingAccount
中的方法并找到一个重写方法。 这就是为什么 b.total()
不会调用 Account
版本。
这种行为是可取的,但起初非常混乱。 下面是一个实例,其中我添加了一个 __str__
方法给超类:
class Account: def __init__(self, starting): self.balance = starting def add(self, value): self.balance += value def total(self): return self.balance def __str__(self): return f"Balance {self.total()}" # can call 2 different functions class InterestingAccount(Account): # derive from super class to get subclass def __init__(self, starting, rate): self.balance = starting self.rate = rate def total(self): # OVERRIDE method return self.balance + self.balance * self.rate def profit(self): return self.balance * self.rate
微妙的部分是 Account
中的 __str__
调用 Account.total()
或 InterestingAccount.total()
,具体取决于 self
的类型:
a = Account(100.0) b = InterestingAccount(100.0, 0.15) print(a) # calls Account.total() print(b) # calls InterestingAccount.total() ''' Balance 100.0 Balance 115.0 '''
练习
定义一个继承自 Point
的 Point3D
。
定义接受 x, y, z
值并设置字段的构造函数。 调用 super().__init__(x, y)
来调用超类的构造函数。
定义/覆盖 distance(q)
,以便它处理 3D 字段值来返回距离。
使用这个来测试:
p = Point3D(3,4,9) q = Point3D(5,6,10) print(p.distance(q))
添加方法 __str__
,以便 print(q)
打印出类似 (3, 4, 5)
的东西。记住:
$dist(x,y) = \sqrt{(x_1-y_1)^2 + (x_2-y_2)^2 + (x_3-y_3)^2)}$
答案
import numpy as np class Point3D(Point): def __init__(self, x, y, z): # reuse/refine super class constructor super().__init__(x,y) self.z = z def distance(self, other): return np.sqrt( (self.x - other.x)**2 + (self.y - other.y)**2 + (self.z - other.z)**2 ) def __str__(self): return f"({self.x},{self.y},{self.z})"
p = Point3D(3,4,9) q = Point3D(5,6,10) print(p.distance(q)) # 3.0
理由和一般思想
因为猎人 - 收集者的思想将世界视为通过发送消息交互的对象集合,所以 OO 编程范例很好地映射到现实世界问题,我们试图通过计算机模拟它们。 此外,在使用我们的思维方式编程时,我们处于最佳状态。
通常,在编写软件时,我们会尝试将现实实体映射到编程结构中。 如果我们有了单词问题,名词通常会成为对象,而动词通常会成为这些对象中的方法。
因为我们可以指定不同类型的对象如何相似,所以我们可以定义新对象,因为它们与现有对象不同。 通过按类别/共性/相似性正确地关联类似的类,作为继承的副作用,代码重用就出现了。
非 OO 语言是不灵活/脆弱的,因为必须指定确切的变量类型。在 OO 语言中, 多态 是使用单个类型引用,来引用相似但不同类型的分组的能力。
以上所述就是小编给大家介绍的《USF MSDS501 计算数据科学中文讲义 2.8 面向对象编程》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- 开源技术 * IBM 微讲堂:Istio 系列(讲义下载&课程回放)
- USF MSDS501 计算数据科学中文讲义 1.3 播放声音
- USF MSDS501 计算数据科学中文讲义 1.2 Python 工具的初次尝试
- 面向Python,面向对象(基础)
- 面向Python,面向对象(基础3)
- Swift 中的面向协议编程:是否优于面向对象编程?
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
写给大忙人看的C++
【美】Brian Overland(布莱恩.奥弗兰德) / 卢涛、李颖 / 电子工业出版社 / 2015-8 / 109.00
《写给大忙人看的C++》全面介绍了C++语言知识,既提供了学习C++语言最新功能的捷径,也为快速找到特定问题的答案提供了便利。《写给大忙人看的C++》简明地描述了C++核心语言和标准库中几乎所有的函数、对象和运算符,一目了然地显示了语法、结构和重要函数的信息,内容组织形式便于快速查找信息。《写给大忙人看的C++》精选了实用的例子来深入地讲解概念,还提供了富有挑战性的练习及参考答案,便于读者举一反三......一起来看看 《写给大忙人看的C++》 这本书的介绍吧!