跳转至

Part3 Object-Oriented Programming


3.1 Terminology

object:

class的实例化结果,可以理解成一个具体的事物,其封装了自身的信息)。

class:

object的模板,定义了特定类型的object的行为。多个object可能是同一class的实例,不同的class之间也可能存在关联。

attribute:

用来表示object状态或特征的数据,具体还可分为class attribute和instance attribute等。

method:

与object交互的方式,是一种作用于特定类型的object的函数。

Example

list实际上是一个内置的class。

1
2
3
4
5
>>> s=list(range(3))
>>> s
[0, 1, 2]
>>> type(s)
<class 'list'> 

list相关的method有:append, extend, insert, pop, remove等。


3.2 Operation on an Example

Class Statement:

在创建class时,往往不能忽略的是__init__。这是一个特殊的被自动调用的method,用于初始化object。self代指class的实例。

例如,下面给出一个描述银行账户的classAccount

class Account:

    def __init__(self,account_holder):
        self.balance=0                      # attribute1: 余额
        self.holder=account_holder          # attribute2: 持有者

    def deposit(self,amount):               # method1: 存款
        self.balance=self.balance+amount
        return self.balance

    def withdraw(self,amount):              # method2: 取款
        if amount>self.balance:
            return 'Insufficient funds'
        self.balance=self.balance-amount
        return self.balance

Creating Instance:

使用classname()创建一个新的object,参数即为__init__对应的参数。

>>> a=Account('Alan')

创建新的object之后,attribute默认可以改变,可以随时添加新的attribute,另一个object也可以作为attribute:

>>> a.balance
0
>>> a.balance=12
>>> a.balance
12
>>> b=Account('Ada')
>>> b.balance=20
>>> a.backup=b
>>> a.backup.balance
20

Object Identity:

使用isis not 来区分区分两个object是否指向同一个:

1
2
3
4
5
6
7
>>> a=Account('John')
>>> b=Account('Jack')
>>> a is b
False
>>> c=a
>>> a is c
True

Class Attributes:

1
2
3
4
5
6
7
class Account:

    interest=0.02 # A class attribute

    def __init__(self,account_holder):
        self.balance=0
        self.holder=account_holder

这里使用了class attribute来表示interest,而不是instance attribute。因此我们不必在每个创建的实例中存储该值,而只需要在class中存储一次。

创建两个实例后,任意一个都可以访问interest

1
2
3
4
5
6
>>> tom_account=Account('Tom')
>>> jim_account=Account('Jim')
>>> tom_account.interest
0.02
>>> jim_account.interest
0.02

在寻找attribute的时候,先在instance attribute中寻找,再在class attribute中寻找。

使用getattr查询attribute对应的值,使用 hasattr查询是否有对应的attribute:

1
2
3
4
>>> getattr(tom_account,'balance')
10
>>> hasattr(tom_account,'deposit')
True

Attribute Assignment:

在object中可以创建一个和class attribute同名的instance attribute(原本的class attribute不变),修改class attribute,如果object自身没有同名的instance attribute,则查询时都会改变;否则不变:

>>> jim_account=Account('Jim')
>>> tom_account=Account('Tom')
>>> jim_account.interest        # 一开始两个实例的interest都是class的
0.02
>>> tom_account.interest
0.02
>>> Account.interest=0.04       # 改变class的interest,由于两个实例都没有各自的interest,因此都会改变
>>> jim_account.interest
0.04
>>> tom_account.interest
0.04
>>> jim_account.interest=0.08   # 为jim_account创建一个新的instance attribute
>>> jim_account.interest        # 优先访问instance attribute
0.08 
>>> tom_account.interest        # 没有instance attribute,访问class attribute
0.04 
>>> Account.interest=0.05       # 改变class attribute
>>> jim_account.interest        # 访问instance attribute,不受影响
0.08
>>> tom_account.interest        # 访问class attribute,改变
0.05

Method calls:

method可以绑定到另一个名称上:

>>> a=Account('Alan')
>>> a.deposit(5)
5
>>> a.deposit(5)
10
>>> a.deposit
<bound method Account.deposit of <__main__.Account object at 0x7f8b1c3b3b50>>
>>> f=a.deposit
>>> f(10)
20
>>> f(10)
30

将method绑定到iterator上,当使用next进行迭代时,method也会对object产生效果:

1
2
3
4
5
6
7
>>> m=map(a.deposit,range(10,20))
>>> a.balance
30
>>> next(m)
40
>>> a.balance
40

Inheritance:

inheritance是一种将多个相似但在专业化程度上有所不同的class关联在一起的方法。

