构造函数时进行类型检测和避免冲突

时间:2018-07-30 03:44:35

标签: python python-3.x

感谢大家的帮助。我把范围缩小了一点。如果您在脚本和类中同时查看HERE并运行该脚本,您将看到发生了什么。

ADD行打印“ 789 789”

何时应打印“ 456 789”

正在发生的事情,是在 new 中,该类正在检测传入参数的类型。但是,如果传入的对象具有与构造函数相同的类型,则它似乎是将传入的对象分页到自身(在类级别),而不是返回旧的对象。我唯一想到的就是会导致456变乳。

那么,如何在构造函数中检测到与类相同类型的东西,并决定不将数据分页到类存储空间中,而是返回先前构造的对象?

import sys
import math

class Foo(): 

    # class level property

    num = int(0) 

    # 
    # Python Instantiation Customs: 
    # 
    # Processing polymorphic input new() MUST return something or 
    # an object?,  but init() cannot return anything. During runtime 
    # __new__ is running at the class level, while init is running 
    # at the instance level. 
    # 

    def __new__(self,*arg): 

        print ("arg type: ", type(arg[0]).__name__)

###  functionally the same as isinstance() below
#
#       if (type(arg[0]).__name__) == "type": 
#           if arg[0].__name__ == "Foo":
#               print ("\tinput was a Foo")
#               return arg[0] # objects of same type intercede

### HERE <------------------------------------- 
# 
# this creams ALL instances, because since we are a class 
# the properties of the incoming object, seem to overwride 
# the class, rather than exist as a separate data structure. 

        if (isinstance(arg[0], Foo)): 
            print ("\tinput was a Foo")
            return arg[0] # objects of same type intercede

        elif (type(arg[0]).__name__) == "int": 
            print ("\tinput was an int")
            self.inum = int(arg[0]) # integers store
            return self

        elif (type(arg[0]).__name__) == "str": 
            print ("\tinput was a str")
            self.inum = int(arg[0]) # strings become integers
            return self

        return self 

    def __init__(self,*arg):
        pass

    # 
    # because if I can do collision avoidance, I can instantiate 
    # inside overloaded operators: 
    # 

    def __add__(self,*arg): 

        print ("add operator overload")

        # no argument returns self

        if not arg: 
            return self

        # add to None or zero return self

        if not arg[0]: 
            return self

        knowntype = Foo.Foo(arg[0])

        # add to unknown type returns False

        if not knowntype: 
            return knowntype

        # both values are calculable, calculate and return a Foo

        typedresult = (self.inum + knowntype.inum) 

        return Foo.Foo(typedresult) 

    def __str__(self): # return a stringified int or empty string

        # since integers don't have character length, 
        # this tests the value, not the existence of:  

        if self.inum: 
            return str(self.inum)

        # so the property could still be zero and we have to 
        # test again for no reason. 

        elif self.inum == 0:
            return str(self.inum)   

        # return an empty str if nothing is defined. 

        return str("")

testfoo.py:

#! /usr/bin/python

import sys
import Foo 

# A python class is not transparent like in perl, it is an object 
# with unconditional inheritance forced on all instances that share 
# the same name. 

classhandle = Foo.Foo 

# The distinction between the special class object, and instance 
# objects is implicitly defined by whether there is a passed value at 
# constructor time. The following therefore does not work. 

# classhandle = Foo.Foo() 

# but we can still write and print from the class, and see it propagate, 
# without having any "object" memory allocated.  

print ("\nclasshandle: ", classhandle)
print ("classhandle classname: ", classhandle.__name__) # print the classname
print ("class level num: ", classhandle.num)     # print the default num
classhandle.classstring = "fdsa" # define an involuntary value for all instances

print ("\n")

# so now we can create some instances with passed properties. 

instance1 = Foo.Foo(int(123)) # 

print ("\ninstance1: ", instance1)
print ("involuntary property derived from special class memory space: ", instance1.classstring)
print ("instance property from int: ", instance1.inum)

print ("\n")

instance2 = Foo.Foo(str("456"))
print ("\ninstance2: ", instance2)
print ("instance2 property from int: ", instance2.inum)

# 
# instance3 stands for (shall we assume) some math that happened a 
# thousand lines ago in a class far far away. We REALLY don't 
# want to go chasing around to figure out what type it could possibly 
# be, because it could be polymorphic itself. Providing a black box so 
# that you don't have to do that, is after all, the whole point OOP. 
# 

print ("\npretend instance3 is unknowningly already a Foo")
instance3 = Foo.Foo(str("789"))

## So our class should be able to handle str,int,Foo types at constructor time. 

print ("\ninstance4 should be a handle to the same memory location as instance3")

