我是Python和OOP的新手。假设我有两个类Base1
和Base2
。假设Base1
计算了一些值a1
,b1
,并且Base2
有一个方法可以将两个值相乘。我的问题是,将a1
的{{1}}和b1
传递给Base1
的正确方法是什么?
通过定义派生类Base2
来实现此目的的方法如下:
Derived
然后:
class Base1:
def __init__(self, x1 , y1):
# perform some computation on x1 and y1
# store result in a1 and b1
self.a1=x1
self.b1=y1
class Base2:
def __init__(self, x2 , y2):
self.a2=x2
self.b2=y2
self.c2=self.multiply(self.a1,self.b1) # using a1 and b1 of Base1
def multiply(self, p,q):
return p*q
class Derived(Base1,Base2):
def __init__(self):
self.describe='Derived Class'
Base1.__init__(self,3,4)
Base2.__init__(self,5,6)
但是,在更复杂的情况下,很容易忘记f=Derived()
f.c2=12
,self.a1
的来源。对我来说,为什么两个基类可以这种方式访问彼此的属性和方法也不明显?
编辑:这是Python 2.7.10。
答案 0 :(得分:2)
在Python 2中,始终继承自object
。否则你会得到不应该使用的旧式类:
class Base1(object):
def __init__(self, x1 , y1):
# perform some computation on x1 and y1
# store result in a1 and b1
self.a1 = x1
self.b1 = y1
class Base2(object):
def __init__(self, x2 , y2):
self.a2 = x2
self.b2 = y2
self.c2 = self.multiply(self.a1, self.b1) # using a1 and b1 of Base1
def multiply(self, p,q):
return p*q
class Derived(Base1, Base2):
def __init__(self):
self.describe='Derived Class'
Base1.__init__(self, 3, 4)
Base2.__init__(self, 5, 6)
Python使用方法解析顺序(mro)查找方法。您可以找到当前的订单:
>>> Derived.mro()
[__main__.Derived, __main__.Base1, __main__.Base2, object]
这意味着Python首先在类multiply()
中查找方法Derived
。如果它在那里找到它,它将使用它。否则它会继续使用mro进行搜索,直到找到它为止。尝试更改Base1
中Base2
和Derived(Base1,Base2)
的顺序,并检查这对mro的影响:
class Derived2(Base2, Base1):
pass
>>> Derived2.mro()
[__main__.Derived2, __main__.Base2, __main__.Base1, object]
self
始终引用实例。在这种情况下f
(f = Derived()
)。 f
获取其属性并不重要。赋值self.x = something
可以在任何涉及的类的任何方法中发生。
答案 1 :(得分:1)
Python是动态的。在尝试访问它们的实际代码行之前,它不会检查属性是否存在。所以你的代码恰好起作用了。仅仅因为你可以做到这一点,并不意味着你应该。 Python依赖于你在组织代码时做出正确的决定,而不是试图保护你不要做愚蠢的事情;我们这里的所有成年人都是。
原因实际上归结为Python是一种动态语言。没有类型分配给变量,因此Python不会提前知道该变量的期望值。除了这个设计决策之外,Python在实际尝试访问属性之前并没有真正检查属性是否存在。
让我们稍微修改Base2
以获得一些清晰度。首先,让Base1
和Base2
继承自object
。 (这是必要的,以便我们可以告诉我们实际处理的类型。)然后将以下print
添加到Base2
:
class Base2(object):
def __init__(self, x2 , y2):
print type(self)
print id(self)
self.a2=x2
self.b2=y2
self.c2=self.multiply(self.a1,self.b1) # using a1 and b1 of Base1
def multiply(self, p,q):
return p*q
现在让我们尝试一下:
>>> d = Derived()
<class '__main__.Derived'>
42223600
>>> print id(d)
42223600
所以我们可以看到即使在Base2
的初始化程序中,Python也知道self
包含Derived
个实例。因为Python使用鸭子类型,所以它不会提前检查self
是否具有a1
或b1
属性;它只是试图访问它们。如果他们在那里,它的工作原理。如果不是,则会抛出错误。您可以通过直接实例化Base2
实例来看到这一点:
>>> Base2(1, 2)
<class '__main__.Base2'>
41403888
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 7, in __init__
AttributeError: 'Base2' object has no attribute 'a1'
请注意,即使出现错误,它仍会在尝试访问print
之前执行a1
语句。在代码行执行之前,Python不会检查属性是否存在。
我们可以变得更疯狂,并在代码运行时向对象添加属性:
>>> b = Base1(1,2)
>>> b.a1
1
>>> b.notyet
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'Base1' object has no attribute 'notyet'
>>> b.notyet = 'adding an attribute'
>>> b.notyet
'adding an attribute'
Base2
不应尝试在不继承Base1
的情况下访问这些变量。即使您只实例化Derived
的实例,也可以这样做,您应该假设其他人可能直接使用Base2
或创建一个继承自Base2
的其他类。而不是Base1
。换句话说,你应该忽略这是可能的。 Python中有很多东西都是这样的。它并不限制你这样做,但你不应该这样做,因为它们会使你或其他人感到困惑或以后会引起问题。众所周知,Python不会尝试限制功能,而是依赖于 you (开发人员)明智地使用该语言。社区对这种方法有一句流行语:我们都是这里的所有成年人。
我将假设Base2
主要用于提供multiply
方法的混合。在这种情况下,我们应该在子类c2
上定义Derived
,因为它可以访问multiply
和属性a1
和b1
。< / p>
对于纯粹派生的值,您应该使用property
:
class Derived(Base1,Base2):
def __init__(self):
self.describe='Derived Class'
Base1.__init__(self,3,4)
Base2.__init__(self,5,6)
@property
def c2(self):
return self.multiply(self.a1,self.b1) # using a1 and b1 of Base1
这可以防止调用者更改值(除非您明确创建一个setter),并避免跟踪它来自何处的问题。它总是会在运行中计算,即使使用看起来就像使用普通属性一样:
x = Derived()
print x.c2
这会按预期提供12
。
答案 2 :(得分:0)
您只需在基类中提供方法multiply
,该方法假定已在基类中定义a1
和b1
。
所以代码就像
class Base1(object):
def __init__(self,a1,b1):
self.a1 = a1
self.b1 = b1
class Base2(Base1):
def multiply():
return self.a1*self.b1
这里你没有为base2提供__init__
,它将使用base1的init方法,它接受参数a1
和a2
所以现在
base = Base2(5,4)
print(base.multiply())