subclass可能具有与base class相同的所有attribute,也可能有一些额外的attribute。编写subclass时,我们只需考虑其与base class不同的部分。

class <name>(<base class>):
    <suite>

定义更低利率和收取手续费的subclassCheckingAccount

1
2
3
4
5
6
class CheckingAccount(Account):
    """A bank account that charges for withdrawals."""
    withdraw_fee=1
    interest=0.01
    def withdraw(self, amount):
        return Account.withdraw(self,amount+self.withdraw_fee) # 注意这里的用法

Looking up Attribute Names on Classes:

base class的attribute不会被复制到subclass中,相反,这是按名称查找attribute的过程的一部分。

顺序:subclass→base class→base class of base class→...

Object-Oriented Design:

  • Inheritance: 表示的是一个is-a关系
  • Composition: 表示的是一个has-a关系
class Bank:
    """A bank has accounts.

    >>> bank=Bank()
    >>> john=bank.open_account('John',10)
    >>> jack=bank.open_account('Jack',5,CheckingAccount)
    >>> john.interst
    0.02
    >>> jack.interst
    0.01
    >>> bank.pay_interest()
    >>> john.balance
    10.2
    >>> bank.too_big_to_fail()
    True
    """
    def __init__(self):
        self.accounts=[] # 该银行持有的账户列表

    def open_account(self, holder, amount, kind=Account):
        account=kind(holder)
        account.deposit(amount)
        self.accounts.append(account)
        return account

    def pay_interest(self):
        for a in self.accounts:
            a.deposit(a.balance*a.interest)

    def too_big_to_fail(self):
        return len(self.accounts)>1

Multiple Inheritance:

multiple inheritance是指一个subclass有多个base class。

1
2
3
4
class SavingAccount(Account):
    deposit_fee=2
    def deposit(self,amount):
        return Account.deposit(self,amount-self.deposit_fee)

假设有一种新的账户:

  • 1%的利率
  • $1的取款费
  • $2的存款费
  • 开户赠送$1
1
2
3
4
class AsSeenOnTVAccount(CheckingAccount,SavingAccount):
    def __init__(self,account_holder):
        self.holder=account_holder
        self.balance=1

graph BT;
    AsSeenOnTVAccount-->CheckingAccount;
    AsSeenOnTVAccount-->SavingAccount;
    CheckingAccount-->Account;
    SavingAccount-->Account


3.3 Representations

repr&str:

大多数object有两种字符串的表达方式:

repr str
对Python interpreter可读 对人类可读
交互式对话中回车打印出来的结果(字符串形式) 使用print打印出来的结果()

大多数情况下,strrepr是一样的:

1
2
3
4
5
6
7
8
>>> 12e12
12000000000000.0
>>> repr(12e12)
'12000000000000.0'
>>> print(12e12)
12000000000000.0
>>> str(12e12)
'12000000000000.0'

不一样的情况:

1
2
3
4
5
6
7
8
9
>>> from fractions import Fraction
>>> Fraction(1,2)  
Fraction(1, 2)
>>> repr(Fraction(1,2))
'Fraction(1, 2)'
>>> print(Fraction(1,2))
1/2
>>> str(Fraction(1,2))
'1/2'

使用eval函数执行一个字符串表达式,并返回表达式的值:

1
2
3
4
5
>>> half=Fraction(1,2)
>>> eval(repr(half))
Fraction(1, 2)
>>> eval(str(half))
0.5

部分object并没有简单对应的表达式,例如函数、类等,尖括号< >表示这不是一个Python表达式:

>>> repr(min)
'<built-in function min>'

Example

>>> s="Hello,World"

>>> s
'Hello,World'
>>> print(s)
Hello,World
>>> eval(s)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<string>", line 1, in <module>
NameError: name 'Hello' is not defined

>>> repr(s)
"'Hello,World'"
>>> print(repr(s))
'Hello,World'
>>> eval(repr(s))
'Hello,World'

>>> str(s)
'Hello,World'
>>> print(str(s))
Hello,World
>>> eval(str(s))
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<string>", line 1, in <module>
NameError: name 'Hello' is not defined

F-Strings and String Interpolation:

字符串插值使用F-string,评估字符串字面量,将其中的表达式({ }中的)替换为其值。

1
2
3
4
5
6
7
8
9
>>> from math import pi

# 不使用F-Strings
>>> 'pi starts with '+str(pi)+'...'
'pi starts with 3.141592653589793...'

# 使用F-Strings
>>> f'pi starts with {pi}...'
'pi starts with 3.141592653589793...'

评估子表达式可能有副作用:

