抽象

目录

1.函数

1.1 参数

1.1.1 关键字参数和默认值

1.1.2 收集参数

1.2 作用域

2. 类

2.1 类属性和方法

2.1.1 函数和方法

2.1.2 属性

2.1.3 类的命名空间

2.1.4 指定超类

2.1.5 抽象基类

3.总结


1.函数

函数执行特定的操作并返回一个值,我们可以调用它。一般而言,要判断某个对象是否可以调用,可使用内置函数callable。在交互模式下,可以使用特殊的内置函数help来查看函数的帮助信息。

def math_test():
    x = 1
    y = math.sqrt
    print(callable(x), callable(y))

运算结果为:

if __name__ == '__main__':
    math_test()

1.1 参数

在有些语言(C++、Python)中,经常需要给参数赋值并让这种修改影响函数外部的变量。在Python中,没法直接这样做,只能修改参数对象本身,而不能修改函数外部的变量。如果修改外部的变量,只能通过函数的返回值来设置。

1.1.1 关键字参数和默认值

Python中的函数参数设置有多种方式,看下面的函数:

def hello(greeting, name):
    print('{},{}!'.format(greeting, name))

调用该函数的时候使用hello('Hello','world'),参数传递有着严格的排列顺序,如果使用hello('world','Hello')调用,就会产生错误的输出结果。有时候,参数的排列顺序可能难以记住,尤其是参数很多时。为了简化调用工作,可指定参数的名称:

hello(greeting='Hello',name='world')

在这里,参数的顺序就无关紧要了:

hello(name='world',greeting='Hello')

像这样使用名称指定的参数称为关键字参数,主要优点是有助于澄清各个参数的作用。虽然这样做的输入量变多了,但每个参数的作用清晰明了,参数错了也没有关系。然而,关键字参数最大的优点在于,可以指定默认值:

def hello(greeting='Hello', name='world'):
    print('{},{}!'.format(greeting, name))

像这样给参数指定默认值后,调用函数时可不提供它。可以根据需要,一个参数值也不提供、提供部分参数值或提供全部参数值。

    hello()
    hello('Hi')
    hello('Hi', 'Martin')

1.1.2 收集参数

有时候,允许用户提供任意数量的参数,我们可以定义如下函数:

def print_params(*params):
    print(params)

参数前面加一个星号,就可以将任意的参数都放在一个元组中:

 print_params('Hello', ',', 'world', '!')

输出结果为一个元组:

 print_params('Hello', ',', 'world', '!')

有时候,我们可以使用这种方式去收集余下位置的参数,如果没有可供收集的参数,params将会是一个空的元组:

def print_params(name, *params):
    print(name)
    print(params)

参数调用如下:

print_params('Hello', ',', 'world', '!')
print_params('Hello')

输出的结果如下:

Hello
(',', 'world', '!')
Hello
()

但是需要注意的是星号不会收集关键字参数,例如执行下面的调用操作会报错:

print_params('Hello', sep=',', param='world', end='!') print_params('Hello', sep=',', param='world', end='!')

输出结果如下:

Traceback (most recent call last):
  File "D:/mycode/python-martin/com/basic/lec02/AbstractTest.py", line 20, in <module>
    print_params('Hello', sep=',', param='world', end='!')
TypeError: print_params() got an unexpected keyword argument 'sep'

要收集关键字参数,需要使用两个星号。这样得到的是一个字典而不是元组:

def print_params(name, **params):
    print(name)
    print(params)

输出结果如下:

Hello
{'sep': ',', 'param': 'world', 'end': '!'}

与赋值时一样,带星号的参数也可以放到非末尾的其他位置,但需要使用名称来指定后续参数:

def print_params(name, *params, end):
    print(name)
    print(params)
    print(end)

输出结果如下:

print_params('Hello', ',', 'world', end='!')
输出结果如下:
Hello
(',', 'world')
!

1.2 作用域

变量是指向值的名称,变量分为全局变量和局部变量。在函数内部给变量赋值时,该变量默认值为局部变量,除非我们使用global进行修饰,明确地告诉Python它是全局变量。如下的代码示例:

x = 1
def scope_test():
    global x
    x += 1

调用以及输出结果如下:

print(x)
scope_test()
print(x)
输出结果为:
1
2

2. 类

每个对象都属于特定的类,并被称为该类的实例。创建类的实例代码如下:

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

    def get_name(self):
        return self.name

    def greet(self):
        print('Hello,world!I am {}.'.format(self.name))

参数self指向对象本身。调用测试如下:

foo = Person()
bar = Person()

foo.set_name('Martin')
bar.set_name('Alice')

foo.greet()
bar.greet()

输出结果如下:

Hello,world!I am Martin.
Hello,world!I am Alice.

对foo调用set_name和greet时,foo都会作为第一个参数自动传递给它们。实际上,可以随便给这个参数命名,但是鉴于它总是指向对象本身,因此习惯上将其命名为self。self是必不可少的,如果没有它所有的方法都无法访问对象本身。

2.1 类属性和方法

2.1.1 函数和方法

方法和函数的区别主要表现在参数self上。方法将其第一个参数关联到它所属的实例上,因此调用的时候无需提供这个参数,但是在定义方法时候必须有这个参数名称,否则报错:

而函数在定义的时候无需定义该参数。

2.1.2 属性

