python入门(十) -- 面向对象
python入门(十) -- 面向对象
兴趣使然的程序员 发表于7个月前
python入门(十) -- 面向对象
  • 发表于 7个月前
  • 阅读 35
  • 收藏 1
  • 点赞 0
  • 评论 0

腾讯云 技术升级10大核心产品年终让利>>>   

和Java、C++、C#一样,python也是一门面向对象的语言,一样有以下相似的概念:

  • 类:用来描述具有相同属性和方法的对象的集合
  • 类变量:行为类似于Java中的static变量,在类中直接声明
  • 实例变量:相当于Java中的非static的成员变量,在构造方法中声明
  • 继承:和Java中相同,一个派生类可以继承基类的字段和方法,是“is-a”关系
  • 多重继承:和Java中不同,python允许多重继承
  • 方法重写:子类可以继承自父类的方法
  • 实例化:创建类的实例
  • 方法:类中可以定义函数
  • 对象:通过类定义的数据结构实例。对象包括两个数据成员(类变量和实例变量)和方法

1、类的定义:

# 定义类
class User:
    # 声明并给类变量赋值
    age = 15

    # 构造方法,可以带参数,python中实例变量在构造方法中声明
    def __init__(self, username, password):
        # 实例变量
        self.userName = username
        self.password = password

    # 无法在构造方法以外的方法中声明实例变量
    def f(self):
        # 在这里写self.gender = 'male'是不对的
        print(self.age)
        return 'hello world'
# 创建类的实例,如果想要后面的参数变为可选,可参考一般方法,设定默认值即可
x = User('cai', '258456')
# 调用实例变量
print(x.age)
print(x.userName)
print(x.password)
# 调用方法
x.f()

需要注意的是:

  • 类变量在类中声明,实例变量在__init__(self)构造方法中声明
  • 类的方法和一般的函数的差别在于,类的方法的第一个参数必须是self,self和Java中的this类似,指代当前对象实例(注意不是当前类)
  • __init__构造方法可以带其他参数,如果想要实现“参数可选”的话,参考之前说明的普通方法,给__init__构造方法的参数设定默认值即可:
# 定义类
class User:
    # 构造方法
    def __init__(self, username='cai', password='258456'):
        self.userName = username
        self.password = password

# 创建类的实例
x = User()
  • 类变量和实例变量的差别在于,类变量如果不改变引用,每个实例操作同一个类变量,如果改变引用,每个实例操作各自的副本。而实例变量每个实例并不相关。这就导致了行为表现的不同:

# 定义类
class User:
    # 类变量,每个对象都操作这个变量
    list = []
    age = 15

    # 构造方法
    def __init__(self, username='cai', password='258456'):
        self.userName = username
        self.password = password
        self.otherList = []

# 创建类的实例
x = User()
y = User()
x.list.append(15)
x.otherList.append(16)
x.age = 16
# 由于是同一个对象,所以x和y的list指向相同的地址,修改一个全都改变
print(y.list)
# 由于每次都是新的对象,所有修改x不会影响y
print(y.otherList)
# 并且x.age = 16声明了新的对象,不再关联原来的类变量,所以不影响y
print(y.age)


和Java中的“静态变量”的差别在于,Java中对静态变量进行操作时,操作的是静态变量本身,而不是副本。作为一门动态语言,python中并没有真正的“静态”(类变量只能共享值,不能共享引用,一旦实例更改了引用,就和类变量脱离了关系),只有类似于静态的行为。在python中也可以直接使用类名.变量来使用变量,而这样使用导致的效果也和Java中差别巨大:

# 创建类的实例
x = User()
y = User()
x.age = 16
# 直接使用类.类变量
User.age = 5
# 返回16 5 5
print(x.age, y.age, User.age)
x.list.append(99)
User.list = [88]
# 受影响,返回[88] [88] [88]
print(x.list, y.list, User.list)
# x改变了引用
x.list = [11]
User.list = []
# 不受影响,返回[11] [] []
print(x.list, y.list, User.list)
# 直接使用类.实例变量,虽然没有报错但是不会有任何效果
User.userName = 5

Java中静态变量的操作:

public class Test {
    private static int num;
    public static void main(String[] args) {
        //直接用类.静态变量,而无需创建实例
        System.out.println(Test.num);
        Test test1 = new Test();
        Test test2 = new Test();
        //实例.静态变量的效果和类.静态变量没有任何差别
        //num“真正的只存在一个”,任意的修改都会影响所有对象
        test1.num = 10;
        System.out.println(test2.num);
    }
}

所以结论是:python中的类变量不是真正的静态变量,类变量只能共享值,不能共享引用,一旦实例更改了引用,就和类变量脱离了关系。

注意下面两种写法的差异:

class ClassA:
    count = 0
    def __init__(self):
        # 使用对象引用
        self.count+=1

x = ClassA()
y = ClassA()
x.count+=1
# 返回2
print(x.count)
# 返回1
print(y.count)
class ClassA:
    count = 0
    def __init__(self):
        # 使用类引用
        ClassA.count+=1

x = ClassA()
y = ClassA()
x.count+=1
# 返回3
print(x.count)
# 返回2
print(y.count)

所以,如果想要实现和Java中类似的静态变量的效果,需要注意:

  1. 使用时,必须只用类名.变量的方式调用
  2. 一旦更改了引用,就会导致该对象的类变量的表现变为和普通变量相同。

2、继承与多继承

python中的实现继承需要注意:

  • 格式为:
    class DrivedClass(BaseClass1, BaseClass2, BaseClass3):
  • 基类和派生类必须在同一个作用域内,比如在同一个包下的不同子包中
  • 和Java中不同,python中基类的构造方法不会被自动调用,需要在派生类中显式调用。调用基类的方法时,使用基类名.方法名(self,参数列表)的形式调用