1
2
3
>>> s=[9,8,7]
>>> f'because {s.pop()} {s.pop()} {s}.'
'because 7 8 [9].'

Polymorphic Functions:

多态函数是一种能处理不同数据类型的函数,strrepr就是典型的多态函数。

Note

repr实际上对参数调用了method__repr__

>>> half.__repr__()
'Fraction(1, 2)'

str实际上对参数调用了method__str__

>>> half.__str__()
'1/2'

Special Method Names:

当一个method名字两边有下划线时,它是一个特殊的method,例如__init____repr____str____add____radd____bool____float__

# 使用内置语法和内置函数
>>> zero,one,two=0,1,2
>>> one+two
3
>>> bool(zero),bool(one)
(False, True)

# 使用特殊的methods
>>> zero,one,two=0,1,2
>>> one.__add__(two)
3
>>> zero.__bool__(),one.__bool__()
(False, True)

Note

__add____radd__的区别在于相加的两个数顺序相反。

Example

class Ratio:
    def __init__(self,n,d):
        self.numer=n
        self.denom=d

    def __repr__(self):
        return 'Ratio({0},{1})'.format(self.numer,self.denom)

    def __str__(self):
        return '{0}/{1}'.format(self.numer,self.denom)

    def __add__(self,other):
        if isinstance(other,int): # 右侧为整数的情况
            n=self.numer+self.denom*other
            d=self.denom
        elif isinstance(other,Ratio):
            n=self.numer*other.denom+self.denom*other.numer
            d=self.denom*other.denom
        elif isinstance(other,float): # 左侧或右侧为浮点数的情况
            return float(self)+other
        g=gcd(n,d)
        return Ratio(n//g,d//g)

    __radd__=__add__ # 左侧为整数的情况,转换为右侧为整数的情况

    def __float__(self):
        return self.numer/self.denom

def gcd(n,d):
    while n!=d:
        n,d=min(n,d),abs(n-d)
    return n

3.4 Linked Lists

Linked Lists:

graph LR
1-->2
2-->3

我们尝试在Python中进行表示:

Link(3,Link(4,Link(5,Link.empty)))

1
2
3
4
5
6
7
8
class Link:

    empty=()

    def __init__(self,first,rest=empty):
        assert rest is Link.empty or isinstance(rest,Link)
        self.first=first
        self.rest=rest

Range, Map, and Filter for Linked Lists:

以下是一个常见的操作,通过filter获得range(1,6)中的奇数,再用square对其依次平方得到iterator,最后用list获得结果的列表:

square,odd=lambda x:x*x,lambda x:x%2==1
list(map(square,filter(odd,range(1,6)))) # [1,9,25]

现在,我们想对链表也进行类似的操作:

map_link(square,filter_link(odd,range_link(1,6))) # Link(1,Link(9,Link(25)))

因此可以给出以下函数定义:

def range_link(start,end):
    """Return a Link containing consecutive intergers from start to end.

    >>> range_link(3,6)
    Link(3,Link(4,Link(5)))
    """
    if start>end:
        return Link.empty
    else:
        return Link(start,range_link(start+1,end))

def map_link(f,s):
    """Return a Link that contains f(x) for each x in Link s.

    >>> map_link(square,range_link(3,6))
    Link(9,Link(16,Link(25)))
    """
    if s is Link.empty:
        return s
    else:
        return Link(f(s.first),map_link(f,s.rest))

def filter_link(f,s):
    """Return a Link that contains only the elements x of Link s for which f(x) is a true value.

    >>> filter_link(odd,range_link(3,6))
    Link(3,Link(5))
    """
    if s is Link.empty:
        return s
    filtered_rest=filter_link(f,s.rest)
    if f(s.first):
        return Link(s.first,filtered_rest)
    else:
        return filtered_rest

Example

向有序链表中插入元素。

def add(s,v):
    """Add v to s, returning modified s.

    >>> s=Link(1,Link(3,Link(5)))
    >>> add(s,0)
    Link(0,Link(1,Link(3,Link(5)))
    >>> add(s,3)
    Link(0,Link(1,Link(3,Link(5))))
    >>> add(s,4)
    Link(0,Link(1,Link(3,Link(4,Link(5)))))
    >>> add(s,6)
    Link(0,Link(1,Link(3,Link(4,Link(5,Link(6))))))
    """
    assert s is not List.empty
    if s.first>v:
        s.first,s.rest=v,Link(s.first,s.rest)
    elif s.first<v and empty(s.rest):
        s.rest=Link(v)
    elif s.first<v:
        add(s.rest,v)
    return s

评论