Python学习笔记(五)——OOP

模块

使用模块import 模块名。有的只导入了某个模块的一个类或者函数,使用from 模块名 import 函数或类名实现。为了避免模块名冲突,Python引入了按目录来组织模块的方法,称为包(Package)。则在a目录下的b.py模块名就是a.b。a目录下必须有一个文件__ini__.py,哪怕是文件内容是空。否则a就成了普通目录。

标准模块文件模板:

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

‘a test module‘ #任何模块的第一个字符串都被视为模块的文档注释

__author__ = ‘Your Name‘

...
def f():
...

if __name__ == ‘__main__‘:  #从其他地方导入模块,if不会通过。因此可以用来在命令行下进行测试
    f()
  • 模块中的非公开函数前缀_,如def _private_1():
  • 可以被直接引用的特殊变量一般用__argname__这种前后各两个下划线的形式来表示,自己定义的公开函数或变量一般就用普通的形式def f,不用这种下划线形式。
  • 需要注意的是,Python并没有一种方法可以完全限制访问private函数或变量。因此这个得靠自觉了

第三方模块可以使用pip安装pip install numpy,它会从python的官方网站自动下载和安装。也可以自己下载下来之后安装。

面向对象编程

类语法

class Student(object):
    pass

object是默认父类名称。实例的创建使用tom = Student()。作为动态语言,Python支持自由给tom绑定类外的新属性tom.name = ‘Tom‘。如果有需要在创建实例时绑定的属性,一般写在类中的__ini__函数中:

def __ini__(self, arg1, arg2):
    self.__arg1 = ...
    self.arg2 = ...

__ini__的第一个参数永远是self,会自动传入。有了这样的__ini__,创建实例时就需要传入arg1arg2参数。__arg1的两个前下划线表明这是一个私有属性,不能被外部访问。

鸭子类型:只要看起来像鸭子,跑起来像鸭子,那就可以看做鸭子

Python的动态性也体现在继承的上。假设有个函数中调用了某类的方法,只要传入的对象也有这个方法,都可以执行,而和这个对象是不是该类或该类子类的实例无关。

获取对象信息

  • 获取对象的信息可以使用type(),同时如果import types,还可以使用type(对象) == types.type模块中的常量函数类型来判断一个对象是否是某种具体函数类型。
  • isinstance(对象,类)的判断方法则如果该对象是参数中类的父类也返回True。能用type判断的基本类型都可以使用isinstance,第二个参数也可以是个list,只要满足是元素之一就返回True。
  • dir()可以将一个对象的所有属性和方法都返回
  • 当不知道对象细节时,可以使用hasattr(obj, ‘arg‘)得到obj是否有arg属性或方法;getattr(obj, ‘arg‘)返回属性或方法;setattr(obj, ‘arg‘, value)设置属性或方法(没有则直接增加)

面向对象高级编程

使用__slots__

因为Python是动态语言,因此可以随意为类、实例绑定属性或者方法。绑定方法要用到from types import MethodTypes.f = MethodType(f, s)将方法f绑定到类或实例s上。同时要注意f的第一个参数要是self才能绑定。

如果想要限制实例的属性,就需要在定义类的时候定义一个特殊变量__slots__ = (‘name1‘,‘name2‘)来限定给实例绑定的属性名只能是name1或者name2。

辨析:

from types import MethodType

class Student(object):
    __slots__ = (‘name‘, ‘sex‘, ‘set_name‘, ‘set_grade‘)
#允许绑定的在下文中称为内方法和内属性。反之称为外方法和外属性#

#内方法中绑定内属性
def set_name(self, name):
    self.name = name

#内方法中绑定外属性#
def set_grade(self, grade):
    self.grade = grade

#外方法中绑定外属性#
def set_score(self, score):
    self.score = score

#外方法中绑定内属性#
def set_sex(self, sex):
    self.sex = sex

tom = Student()
jane = Student()

(1)给实例绑定属性:

tom.name = ‘Tom‘    #正确,name是内属性
jane.grade = 98    #错误,grade是外属性,不能绑定
print(jane.name)    #错误,没有定义实例jane的name