instance4 = Foo.Foo(instance3) # SHOULD return instance3 on type collision

# because if it does, we should be able to hand all kinds of garbage to 
# overloaded operators, and they should remain type safe.  

# HERE <-----------------------------
# 
# the creation of instance4, changes the instance properties of instance2: 
# below, the instance properties inum, are now both "789". 

print ("ADDING: ", instance2.inum, " ", instance4.inum)

# instance6 = instance2 + instance4 # also should be a Foo object
# instance5 = instance4 + int(549) # instance5 should be a Foo object. 

2 个答案:

答案 0 :(得分:3)

  

在构造函数时如何返回一个非新对象?

通过重写构造方法__new__,而不是初始化方法__init__

__new__方法构造一个实例-通常通过调用超级对象的__new__来构造实例,该实例最终会上升到object.__new__,后者会进行实际分配和其他秘密操作,但是您可以覆盖它以返回一个预先存在的值。

__init__方法将由__new__构造的值传递给它,因此现在不构造该值为时已晚。

请注意,如果Foo.__new__返回一个Foo实例(无论是新创建的实例还是现有的实例),则将在其上调用Foo.__init__。因此,覆盖__new__以返回对现有对象的引用的类通常需要等幂__init__-通常,您根本不覆盖__init__,而是在{内进行所有初始化{1}}。


那里有许多简单的__new__方法的示例,但让我们展示一个实际上对您要的内容进行简化的示例:

__new__

现在:

class Spam:
    _instances = {}
    def __new__(cls, value):
        if value not in cls._instances:
            cls._instances[value] = super().__new__(cls)
            cls._instances[value].value = value
        return cls._instances[value]

请注意,我确保使用>>> s1 = Spam(1) >>> s2 = Spam(2) >>> s3 = Spam(1) >>> s1 is s2 False >>> s1 is s3 True 而不是super,并且使用object 1 而不是cls._instances。所以:

Spam._instances

但是,与其将其隐藏在>>> class Eggs(Spam): ... pass >>> e4 = Eggs(4) >>> Spam(4) <__main__.Eggs at 0x12650d208> >>> Spam(4) is e4 True >>> class Cheese(Spam): ... _instances = {} >>> c5 = Cheese(5) >>> Spam(5) <__main__.Spam at 0x126c28748> >>> Spam(5) is c5 False 方法中,不如使用类方法的替代构造函数,甚至是单独的工厂函数,可能是一个更好的选择。

对于某些类型(例如,像__new__这样的简单不可变容器),用户没有理由关心tuple返回一个新元组还是一个现有元组,因此覆盖是有意义的构造函数。但是对于某些其他类型,尤其是可变类型,它可能导致混乱。

最好的测试方法是问问自己(或类似情况)是否会使您的用户感到困惑:

tuple(…)
  • 如果那不可能发生(例如,因为>>> f1 = Foo(x) >>> f2 = Foo(x) >>> f1.spam = 1 >>> f2.spam = 2 >>> f1.spam 2 是不可变的),请覆盖Foo
  • 如果这正是用户期望的结果(例如,因为__new__是具有实际Foo的某个对象的代理,并且同一对象的两个代理最好看到相同的{{1 }}),可能会覆盖spam
  • 如果会造成混淆,请不要覆盖spam

例如,使用类方法:

__new__

...如果__new__证明是真实的,则不太可能令人惊讶。


1。即使您将>>> f1 = Foo.from_x(x) >>> f2 = Foo.from_x(x) 定义为实例方法,并且其主体看起来像是一个类方法,它实际上是一个静态方法,该方法会传递您要构造的类(将是f1 is f2__new__的子类作为普通的第一个参数,并在其后传递构造函数参数(和关键字参数)。

答案 1 :(得分:0)

感谢所有提供帮助的人!为了理解如何重构已经编写的,但是存在可伸缩性问题的现有程序,已经提出了这个答案。以下是完整的工作示例。它显示的是:

在传入类型既是用户定义的又是内置的的情况下,能够测试传入类型并避免在构造函数时不必要的对象重复的功能。通过重新定义的运算符或方法即时构建的能力。这些功能对于编写可扩展的可支持API代码是必需的。 YMMV。

Foo.py

import sys
import math

class Foo(): 

    # class level property

    num = int(0) 

    # 
    # Python Instantiation Customs: 
    # 
    # Processing polymorphic input new() MUST return something or 
    # an object,  but init() MAYNOT return anything. During runtime 
    # __new__ is running at the class level, while __init__ is 
    # running at the instance level. 
    # 

    def __new__(cls,*arg): 

        print ("arg type: ", type(arg[0]).__name__)

        # since we are functioning at the class level, type() 
        # is reaching down into a non-public namespace, 
        # called "type" which is presumably something that 
        # all objects are ultimately derived from. 

        # functionally this is the same as isinstance() 

        if (type(arg[0]).__name__) == "Foo": 
            fooid = id(arg[0])
            print ("\tinput was a Foo: ", fooid)
            return arg[0] # objects of same type intercede

        # at the class level here, we are calling into super() for 
        # the constructor. This is presumably derived from the type() 
        # namespace, which when handed a classname, makes one of 
        # whatever it was asked for, rather than one of itself.  

        elif (type(arg[0]).__name__) == "int": 
            self = super().__new__(cls)
            self.inum = int(arg[0]) # integers store
            fooid = id(self)
            print ("\tinput was an int: ", fooid)
            return (self)

        elif (type(arg[0]).__name__) == "str": 
            self = super().__new__(cls)
            self.inum = int(arg[0]) # strings become integers
            fooid = id(self)
            print ("\tinput was a str: ", fooid)
            return (self)

#   def __init__(self,*arg):
#       pass

    # 
    # because if I can do collision avoidance, I can instantiate 
    # inside overloaded operators: 
    # 

    def __add__(self,*arg): 

        argtype = type(arg[0]).__name__

        print ("add overload in class:", self.__class__)

        if argtype == "Foo" or argtype == "str" or argtype == "int":   

            print ("\tfrom a supported type")

            # early exit for zero

            if not arg[0]: 
                return self

            # localized = Foo.Foo(arg[0])

            # FAILS: AttributeError: type object 'Foo' has no attribute 'Foo'
            # You can't call a constructor the same way from inside and outside


            localized = Foo(arg[0])

            print ("\tself class: ", self.__class__)
            print ("\tself number: ", self.inum)
            print ()
            print ("\tlocalized class: ", localized.__class__)
            print ("\tlocalized number: ", localized.inum)
            print ()

            answer = (self.inum + localized.inum) 
            answer = Foo(answer)    

            print ("\tanswer class:", answer.__class__)
            print ("\tanswer sum result:", answer.inum)

            return answer

        assert(0), "Foo: cannot add an unsupported type"

    def __str__(self): # return a stringified int or empty string

        # Allow the class to stringify as if it were an int. 

        if self.inum >= 0: 
            return str(self.inum)

testfoo.py

#! /usr/bin/python

import sys
import Foo 

# A python class is not transparent like in perl, it is an object 
# with unconditional inheritance forced on all instances that share 
# the same name. 

classhandle = Foo.Foo 

# The distinction between the special class object, and instance 
# objects is implicitly defined by whether there is a passed value at 
# constructor time. The following therefore does not work. 

# classhandle = Foo.Foo() 

# but we can still write and print from the class, and see it propagate, 
# without having any "object" memory allocated.  

print ("\nclasshandle: ", classhandle)
print ("classhandle classname: ", classhandle.__name__) # print the classname
print ("class level num: ", classhandle.num)     # print the default num
classhandle.classstring = "fdsa" # define an involuntary value for all instances

print ("\n")

# so now we can create some instances with passed properties. 

instance1 = Foo.Foo(int(123)) # 

print ("\ninstance1: ", instance1)
print ("involuntary property derived from special class memory space: ", instance1.classstring)
print ("instance property from int: ", instance1.inum)

print ("\n")

instance2 = Foo.Foo(str("456"))
print ("\ninstance2: ", instance2)
print ("instance2 property from int: ", instance2.inum)

# 
# instance3 stands for (shall we assume) some math that happened a 
# thousand lines ago in a class far far away. We REALLY don't 
# want to go chasing around to figure out what type it could possibly 
# be, because it could be polymorphic itself. Providing a black box so 
# that you don't have to do that, is after all, the whole point OOP. 
# 

print ("\npretend instance3 is unknowningly already a Foo\n")
instance3 = Foo.Foo(str("789"))

## So our class should be able to handle str,int,Foo types at constructor time. 

print ("\ninstance4 should be a handle to the same memory location as instance3\n")

instance4 = Foo.Foo(instance3) # SHOULD return instance3 on type collision

print ("instance4: ", instance4) 

# because if it does, we should be able to hand all kinds of garbage to 
# overloaded operators, and they should remain type safe.  

# since we are now different instances these are now different:  

print ("\nADDING:_____________________\n", instance2.inum, " ", instance4.inum)

instance5 = instance4 + int(549) # instance5 should be a Foo object. 
print ("\n\tAdd instance4, 549, instance5: ", instance4, " ", int(549), " ", instance5, "\n")

instance6 = instance2 + instance4 # also should be a Foo object
print ("\n\tAdd instance2, instance4, instance6: ", instance2, " ", instance4, " ", instance6, "\n")

print ("stringified instance6: ", str(instance6))