Python面向对象编程(Object Oriented Programming,OOP)之进阶

isinstance和issubclass

1. isinstance()

Python中内建函数。

语法:isinstance(object, classinfo)

如果参数object是classinfo的实例,或者object是classinfo类的子类的一个实例, 返回True。如果object不是一个给定类型的的对象, 则返回结果总是False。

class A(object):
    pass


class B(A):
    pass


obj1 = A()
obj2 = B()

a = isinstance(obj1, A)
b = isinstance(obj2, A)
print(a)
print(b)

结果:

True
True

Process finished with exit code 0

2. issubclass()

Python中内建函数。

语法:issubclass(sub, super)

检查sub类是否是super类的派生类(子类),是的话返回True,不是返回False。

class A(object):
    pass


class B(A):
    pass


obj1 = A()
obj2 = B()

print(issubclass(B, A))

结果:

True

Process finished with exit code 0

反射

概念

在做程序开发中,我们常常会遇到这样的需求:需要执行对象里的某个方法,或需要调用对象中的某个变量,但是由于种种原因我们无法确定这个方法或变量是否存在,这是我们需要用一个特殊的方法或机制要访问和操作这个未知的方法或变量,这中机制就称之为反射。

意义

反射的概念是由Smith在1982年首次提出的,主要是指程序可以访问、检测和修改它本身状态或行为的一种能力(自省)。这一概念的提出很快引发了计算机科学领域关于应用反射性的研究。它首先被程序语言的设计领域所采用,并在Lisp和面向对象方面取得了成绩。

python面向对象中的反射:通过字符串的形式操作对象相关的属性。python中的一切事物都是对象(都可以使用反射)。

接下记录下反射几个重要方法:

hasattr

判断对象中是否有这个方法或变量:

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

    def talk(self):
        print("%s正在说话" % self.name)


p = Person('张三')
p.talk()
print(hasattr(p, 'talk'))
print(hasattr(p, 'name'))
print(hasattr(p, 'python'))

结果:

张三正在说话
True
True
False

Process finished with exit code 0

getattr

用于返回对象的属性值:

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

    def talk(self):
        print("%s正在说话" % self.name)


p = Person('张三')
p.talk()
g1 = getattr(p, 'name')
print(g1)  # 打印括号值
g2 = getattr(p, 'talk')
g2()  # 调用括号的方法
g3 = getattr(p, 'python', '找不到值')
print(g3)  # 第三个参数为找不到返回值

结果:

张三正在说话
张三
张三正在说话
找不到值

Process finished with exit code 0

setattr

为对象添加变量或者方法:

def func_one(self):
    print("%s正在说话" % self.name)


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


p = Person('张三')
setattr(p, 'talk', func_one)  # 将func_one函数添加到对象p中并命名为talk
p.talk(p)  # 调用talk方法

setattr(p, 'age', 30)  # 添加一个age变量,赋值为30
print(p.age)  # 打印age:30

结果:

张三正在说话
30

Process finished with exit code 0

delattr

删除对象中的变量(不能删除方法,实际开发中慎用)

class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def talk(self):
        print("%s正在说话" % self.name)


p = Person('张三', 18)
delattr(p, 'age')
# print(p.age)  # 'Person' object has no attribute 'age'
# delattr(p, 'talk')  # 报错,不能删除方法

结果:


Process finished with exit code 0

Python面向对象中,类也可以是对象:

class Foo:
    STATIC_WORD = 'Python'

    def __init__(self):
        self.name = '张三'

    def func(self):
        return 'func'

    @classmethod
    def bar(cls):
        return 'bar'


print(getattr(Foo, 'STATIC_WORD'))
print(getattr(Foo, 'func'))
print(getattr(Foo, 'bar'))

结果:

Python
<function Foo.func at 0x0000000000AA57B8>
<bound method Foo.bar of <class '__main__.Foo'>>

Process finished with exit code 0

分析:getattr表示获得对象中方法或者变量的内存地址。当我们使用类代替实例化对象时,调用类中静态属性返回其值,动态属性返回其内存地址,动态属性中的类方法返回类方法位置。

__str__和__repr__

__repr__和__str__这两个方法都是用于显示的,__str__是面向用户的,而__repr__面向程序员。

下面演示(__repr__)在ipython中的过程:

In [2]: class TestOne:
   ...:     def __init__(self):
   ...:         self.name = 'Jay Chou'
   ...:         self.age = 35
   ...:     def __repr__(self):
   ...:         return '%s %s %s' % (self.__class__.__name__,self.name,self.age)
   ...:

In [3]: str(TestOne())
Out[3]: 'TestOne Jay Chou 35'

In [4]: repr(TestOne())
Out[4]: 'TestOne Jay Chou 35'

下面演示(__str__)在ipython中的过程:

In [6]: class TestTwo:
   ...:     def __init__(self):
   ...:         self.name = 'Jay Chou'
   ...:         self.age = 35
   ...:     def __str__(self):
   ...:         return '%s %s %s' % (self.__class__.__name__,self.name,self.age)
   ...:

In [7]: str(TestTwo())
Out[7]: 'TestTwo Jay Chou 35'

In [8]: repr(TestTwo())
Out[8]: '<__main__.TestTwo object at 0x000001D2D7288B00>'

可以看出没有__str__的情况下确实是在str()方法中会表现出使用了__repr__,反之则不会。所以__str__的实现其实是可选的,如果需要一个容易看懂的打印对象的功能可以实现__str__。

__getitem__、__setitem__和__delitem__

用于索引操作,如字典。分别表示获取、设置和删除操作。

class Demo:
    def __init__(self):
        self.value = {}
        self.name = 'Jay Chou'

    def __getitem__(self, item):
        print('__getitem__ 获取', item)
        return self.value[item]

    def __setitem__(self, key, value):
        print('__setitem__设置', key, value)
        self.value[key] = value

    def __delitem__(self, key):
        print('__delitem__删除', key)
        del self.value[key]

    def __len__(self):
        return len(self.value)


print(Demo.__doc__)

demo = Demo()
demo['Jay Chou'] = '周杰伦'
demo['JJ'] = '林俊杰'
demo['Andy'] = '刘德华'
demo['Jack Ma'] = '马云'
print(demo['Jack Ma'])
del demo['Jay Chou']
print(len(demo))

结果:

None
__setitem__设置 Jay Chou 周杰伦
__setitem__设置 JJ 林俊杰
__setitem__设置 Andy 刘德华
__setitem__设置 Jack Ma 马云
__getitem__ 获取 Jack Ma
马云
__delitem__删除 Jay Chou
3

Process finished with exit code 0

__new__

__new__方法接受的参数虽然也是和__init__一样,但__init__是在类实例创建之后调用,而 __new__方法正是创建这个类实例的方法。

class Demo:
    def __init__(self, name, age):
        print("__init__方法")
        self.name = name
        self.age = age

    def __new__(cls, *args, **kwargs):
        print("这是__new__方法")
        return super(Demo, cls).__new__(cls)

    def __str__(self):
        return 'Demo:%s-->%s' % (self.name, self.age)


if __name__ == '__main__':
    demo = Demo('张三', 18)
    print(demo)

结果:

这是__new__方法
__init__方法
Demo:张三-->18

Process finished with exit code 0

我们可以看见,__new__()在__init__()前面执行,为函数开辟一个空间。

__call__

Python中的函数是一级对象。这意味着Python中的函数的引用可以作为输入传递到其他的函数/方法中,并在其中被执行。 而Python中类的实例(对象)可以被当做函数对待。也就是说,我们可以将它们作为输入传递到其他的函数/方法中并调用他们,正如我们调用一个正常的函数那样。

鉴于其与__init__和__del__的相关性,我们将放在一起研究:

class Demo:
    lst = []

    def __init__(self, begin, end):
        print("__init__方法")
        for i in range(begin, end):
            self.lst.append(i)

    def __call__(self, index):  # 默认为 *args, **kwargs
        print("__call__方法")
        if index < len(self.lst):
            return self.lst[index]

    def __del__(self):
        print("__del__方法")  # 释放内存
        new_index = int(input('输入要删除的index:'))
        del self.lst[new_index]
        print(self.lst)
        print("已删除并释放内存")


demo = Demo(0, 10)
print(demo(2))
print(demo(5))
print(demo(8))

结果:

__init__方法
__call__方法
2
__call__方法
5
__call__方法
8
__del__方法
输入要删除的index:5
[0, 1, 2, 3, 4, 6, 7, 8, 9]
已删除并释放内存

Process finished with exit code 0

__len__

如果一个类表现得像一个list,要获取有多少个元素,就得用 len() 函数。 要让 len() 函数工作正常,类必须提供一个特殊方法__len__(),它返回元素的个数。

class Demo:
    def __init__(self, *args):
        self.names = args

    def __len__(self):
        return len(self.names)


demo = Demo(1, 2, 3, 4, 5, 6, 7, 8, 9, 0)
print(len(demo))

结果:

10

Process finished with exit code 0

你也许会觉得这样没有啥用,那么我讲演示一个算法题:

例题:斐波那契数列是由 0, 1, 1, 2, 3, 5, 8...构成。 请编写一个Fib类,Fib(10)表示数列的前10个元素,print(Fib(10) )可以打印出数列的前 10 个元素,len(Fib(10))可以正确返回数列的个数10。

参考代码:

class Fib:
    def __init__(self, num):
        a, b, L = 0, 1, []
        for n in range(num):
            L.append(a)
            a, b = b, a + b
            self.numbers = L

    def __str__(self):
        return str(self.numbers)

    def __len__(self):
        return len(self.numbers)


f = Fib(10)
print(f)
print(len(f))

结果:

[0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
10

Process finished with exit code 0

__hash__和__eq__

python有两个hash库,密码学的hashlib,zlib的adler32()与crc32()。

对于简单数字这两个库都不用,直接用hash函数就行,hash函数常用于set,dict等定位其中元素。

python每个对象都有个id,本质上id是其内存地址,is比较是基于id的,可用id(x)查看其值,而基类object的__hash__方法就是将其id/16取整后作为integer返回。

需要注意的是只有immutable数值类型才能用hash方法,list与dict没有。所以,如果我们创建的是mutable的对象,就让hash函数返回None就行了。

__eq__() 方法:用于==对比,是基于hash值的。   

对于immutable object,hash返回基于id的变数,eq用id相比就可以了。而mutable object写eq,hash返回None。

class DemoPar:
    insure = False

    def __init__(self, rank, suit, hard, soft):
        self.rank = rank
        self.suit = suit
        self.hard = hard
        self.soft = soft

    def __repr__(self):
        return "{__class__.__name__}(suit={suit!r}, rank={rank!r})". \
            format(__class__=self.__class__, **self.__dict__)

    def __str__(self):
        return "{rank}{suit}".format(**self.__dict__)

    def __eq__(self, other):
        return self.suit == other.suit and self.rank == other.rank

    def __hash__(self):
        return hash(self.suit) ^ hash(self.rank)


class DemoSub(DemoPar):
    insure = True

    def __init__(self, rank, suit):
        super().__init__('A', suit, 1, 11)


demoSubOne = DemoSub(1, '?')
demoSubTwo = DemoSub(1, '?')

print(id(demoSubOne), id(demoSubTwo))
print(id(demoSubOne) / 16, id(demoSubTwo) / 16)
print(hash(demoSubOne), hash(demoSubTwo))
print(demoSubOne == demoSubTwo)
print(demoSubOne is demoSubTwo)

结果:

1638200644552 1638200644608
102387540284.5 102387540288.0
-6769852609266822034 -6769852609266822034
True
False

Process finished with exit code 0

 

参考

  1. https://blog.csdn.net/qianguozheng/article/details/50396776
  2. http://www.cnblogs.com/Eva-J/articles/7351812.html#_label7
  3. http://python.jobbole.com/86506/
  4. https://blog.csdn.net/yaokai_assultmaster/article/details/70256621
  5. https://blog.csdn.net/lis_12/article/details/54631368
  6. https://www.cnblogs.com/superxuezhazha/p/5792199.html
  7. https://www.cnblogs.com/pengsixiong/p/5381850.html

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值