#第三句可见实例绑定的属性只算各个实例自己的

(2)给类绑定属性:

Student.name = ‘name‘    #内属性可以绑定
Student.grade = 0    #外属性也可以绑定,可见__slots__只作用于实例

tom.name = ‘Tom‘    #错误,可见通过这种方法绑定的属性到实例中为只读
print(jane.name)    #输出为name

(3)给实例绑定方法:

tom.set_name = MethodType(set_name, tom)    #内方法,其中绑定内属性
tom.set_grade = MethodType(set_name, tom)    #内方法,其中绑定外属性,调用方法时会报错
tom.set_score = MethodType(set_score, tom)    #外方法,不可绑定
tom.set_sex = MethodType(set_name, tom)    #外方法,不可绑定

(4)给类绑定方法

#绑定内方法
Student.set_name = MethodType(set_name, Student)

tom.name = ‘Tom‘
print(jane.name)    #报错,可见在调用方法绑定内属性之前,“实例.属性 =”其实回到了第一种辨析的情况

tom.set_name(‘Tom‘)
jane.set_name(‘Jane‘)    #调用内方法,才将内属性绑定到了类
tom.name = ‘Tom‘    #报错,可见上一句绑定的属性和(3)中相同,只读。只能通过类方法修改
print(jane.name)
print(tom.name)    #输出也是Jane,可见通过类方法修改的属性是类属性,各实例的对应属性都是最后一次调用类方法之后的属性值

#绑定内方法,其中绑定外属性
Student.set_grade = MethodType(set_grade, Student)
tom.set_grade(4)    #调用方法绑定了外属性
tom.grade = 4    #类属性只读,不可直接修改
print(tom.grade)    #实例拥有了外属性,不过是类属性,只读,只能通过类方法修改

剩下的两种情况不再赘述,因为__slots__不限制类,因此任何方法和属性都可以被绑定。只不过通过调用方法绑定的属性是只读的,只能通过调用方法修改,不能在实例中直接使用.赋值修改通过以上辨析,总结如下:

__slots__只限制实例的方法或属性绑定,不限制类。对类绑定的方法和属性都可以在实例中调用,但类属性在实例中是只读的,它的值为最后一次调用方法或通过类.属性=赋值的结果,不能在实例中用实例.属性=修改。

使用@property

当需要修改属性时,为了能够检查参数,一般调用方法来设置属性值。但这样的话就略显麻烦。这时候@property装饰器就起作用了。

@property
def name(self):
    return self._name

@name.setter #如果不用下面的部分,则name就变成了只读属性
def name(self,value):
    ...类型检查等
    self._name = value

注意属性_name一定要有下划线来区分属性和方法,否则名字相同变成了方法,上面的代码就成了递归,会报现递归次数溢出错误

多重继承

如果继承的多个父类都有相同的方法,则以第一个为准,后面几个父类的该方法不会起作用。

定制类

诸如__slots__这种两端都有两个下划线的变量或者函数是有特殊用途的,因此可以通过定义这类对象来对类进行定制

__str__(self):当执行print(类)时,程序会打印出属性和内存地址,如果想要打印出特定的内容,则可以定义一个__str__方法,输出特定的内容。如果不用print而是直接调用类名输出信息,则需要定义__repr__()方法,简单的一种定义方法就是__repr__ = __str__快速定义。

__iter__(self):如果想要迭代一个类则需要定义__iter__方法返回self,然后再定义一个__next__(self)方法执行迭代中要做的事情

__getitem__(self, n):把类变成像list那样可以按下标取元素操作。在__getitem__下返回元素值。如果要实现切片等功能,还要再对这个函数进行进一步的细化,比如判断传入的参数是否是slice等一系列后续操作。同样地,将类变为像dict那样,使用__setitem__。使用__delitem__删除元素。以上的这些方法将类变成list或dict的“鸭子”,使类可以像list或dict那样操作。

__getattr__(self, attr):返回动态属性或方法。这个函数下边对attr进行判断,从而绑定规定的属性或者方法。绑定属性返回属性值,绑定方法则返回lambda函数。如果没有任何限制,显然任何属性或方法都可以绑定,这时候如果返回自身,则就实现了链式调用。利用这一点构造一个针对不同API的SDK。以下的代码就实现了一个GitHub的API的URL输出:

class Chain(object):

    def __init__(self, path =‘‘):
        self._path = path

    def __getattr__(self, path):
        return Chain(‘%s/%s‘ %(self._path, path))

    def __call__(self,name):
        return Chain(‘%s/:%s‘ %(self._path, name))

    def __str__(self):
        return ‘GET ‘ + self._path        

print(Chain().users(‘hermanncain‘).repos)

#输出为GET /users/:hermanncain/repos

__call__(self):调用实例自身。即调用s()时执行__call__(self)。也可以使用__call__(self, *args, **kw)来传递参数,则调用s(*args, *kw)时执行其中的内容。这样模糊了类和实例的界限。

枚举类

from enum import Enum

Month = Enum(‘Month‘, (‘Jan‘, ‘Feb‘, ‘Mar‘, ‘Apr‘, ‘May‘, ‘Jun‘, ‘Jul‘, ‘Aug‘, ‘Sep‘, 

‘Oct‘, ‘Nov‘, ‘Dec‘))

for name, member in Month.__member__.items():
    print(name, ‘=>‘, member, ‘,‘, member.value)

value为枚举成员对应的int值,默认从1开始

另一种从Enum派生的自定义枚举类,语法就和C++中的枚举类型有些像了:

from enum import Enum, unique

@unique
class Weekday(Enum):
    Sun = 0
    Mon = 1
    ...

@unique装饰器用于检查重复值。

对枚举类的成员有以下几种引用方法:

print(Weekday.Sun)
print(Weekday[‘Sun‘])
print(Weekday(1))
#以上的输出都是Weekday.Sun

print(Weekday.Sun.value)
#输出0

元类*

使用type()创建类

动态语言的函数和类的定义是在运行时动态创建的。写一个a.py的模块,在模块里定义class A,然后在主程序中from a import A,载入模块时执行模块所有语句,结果就是动态创建出一个A的class。type(A)返回值是typetype(A的实例)的返回值则是class ‘__main__‘.A。但是type()也可以用于动态创建类。实际上Python解释器遇到class定义时,也是使用type()动态地将该类创建出来的:

def f(self, ...):
    ...

A = type(‘A‘, (object,), dict(af = f)

这样就动态创建了一个A类,父类是object(注意多重继承父类放在tuple里,如果只有一个父类则tuple只有一个元素,逗号不可少),并且有一个名为af的方法。

metaclass元类*

类相当于元类的实例。metaclass是Python面向对象里最难理解,也是最难使用的魔术代码,一般用不到

*这部分内容先略过,等到用到的时候根据实例来整理

时间: 2024-10-09 10:03:43

Python学习笔记(五)——OOP的相关文章

python学习笔记(五):装饰器、生成器、内置函数、json

这周学习了装饰器和生成器,写下博客,记录一下装饰器和生成器相关的内容. 一.装饰器 装饰器,这个器就是函数的意思,连起来,就是装饰函数,装饰器本身也是一个函数,它的作用是用来给其他函数添加新功能,比如说,我以前写了很多代码,系统已经上线了,但是性能比较不好,现在想把程序里面每个函数都加一个功能,用来统计每个函数的运行时间是多少,找出来运行比较慢的函数,来优化代码,就需要添加一个新的功能,来统计程序的运行时间,那这样的话,就得修改每个函数了,需要改代码,但是代码特别多,改完了公司倒闭了,这时候装饰

Python学习笔记五(模块与包)

一.模块 1.模块介绍 一个模块就是包含了一组功能的python文件,可以通过import导入模块使用. python中模块分为四个类别: a) 使用python编写的.py文件 b) 已被编译为共享库或DLL的C或C++扩展 c) 把一系列模块组织到一起的文件夹,文件夹内有__init__.py文件,称该文件夹为包 d) 使用C编写并链接到python解释器的内置模块 定义my_module.py模块,模块名为my_module print("from my_module.py")

