绑定方法及面向对象小结

绑定方法

对象的绑定方法

在类中没有被任何装饰器修饰的方法就是 绑定到对象的方法,这类方法专门为对象定制。

class Person:
    country = "China"

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

    def speak(self):
        print(self.name + ', ' + str(self.age))


p = Person('Kitty', 18)


print(p.__dict__)  # {'name': 'Kitty', 'age': 18}
print(Person.__dict__['speak'])  # <function Person.speak at 0x10f0dd268>

speak 即为绑定到对象的方法,这个方法不在对象的名称空间中,而是在类的名称空间中。

通过对象调用绑定到对象的方法,会有一个自动传值的过程,即自动将当前对象传递给方法的第一个参数(self,一般都叫 self,也可以写成别的名称);若是使用类调用,则第一个参数需要手动传值。

p = Person('Kitty', 18)
# 通过对象调用
p.speak()  # Kitty, 18


 # 通过类调用
Person.speak(p)  # Kitty, 18

类的绑定方法

类中使用 @classmethod 修饰的方法就是绑定到类的方法。这类方法专门为类定制。通过类名调用绑定到类的方法时,会将类本身当做参数传给类方法的第一个参数。

class Operate_database():
    host = '192.168.0.5'
    port = '3306'
    user = 'abc'
    password = '123456'

    @classmethod
    def connect(cls):  # 约定俗成第一个参数名为cls,也可以定义为其他参数名
        print(cls)
        print(cls.host + ':' + cls.port + ' ' + cls.user + '/' + cls.password)


Operate_database.connect()
'''
<class '__main__.Operate_database'>
192.168.0.5:3306 abc/123456
'''

通过对象也可以调用,只是默认传递的第一个参数还是这个对象对应的类。

Operate_database().connect()  # 输出结果一致
'''
<class '__main__.Operate_database'>
192.168.0.5:3306 abc/123456
'''

非绑定方法

在类内部使用 @staticmethod 修饰的方法即为非绑定方法,这类方法和普通定义的函数没有区别,不与类或对象绑定,谁都可以调用,且没有自动传值的效果。

import hashlib


class Operate_database():
    def __init__(self, host, port, user, password):
        self.host = host
        self.port = port
        self.user = user
        self.password = password

    @staticmethod
    def get_passwrod(salt, password):
        m = hashlib.md5(salt.encode('utf-8'))  # 加盐处理
        m.update(password.encode('utf-8'))
        return m.hexdigest()


hash_password = Operate_database.get_passwrod('lala', '123456')  # 通过类来调用
print(hash_password)  # f7a1cc409ed6f51058c2b4a94a7e1956


p = Operate_database('192.168.0.5', '3306', 'abc', '123456')
hash_password = p.get_passwrod(p.user, p.password)  # 也可以通过对象调用
print(hash_password)  # 0659c7992e268962384eb17fafe88364

简而言之,非绑定方法就是将普通方法放到了类的内部。

练习

假设我们现在有一个需求,需要让 Mysql 实例化出的对象可以从文件 settings.py 中读取数据。

# settings.py
IP = '1.1.1.10'
PORT = 3306
NET = 27

# test.py
import uuid

class Mysql:
    def __init__(self, ip, port, net):
        self.uid = self.create_uid()
        self.ip = ip
        self.port = port
        self.net = net

    def tell_info(self):
        """查看ip地址和端口号"""
        print('%s:%s' % (self.ip, self.port))

    @classmethod
    def from_conf(cls):
        return cls(IP, NET, PORT)

    @staticmethod
    def func(x, y):
        print('不与任何人绑定')

    @staticmethod
    def create_uid():
        """随机生成一个字符串"""
        return uuid.uuid1()


# 默认的实例化方式:类名()
obj = Mysql('10.10.0.9', 3307, 27)
obj.tell_info()  # 10.10.0.9:3307

绑定方法小结

如果函数体代码需要用外部传入的类,则应该将该函数定义成绑定给类的方法

如果函数体代码需要用外部传入的对象,则应该将该函数定义成绑定给对象的方法

# 一种新的实例化方式:从配置文件中读取配置完成实例化
obj1 = Mysql.from_conf()
obj1.tell_info()  # 1.1.1.10:27

print(obj.tell_info)  # <bound method Mysql.tell_info of <__main__.Mysql object at 0x10f469240>>

print(obj.from_conf)  # <bound method Mysql.from_conf of <class '__main__.Mysql'>>

非绑定方法小结

如果函数体代码既不需要外部传入的类也不需要外部传入的对象,则应该将该函数定义成非绑定方法/普通函数

obj.func(1, 2)
# 不与任何人绑定
Mysql.func(3, 4)
# 不与任何人绑定

print(obj.func)  # <function Mysql.func at 0x10f10e620>

print(Mysql.func)  # <function Mysql.func at 0x10f10e620>

print(obj.uid)  # a78489ec-92a3-11e9-b4d7-acde48001122

面向对象进阶小结

类的继承

继承父类,则会有父类的所有属性和方法

class ParentClass1():
	pass

class ParentClass2():
	pass

class SubClass(ParentClass1,ParentClass2):
	pass

类的派生

继承父类的同时自己有 init,然后也需要父类的 init

class ParentClass1():
	def __init__(self,name):
		pass


class SubClass(ParentClass):
	def __init__(self,age):
		# 1. ParentClass1.__init__(self,name)
		# 2. super(SubClass,self).__init__(name)
		self.age = age

类的组合

类对象可以引用/当做参数传入/当做返回值/当做容器元素,类似于函数对象

class ParentClass1():
	count = 0
	def __init__(self,name):
		pass

class SubClass(ParentClass):
	def __init__(self,age):
		self.age = age

pc = ParentClass1()
sc = SubClass()

sc.parent_class = pc  # 组合
sc.parent_class.count  # 0

菱形继承问题

新式类:继承 object 的类,python3 中全是新式类

经典类:没有继承 object 的类,只有 python2 中有

在菱形继承的时候,新式类是广度优先(老祖宗最后找);经典类深度优先(一路找到底,再找旁边的)

多态与多态性

一种事物的多种形态,动物–>人/猪/狗

# 多态
import abc

class Animal(metaclass=abc.ABCmeta):
	@abc.abstractmethod
	def eat():
		print('eat')

class People(Animal):
	def eat():
		pass

class Pig(Animal):
	def eat():
		pass
    def run():
        pass

class Dog(Animal):  # 报错
	def run():
		pass

# 多态性
peo = People()
peo.eat()
peo1 = People()
peo1.eat()
pig = Pig()
pig.eat()

def func(obj):
	obj.eat()

class Cat(Animal):
	def eat():
		pass
cat = Cat()

func(cat)

鸭子类型:只要长得像鸭子,叫的像鸭子,游泳像鸭子,就是鸭子

# 继承一个父类,父类中有方法,在子类中重写方法
# 鸭子类型:不需要显示继承一个类,只要多个类中有同样的属性或方法,我们把它们称之为一种类,python,go
# 非鸭子类类型语言:如果要属于同一类,必须显示的继承某个基类,这样才属于基类这个类型,java

# python语言建议使用鸭子类型(约定),但在实际开发中,我们经常不使用鸭子类型这种特性,出错概率低
# 实际编码:要么认为约定必须有哪些方法(符合鸭子类型),可控性低;要么强制约定有哪些方法(abc模块,使用抛异常)

# java中:重写:重写是子类对父类的允许访问的方法的实现过程进行重新编写, 返回值和形参都不能改变
# java中:重载:是在一个类里面,方法名字相同,而参数不同。返回类型可以相同也可以不同

类的封装

隐藏属性,只有类内部可以访问,类外部不可以访问

class Foo():
	__count = 0

	def get_count(self):
		return self.__count

f = Foo()
f.__count  # 报错
f._Foo__count # 不能这样做

类的 property 特性

把方法变成属性引用

class People():
	def __init__(self,height,weight):
		self.height = height
		self.weight = weight

	@property
	def bmi(self):
		return weight/(height**2)

	@bmi.setter
	def bmi(self,value)
		print('setter')

    @bmi.deleter
    def bmi(self):
        print('delter')

peo = People
peo.bmi

类与对象的绑定方法和非绑定方法

没有任何装饰器装饰的方法就是对象的绑定方法, 类能调用, 但是必须得传参给 self

被 @classmethod 装饰器装饰的方法是类的绑定方法,参数写成 cls, cls 是类本身, 对象也能调用, 参数 cls 还是类本身

被 @staticmethod 装饰器装饰的方法就是非绑定方法, 就是一个普通的函数

isinstance 与 type

在游戏项目中,我们会在每个接口验证客户端传过来的参数类型,如果验证不通过,返回给客户端“参数错误”错误码。

这样做不但便于调试,而且增加健壮性。因为客户端是可以作弊的,不要轻易相信客户端传过来的参数。

验证类型用 type 函数,非常好用,比如

print(type('foo') == str)  # True

print(type(2.3) in (int, float))  # True

既然有了 type()来判断类型,为什么还有 isinstance()呢?

一个明显的区别是在判断子类。

type()不会认为子类是一种父类类型;isinstance()会认为子类是一种父类类型。

千言不如一码。

class Foo(object):
    pass

class Bar(Foo):
    pass

print(type(Foo()) == Foo)  # True

print(type(Bar()) == Foo)  # False

# isinstance参数为对象和类
print(isinstance(Bar(),Foo))  # True

需要注意的是,旧式类跟新式类的 type()结果是不一样的。旧式类都是<type ‘instance’>。

# python2.+
class A:
    pass

class B:
    pass

class C(object):
    pass

print('old style class',type(A()))  # old style class <type 'instance'>

print('old style class',type(B()))  # old style class <type 'instance'>

print('new style class',type(C()))  # new style class <class '__main__.C'>

print(type(A()) == type(B()))  # True

注意:不存在说 isinstance 比 type 更好。只有哪个更适合需求。

issubclass

class Parent:
    pass

class Sub(Parent):
    pass


print(issubclass(Sub, Parent))  # True
print(issubclass(Parent, object))  # True