类中的属性一般需要定义为私有属性,私有属性是不能从对象的外部进行访问,只能通过存取器方法来进行访问。Python没有为私有属性提供直接的支持,而是要求程序员知道在什么情况下从外部修改属性是安全的。要让方法或者属性成为私有的,只需要让其名称以两个下划线打头即可,看如下的代码示例:

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

    def get_name(self):
        return self.name

    def __input_password(self):
        print("I am inputting password")

    def modify_password(self):
        self.__input_password()

person = Person()
person.modify_password()
person.__input_password()

输出的结果如下:

I am inputting password
Traceback (most recent call last):
  File "D:/mycode/python-martin/com/basic/lec02/clazz/Person.py", line 17, in <module>
    person.__input_password()
AttributeError: 'Person' object has no attribute '__input_password'

虽然以两个下划线打头有点怪异,但这样方法类似于其他语言中的标准私有方法。虽然以两个下划线打头有点怪异,但这样的方法类似于其他语言中的标准私有方法。然而,真正的实现原理却并不标准:在类的定义中,对所有以两个下划线开头的名称都进行转换,即在开头加上一个下划线和类名。只要知道了真正的实现原理,我们就可以使用下面的方式来访问类的私有方法:

person._Person__input_password()

总之,我们虽然无法禁止别人访问对象的私有方法和属性,但这种名称修改方式却向外部发出了强烈的信号,该方法或属性是私有的。例如,我们使用from module import * 不会导入以一个下划线打头的名称。

2.1.3 类的命名空间

在class语句中定义的代码都是在一个特殊的命名空间(类的命名空间)内执行的,而类的所有成员都可以访问到这个命名空间。

class MemberCounter:
    members = 0

    def init(self):
        MemberCounter.members += 1


m1 = MemberCounter()
m1.init()
print(MemberCounter.members, m1.members)

m2 = MemberCounter()
m2.init()
print(MemberCounter.members, m1.members, m2.members)

输出结果为:
1 1
2 2 2

上述代码在类的作用域内定义了一个变量,所有的成员都可以访问它,这里使用它来计算类实例的数量。如果我们在一个实例中给属性members赋值,那又如何呢?

class MemberCounter:
    members = 0

    def init(self):
        MemberCounter.members += 1


m1 = MemberCounter()
m1.init()
print(MemberCounter.members, m1.members)

m2 = MemberCounter()
m2.init()
print(MemberCounter.members, m1.members, m2.members)

m1.members = 'Hello'
print(MemberCounter.members, m1.members, m2.members)
输出结果为:
1 1
2 2 2
2 Hello 2

新的值被写入m1的一个属性中,这个属性遮住了类级变量。有点类似于函数中局部变量和全局变量的之间的关系。

2.1.4 指定超类

要指定超类,可以在class语句中的类名后加上超类名,并将其用圆括号括起。

class Filter:
    def init(self):
        self.blocked = []

    def filter(self, sequence):
        return [x for x in sequence if x not in self.blocked]


class SPAMFilter(Filter):
    def init(self):
        self.blocked = ['SPAM']


filter = SPAMFilter()
filter.init()
print(filter.filter(['SPAM', 'VFSG', 'ASDF']))

输出结果为;
['VFSG', 'ASDF']
  • 要确定一个类是否是另外一个类的子类,可以使用内置方法issubclass
  • 如果想知道一个类的基类,可以访问其属性_bases_
  • 要确定对象是否是特定类的实例,可使用isinstance或type

实例代码如下:

class Filter:
    def init(self):
        self.blocked = []

    def filter(self, sequence):
        return [x for x in sequence if x not in self.blocked]


class SPAMFilter(Filter):
    def init(self):
        self.blocked = ['SPAM']


filter = SPAMFilter()
filter.init()

print(issubclass(SPAMFilter, Filter))
print(SPAMFilter.__bases__)
print(isinstance(filter, SPAMFilter), isinstance(filter, Filter))
print(filter.__class__)
print(type(filter))

输出结果为:

True
(<class '__main__.Filter'>,)
True True
<class '__main__.SPAMFilter'>
<class '__main__.SPAMFilter'>

一个类可以有多个超类,这被称为多重继承。如果有多个超类一不同的方式实现了同一个方法,必须在class语句中小心排列这些超类,因为位于前面的类的方法将覆盖位于后面的类的方法。

2.1.5 抽象基类

抽象类是应该被实例化的类,其职责是定义子类应实现的一组抽象方法。Python通过引入模块abc使用@abstractmethod注解,来为抽象基类提供支持。

from abc import ABC, abstractmethod


class Filter(ABC):
    def init(self):
        self.blocked = []

    @abstractmethod
    def filter(self, sequence):
        return [x for x in sequence if x not in self.blocked]

filter = Filter()

执行就会报错:

Traceback (most recent call last):
  File "D:/mycode/python-martin/com/basic/lec7/Filter.py", line 18, in <module>
    filter = Filter()
TypeError: Can't instantiate abstract class Filter with abstract methods filter

3.总结

  • 将相关的东西放在一起,如果一个函数操作一个全局变量,最好将它们作为一个类的属性和方法
  • 不要让对象之间过于亲密,方法应只关心其所属实例的属性,对于其他实例的状态,让它们自己去管理就好了。
  • 慎用继承,尤其是多重继承。继承有时很有用,但在有些情况下可能带来不必要的复杂性。要正确地使用多重继承很难,要排除其中的BUG更难
  • 保持简单。让方法短小紧凑,应该确保大多数方法都能在30秒内读完并理解,对于其余的方法,尽可能将其篇幅控制在一屏或者一页内。

 

 

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值