Python学习笔记五_数据类型(字符串)

已经学习过的数据类型包括:int.float.list.tuple.dict.这篇来单独记录下字符串.单独将字符串这种数据类型写出来,是因为python里有很多非常有用的字符串内置方法. 一.字符串定义 字符串可以存任意类型的字符串,比如名字,一句话等等. 1 name = 'Rebecca' 2 msg = 'Fighting' 二.字符串内置方法 1.常用方法 输出为字符串的方法 a = 'my name is Rebecca' print(a.capitalize()) #将字符串的第一个

Python学习笔记五:数字和字符串

数据类型是不允许改变的,这就意味着如果改变数字数据类型得值,将重新分配内存空间. Python数字类型转换 int(x [,base ]) 将x转换为一个整数 long(x [,base ]) 将x转换为一个长整数 float(x ) 将x转换到一个浮点数 complex(real [,imag ]) 创建一个复数 str(x ) 将对象 x 转换为字符串 repr(x ) 将对象 x 转换为表达式字符串 eval(str ) 用来计算在字符串中的有效Python表达式,并返回一个对象 tupl

Python学习笔记五--条件和循环

5.1 if语句 没什么好说,if语句语法如下: if expression: expr_true_suit 5.1.1多重条件表达式 单个if语句可以通过布尔操作符and,or,not实现多重条件判断或否定判断. if not warn and (system_load>=10): print 'WARNING:LOSING RESOURCE' warn+=1 5.2 else 语句 如果if条件为假程序将执行else后的语句. if expression: expr_true_suit el

python学习笔记(五) - 模块

一. 使用模块 引入sys模块,使用import #!/usr/bin/env python # -*- coding: utf-8 -*- ' a test module ' __author__ = 'Michael Liao' import sys def test(): args = sys.argv if len(args)==1: print 'Hello, world!' elif len(args)==2: print 'Hello, %s!' % args[1] else: p

python学习笔记五:字符串方法

常用字符串常量: string.digits:包含数字0~9的字符串 string.letters:包含所有字母(大写或小写字符串,在python3.0中,使用string.ascii-letters代替) string.lowercase:包含所有小写字母的字符串 string.printable:包含所有可打印字符的字符串 string.punctuation:包含所有标点的字符串 string.uppercase:包含所有大写字母的字符串 1)find:在较长的字符串中查找子串,返回子串所

python学习笔记(五) 200行实现2048小游戏

用前文提到的基础知识,和网上流行的2048源码,用python实现该游戏. 先将用户操作和游戏逻辑绑定. WASD分别对应移动方向上.左.下.右 然后实现矩阵的转置和逆置,这样只要实现一个方向的移动,通过转置和逆置就可以得到其他方向 的移动. 基本的函数声明完成了,下面定义GameField类,主要实现游戏逻辑和状态转换. GameField类和其中包含的一些函数.先看构造函数. 构造棋盘并且刷新棋盘,作为初次游戏的布局. spawn函数为随机某个位置设置随机值. 移动函数,这个函数比较复杂,代

Python学习笔记五,函数及其参数

在Python中如何自定义函数:其格式为 def 函数名(函数参数): 内容 交互模式下编写函数完毕按两次回车返回>>> pass作为函数为空函数的占位符(她的意思是什么都不做),也就是说没想好写什么函数,先用pass占位,然后先让程序跑起来: 数据类型检查可以用内置函数isinstance(object,calssinfo)实现:如果参数object是classinfo的实例,或者object是classinfo类的子类的一个实例, 返回True.如果object不是一个给定类型的的对

python学习笔记五:模块和包

一.模块用import导入 cal.py: #!/usr/bin/python def add(x,y): return x+y if __name__ == '__main__': print add(1,2) 注:__name__为内置变量,如果直接在CLI中调用值为__mail__,否则为文件名. 在new.py中导入: import cal print cal.add(2,3); 二.包:按目录名组织的模块 1.建立一个名字为包名字的文件夹 2.在该文件夹下创建一个__init__.py