【问题标题】:Type detection and collision avoidance at constructor time构造函数时的类型检测和碰撞避免
【发布时间】:2018-07-30 03:44:34
【问题描述】:

感谢大家迄今为止的帮助。我已经缩小了一点。如果您在脚本和类中都查看 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. 

【问题讨论】:

  • 这可能是一个迂腐的观点,但我认为重要的是,“那些输入类型可能是基元或对象。” Python 中没有原始类型。一切都是对象。
  • “一切都是对象”:对我来说这似乎是不正确的。在 new 中(构造函数阶段?) type() 没有找到传递结构的命名空间,但在 init 中找到了。在 perl 术语中,这意味着数据结构在 new 中是未受祝福的,并且还不是对象。这是一种强制性的习俗。据我所知,除了让一群人从一个共同的角度看待代码之外,没有任何理由。
  • 我不明白你在这里说什么,以及它与 Python 中的 everything are an object 有何关系。同样,没有原始类型。当您说“type() 未找到传递结构的名称空间”时,您的意思不清楚。通过什么结构?什么命名空间?命名空间实际上只是 Python 中的对象,通常(尽管不总是)dict 对象。您似乎正在尝试应用 Pearl 中不适用于 Python 的概念,没有“有福和无福”的对象
  • 无论如何,这里有一个很好的答案来描述你对__new____init__ 的错误。此外,(type(arg[0]).__name__) == "int" 这个构造不是你应该如何在 Python 中进行类型检查。如果你想要一个特定的类型,你使用type(obj) is int,如果你想处理子类关系,使用isinstance(obj, int)__name__ 属性是您所说的命名空间吗?
  • @JamesAanderson 您可以在__super__ 中进行操作,就像 abarnart 的回答一样。 instance = super().__new__(cls); instance.inum = int(arg[0]); return instance。请记住,__new__ 应该返回一个实例,而不是 self,它实际上是类,这就是为什么在实现 __new__ 时按惯例将其命名为 cls 只是为了提醒您。

标签: python python-3.x


【解决方案1】:

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

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

__new__ 方法构造一个实例——通常通过调用 super 的 __new__,最终达到 object.__new__,它执行实际分配和其他隐藏的东西,但您可以覆盖它以返回一个预先存在的值。

__init__ 方法收到了一个已经由 __new__ 构造的值,所以它不构造该值已经太迟了。

注意,如果Foo.__new__ 返回一个Foo 实例(无论是新创建的还是现有的),Foo.__init__ 将被调用。因此,覆盖__new__ 以返回对现有对象的引用的类通常需要一个幂等的__init__——通常,您根本不覆盖__init__,而是在__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,以及cls._instances1 而不是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 是具有实际 spam 的某个对象的代理,并且同一对象的两个代理最好看到相同的 spam),可能会覆盖__new__
  • 如果会造成混淆,可能不要覆盖__new__

例如,使用类方法:

>>> f1 = Foo.from_x(x)
>>> f2 = Foo.from_x(x)

…如果f1 is f2 被证明是真的,那就不太可能令人惊讶了。


1。即使您将__new__ 定义为实例方法,并且它的主体看起来像类方法,但它实际上是一个静态方法,它通过您尝试构造的类(将是Spam 或@ 的子类) 987654359@) 作为普通的第一个参数,之后传递构造函数参数(和关键字参数)。

【讨论】:

  • @martineau 完成。我想我想出了与 OP 的预期用途相关的最简单的例子。
  • @martineau 你认为它还需要一个示例from_x 替代构造函数实现,还是答案已经太长而那部分已经足够明显了?
  • 在第一个代码示例中,这看起来就像您创建了一个对象注册表。我不需要那样做。我不希望针对特定的先前对象进行验证,仅针对任何对象的命名空间进行验证。我正在尝试规范化不同的输入类型,以便我导出的接口与类型无关。为此,我必须知道我的输入类型是什么,但我只需要知道它们是什么,而不是它们是什么。
  • @JamesAanderson 您仍然可以使用这种方法执行此操作,只需检查 type(value) 并改为在您的 __init__ 中进行处理,例如if isinstance(value, str): return super().__new__(cls, int(value)) EDIT 的效果相反,您必须将属性添加到 super().__new__ 返回的内容中,如上例所示
  • @JamesAanderson 我只需要一个 something 的例子,它有理由不总是在 __new__ 中返回一个新对象,而享元注册是我最简单的例子可以想到,所以我的例子的逻辑不会妨碍你需要理解的部分。如果这还不足以让您理解这个想法并构建您需要的东西,请告诉我不清楚的地方,我会更新它。
【解决方案2】:

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

在给定用户定义和内置的传入类型的情况下,能够在构造函数时测试传入类型并避免不必要的对象重复。从重新定义的运算符或方法动态构建的能力。这些功能是编写可扩展的可支持 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))

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2012-12-23
    • 2011-09-05
    • 2014-07-03
    • 1970-01-01
    • 2012-11-27
    • 2012-05-09
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多