python中有几个用于继承的函数:

  • isinstance()用于检查实例类型,或者类继承
  • issubclass()只用于检查类继承
  • type()只用于检查实例类型

例如:

class ClassA:
    pass


# ClassB是ClassA的子类
class ClassB(ClassA):
    pass

# True,注意第一个参数是对象实例,不是类。第二个参数是类
print(isinstance(ClassB(), ClassA))
# True,注意两个参数都是类
print(issubclass(ClassB, ClassA))
# False,参数是类
print(type(ClassB) == ClassA)

3、私有属性、私有方法的定义

python中没有访问修饰符,如果想要实现“私有变量”、“私有方法”的话,需要在变量名、方法名加上两个下划线__作为标识,例如:

class Mapping:
    def __init__(self, iterable):
        self.items_list = []
        # 在初始化函数中使用update函数的副本进行初始化
        self.__update(iterable)

    def update(self,iterable):
        for item in iterable:
            self.items_list.append(item)

    __update = update


class MappingSubClass(Mapping):
    # 由于使用了副本,重写update方法并不会影响到基类的初始化
    def update(self, keys, values):
        for item in zip(keys, values):
            self.items_list.append(item)

4、global和nonlocal

python引用变量的顺序: 当前作用域局部变量->外层作用域变量->当前模块中的全局变量->python内置变量。

global 语句用以指明某个特定的变量为全局作用域,并重新绑定它。nonlocal 语句用以指明某个特定的变量为封闭作用域,并重新绑定它。

简单来说,就是允许在方法内操作方法外部的变量,而不经过参数传递(Java中方法不能调用除了静态成员以外的任何方法外的变量,python中有所不同,没有静态的概念,但是可以通过global实现类似的效果)。

def scope_test():
    def do_local():
        # 不使用nonlocal时,对spam的改变不会影响外部的同名变量spam
        spam = "local spam"

    def do_nonlocal():
        # 使用nonlocal修饰,使在do_nonlocal()方法的范围内,可以操作scope_test()内的spam
        nonlocal spam
        spam = "nonlocal spam"

    def do_global():
        # 使用global修饰,使在当前范围内,可以操作全局的spam
        global spam
        spam = "global spam"
    # scope_test范围的spam
    spam = "test spam"
    do_local()
    # 这个和下面三个print,使用的都是scope_test范围的spam
    # 不变
    print("After local assignment:", spam)
    do_nonlocal()
    # 改变为nonlocal spam
    print("After nonlocal assignment:", spam)
    do_global()
    # 依然是nonlocal spam,因为do_global改变的是更外部的spam
    print("After global assignment:", spam)

scope_test()
# 改变为global spam
print("In global scope:", spam)

简单地说,即nonlocal使方法可以操作其上一级作用域的同名对象,global使方法可以操作最外层作用域里的同名对象。

5、自定义迭代器

可以通过重写__iter__(self)方法和__next__(self)方法自定义迭代器。__iter__(self)方法返回一个带有__next()__对象,如果已经定义了__next()__,__iter__()方法返回self即可。__next()__方法定义迭代的规则。例如:

class Reverse:
    
    def __init__(self, data):
        self.data = data
        self.index = len(data)
        
    def __iter__(self):
        return self
    
    def __next__(self):
        if self.index == 0:
            raise StopIteration
        self.index -= 1
        return self.data[self.index]

6、自定义生成器

Generator用于创建迭代器。需要返回数据的时候使用yield语句。例如:

def reverse(data):
    for index in range(len(data)-1, -1, -1):
        yield data[index]

for char in reverse('golf'):
    print(char)

 

7、和其他语言的不同

在 Python 里请不要使用属性(attributes)读取方法(getters 和 setters)。如果你之前学过其它语言(比如 Java),你可能会想要在你的类里面定义属性读取方法。请不要这样做,直接使用属性就可以了,就像下面这样:

>>> class Student(object):
...     def __init__(self, name):
...         self.name = name
...
>>> std = Student("Kushal Das")
>>> print(std.name)
Kushal Das
>>> std.name = "Python"
>>> print(std.name)
Python

7.1、Properties 装饰器

你可能想要更精确的调整控制属性访问权限,你可以使用 @property 装饰器,@property 装饰器就是负责把一个方法变成属性调用的。

下面有个银行账号的例子,我们要确保没人能设置金额为负,并且有个只读属性 cny 返回换算人名币后的金额。

#!/usr/bin/env python3

class Account(object):
    """账号类,
    amount 是美元金额.
    """
    def __init__(self, rate):
        self.__amt = 0
        self.rate = rate

    @property
    def amount(self):
        """账号余额(美元)"""
        return self.__amt

    @property
    def cny(self):
        """账号余额(人名币)"""
        return self.__amt * self.rate

    @amount.setter
    def amount(self, value):
        if value < 0:
            print("Sorry, no negative amount in the account.")
            return
        self.__amt = value

if __name__ == '__main__':
    acc = Account(rate=6.6) # 基于课程编写时的汇率
    acc.amount = 20
    print("Dollar amount:", acc.amount)
    print("In CNY:", acc.cny)
    acc.amount = -100
    print("Dollar amount:", acc.amount)

运行程序:

此处输入图片的描述

 

标签: Python
共有 人打赏支持
粉丝 18
博文 107
码字总数 79840
×
兴趣使然的程序员
如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
* 金额(元)
¥1 ¥5 ¥10 ¥20 其他金额
打赏人
留言
* 支付类型
微信扫码支付
打赏金额:
已支付成功
打赏金额: